Fix memory corruption through InitString and second-engine loading
[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         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 void
902 Load (ChessProgramState *cps, int i)
903 {
904     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
905     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
906         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
907         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
908         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
909         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
910         appData.firstProtocolVersion = PROTOVER;
911         ParseArgsFromString(buf);
912         SwapEngines(i);
913         ReplaceEngine(cps, i);
914         FloatToFront(&appData.recentEngineList, engineLine);
915         return;
916     }
917     p = engineName;
918     while(q = strchr(p, SLASH)) p = q+1;
919     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
920     if(engineDir[0] != NULLCHAR) {
921         ASSIGN(appData.directory[i], engineDir);
922     } else if(p != engineName) { // derive directory from engine path, when not given
923         p[-1] = 0;
924         ASSIGN(appData.directory[i], engineName);
925         p[-1] = SLASH;
926         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
927     } else { ASSIGN(appData.directory[i], "."); }
928     if(params[0]) {
929         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
930         snprintf(command, MSG_SIZ, "%s %s", p, params);
931         p = command;
932     }
933     ASSIGN(appData.chessProgram[i], p);
934     appData.isUCI[i] = isUCI;
935     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
936     appData.hasOwnBookUCI[i] = hasBook;
937     if(!nickName[0]) useNick = FALSE;
938     if(useNick) ASSIGN(appData.pgnName[i], nickName);
939     if(addToList) {
940         int len;
941         char quote;
942         q = firstChessProgramNames;
943         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
944         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
945         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
946                         quote, p, quote, appData.directory[i], 
947                         useNick ? " -fn \"" : "",
948                         useNick ? nickName : "",
949                         useNick ? "\"" : "",
950                         v1 ? " -firstProtocolVersion 1" : "",
951                         hasBook ? "" : " -fNoOwnBookUCI",
952                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
953                         storeVariant ? " -variant " : "",
954                         storeVariant ? VariantName(gameInfo.variant) : "");
955         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
956         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
957         if(q)   free(q);
958         FloatToFront(&appData.recentEngineList, buf);
959     }
960     ReplaceEngine(cps, i);
961 }
962
963 void
964 InitTimeControls ()
965 {
966     int matched, min, sec;
967     /*
968      * Parse timeControl resource
969      */
970     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
971                           appData.movesPerSession)) {
972         char buf[MSG_SIZ];
973         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
974         DisplayFatalError(buf, 0, 2);
975     }
976
977     /*
978      * Parse searchTime resource
979      */
980     if (*appData.searchTime != NULLCHAR) {
981         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
982         if (matched == 1) {
983             searchTime = min * 60;
984         } else if (matched == 2) {
985             searchTime = min * 60 + sec;
986         } else {
987             char buf[MSG_SIZ];
988             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
989             DisplayFatalError(buf, 0, 2);
990         }
991     }
992 }
993
994 void
995 InitBackEnd1 ()
996 {
997
998     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
999     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1000
1001     GetTimeMark(&programStartTime);
1002     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1003     appData.seedBase = random() + (random()<<15);
1004     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1005
1006     ClearProgramStats();
1007     programStats.ok_to_send = 1;
1008     programStats.seen_stat = 0;
1009
1010     /*
1011      * Initialize game list
1012      */
1013     ListNew(&gameList);
1014
1015
1016     /*
1017      * Internet chess server status
1018      */
1019     if (appData.icsActive) {
1020         appData.matchMode = FALSE;
1021         appData.matchGames = 0;
1022 #if ZIPPY
1023         appData.noChessProgram = !appData.zippyPlay;
1024 #else
1025         appData.zippyPlay = FALSE;
1026         appData.zippyTalk = FALSE;
1027         appData.noChessProgram = TRUE;
1028 #endif
1029         if (*appData.icsHelper != NULLCHAR) {
1030             appData.useTelnet = TRUE;
1031             appData.telnetProgram = appData.icsHelper;
1032         }
1033     } else {
1034         appData.zippyTalk = appData.zippyPlay = FALSE;
1035     }
1036
1037     /* [AS] Initialize pv info list [HGM] and game state */
1038     {
1039         int i, j;
1040
1041         for( i=0; i<=framePtr; i++ ) {
1042             pvInfoList[i].depth = -1;
1043             boards[i][EP_STATUS] = EP_NONE;
1044             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1045         }
1046     }
1047
1048     InitTimeControls();
1049
1050     /* [AS] Adjudication threshold */
1051     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1052
1053     InitEngine(&first, 0);
1054     InitEngine(&second, 1);
1055     CommonEngineInit();
1056
1057     pairing.which = "pairing"; // pairing engine
1058     pairing.pr = NoProc;
1059     pairing.isr = NULL;
1060     pairing.program = appData.pairingEngine;
1061     pairing.host = "localhost";
1062     pairing.dir = ".";
1063
1064     if (appData.icsActive) {
1065         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1066     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1067         appData.clockMode = FALSE;
1068         first.sendTime = second.sendTime = 0;
1069     }
1070
1071 #if ZIPPY
1072     /* Override some settings from environment variables, for backward
1073        compatibility.  Unfortunately it's not feasible to have the env
1074        vars just set defaults, at least in xboard.  Ugh.
1075     */
1076     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1077       ZippyInit();
1078     }
1079 #endif
1080
1081     if (!appData.icsActive) {
1082       char buf[MSG_SIZ];
1083       int len;
1084
1085       /* Check for variants that are supported only in ICS mode,
1086          or not at all.  Some that are accepted here nevertheless
1087          have bugs; see comments below.
1088       */
1089       VariantClass variant = StringToVariant(appData.variant);
1090       switch (variant) {
1091       case VariantBughouse:     /* need four players and two boards */
1092       case VariantKriegspiel:   /* need to hide pieces and move details */
1093         /* case VariantFischeRandom: (Fabien: moved below) */
1094         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1095         if( (len >= MSG_SIZ) && appData.debugMode )
1096           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1097
1098         DisplayFatalError(buf, 0, 2);
1099         return;
1100
1101       case VariantUnknown:
1102       case VariantLoadable:
1103       case Variant29:
1104       case Variant30:
1105       case Variant31:
1106       case Variant32:
1107       case Variant33:
1108       case Variant34:
1109       case Variant35:
1110       case Variant36:
1111       default:
1112         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1113         if( (len >= MSG_SIZ) && appData.debugMode )
1114           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1115
1116         DisplayFatalError(buf, 0, 2);
1117         return;
1118
1119       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1120       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1121       case VariantGothic:     /* [HGM] should work */
1122       case VariantCapablanca: /* [HGM] should work */
1123       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1124       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1125       case VariantKnightmate: /* [HGM] should work */
1126       case VariantCylinder:   /* [HGM] untested */
1127       case VariantFalcon:     /* [HGM] untested */
1128       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1129                                  offboard interposition not understood */
1130       case VariantNormal:     /* definitely works! */
1131       case VariantWildCastle: /* pieces not automatically shuffled */
1132       case VariantNoCastle:   /* pieces not automatically shuffled */
1133       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1134       case VariantLosers:     /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantSuicide:    /* should work except for win condition,
1137                                  and doesn't know captures are mandatory */
1138       case VariantGiveaway:   /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantTwoKings:   /* should work */
1141       case VariantAtomic:     /* should work except for win condition */
1142       case Variant3Check:     /* should work except for win condition */
1143       case VariantShatranj:   /* should work except for all win conditions */
1144       case VariantMakruk:     /* should work except for draw countdown */
1145       case VariantBerolina:   /* might work if TestLegality is off */
1146       case VariantCapaRandom: /* should work */
1147       case VariantJanus:      /* should work */
1148       case VariantSuper:      /* experimental */
1149       case VariantGreat:      /* experimental, requires legality testing to be off */
1150       case VariantSChess:     /* S-Chess, should work */
1151       case VariantGrand:      /* should work */
1152       case VariantSpartan:    /* should work */
1153         break;
1154       }
1155     }
1156
1157 }
1158
1159 int
1160 NextIntegerFromString (char ** str, long * value)
1161 {
1162     int result = -1;
1163     char * s = *str;
1164
1165     while( *s == ' ' || *s == '\t' ) {
1166         s++;
1167     }
1168
1169     *value = 0;
1170
1171     if( *s >= '0' && *s <= '9' ) {
1172         while( *s >= '0' && *s <= '9' ) {
1173             *value = *value * 10 + (*s - '0');
1174             s++;
1175         }
1176
1177         result = 0;
1178     }
1179
1180     *str = s;
1181
1182     return result;
1183 }
1184
1185 int
1186 NextTimeControlFromString (char ** str, long * value)
1187 {
1188     long temp;
1189     int result = NextIntegerFromString( str, &temp );
1190
1191     if( result == 0 ) {
1192         *value = temp * 60; /* Minutes */
1193         if( **str == ':' ) {
1194             (*str)++;
1195             result = NextIntegerFromString( str, &temp );
1196             *value += temp; /* Seconds */
1197         }
1198     }
1199
1200     return result;
1201 }
1202
1203 int
1204 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1205 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1206     int result = -1, type = 0; long temp, temp2;
1207
1208     if(**str != ':') return -1; // old params remain in force!
1209     (*str)++;
1210     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1211     if( NextIntegerFromString( str, &temp ) ) return -1;
1212     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1213
1214     if(**str != '/') {
1215         /* time only: incremental or sudden-death time control */
1216         if(**str == '+') { /* increment follows; read it */
1217             (*str)++;
1218             if(**str == '!') type = *(*str)++; // Bronstein TC
1219             if(result = NextIntegerFromString( str, &temp2)) return -1;
1220             *inc = temp2 * 1000;
1221             if(**str == '.') { // read fraction of increment
1222                 char *start = ++(*str);
1223                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1224                 temp2 *= 1000;
1225                 while(start++ < *str) temp2 /= 10;
1226                 *inc += temp2;
1227             }
1228         } else *inc = 0;
1229         *moves = 0; *tc = temp * 1000; *incType = type;
1230         return 0;
1231     }
1232
1233     (*str)++; /* classical time control */
1234     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1235
1236     if(result == 0) {
1237         *moves = temp;
1238         *tc    = temp2 * 1000;
1239         *inc   = 0;
1240         *incType = type;
1241     }
1242     return result;
1243 }
1244
1245 int
1246 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1247 {   /* [HGM] get time to add from the multi-session time-control string */
1248     int incType, moves=1; /* kludge to force reading of first session */
1249     long time, increment;
1250     char *s = tcString;
1251
1252     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1253     do {
1254         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1255         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1256         if(movenr == -1) return time;    /* last move before new session     */
1257         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1258         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1259         if(!moves) return increment;     /* current session is incremental   */
1260         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1261     } while(movenr >= -1);               /* try again for next session       */
1262
1263     return 0; // no new time quota on this move
1264 }
1265
1266 int
1267 ParseTimeControl (char *tc, float ti, int mps)
1268 {
1269   long tc1;
1270   long tc2;
1271   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1272   int min, sec=0;
1273
1274   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1275   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1276       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1277   if(ti > 0) {
1278
1279     if(mps)
1280       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1281     else 
1282       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1283   } else {
1284     if(mps)
1285       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1286     else 
1287       snprintf(buf, MSG_SIZ, ":%s", mytc);
1288   }
1289   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1290   
1291   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1292     return FALSE;
1293   }
1294
1295   if( *tc == '/' ) {
1296     /* Parse second time control */
1297     tc++;
1298
1299     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1300       return FALSE;
1301     }
1302
1303     if( tc2 == 0 ) {
1304       return FALSE;
1305     }
1306
1307     timeControl_2 = tc2 * 1000;
1308   }
1309   else {
1310     timeControl_2 = 0;
1311   }
1312
1313   if( tc1 == 0 ) {
1314     return FALSE;
1315   }
1316
1317   timeControl = tc1 * 1000;
1318
1319   if (ti >= 0) {
1320     timeIncrement = ti * 1000;  /* convert to ms */
1321     movesPerSession = 0;
1322   } else {
1323     timeIncrement = 0;
1324     movesPerSession = mps;
1325   }
1326   return TRUE;
1327 }
1328
1329 void
1330 InitBackEnd2 ()
1331 {
1332     if (appData.debugMode) {
1333         fprintf(debugFP, "%s\n", programVersion);
1334     }
1335     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1336
1337     set_cont_sequence(appData.wrapContSeq);
1338     if (appData.matchGames > 0) {
1339         appData.matchMode = TRUE;
1340     } else if (appData.matchMode) {
1341         appData.matchGames = 1;
1342     }
1343     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1344         appData.matchGames = appData.sameColorGames;
1345     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1346         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1347         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1348     }
1349     Reset(TRUE, FALSE);
1350     if (appData.noChessProgram || first.protocolVersion == 1) {
1351       InitBackEnd3();
1352     } else {
1353       /* kludge: allow timeout for initial "feature" commands */
1354       FreezeUI();
1355       DisplayMessage("", _("Starting chess program"));
1356       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1357     }
1358 }
1359
1360 int
1361 CalculateIndex (int index, int gameNr)
1362 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1363     int res;
1364     if(index > 0) return index; // fixed nmber
1365     if(index == 0) return 1;
1366     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1367     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1368     return res;
1369 }
1370
1371 int
1372 LoadGameOrPosition (int gameNr)
1373 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1374     if (*appData.loadGameFile != NULLCHAR) {
1375         if (!LoadGameFromFile(appData.loadGameFile,
1376                 CalculateIndex(appData.loadGameIndex, gameNr),
1377                               appData.loadGameFile, FALSE)) {
1378             DisplayFatalError(_("Bad game file"), 0, 1);
1379             return 0;
1380         }
1381     } else if (*appData.loadPositionFile != NULLCHAR) {
1382         if (!LoadPositionFromFile(appData.loadPositionFile,
1383                 CalculateIndex(appData.loadPositionIndex, gameNr),
1384                                   appData.loadPositionFile)) {
1385             DisplayFatalError(_("Bad position file"), 0, 1);
1386             return 0;
1387         }
1388     }
1389     return 1;
1390 }
1391
1392 void
1393 ReserveGame (int gameNr, char resChar)
1394 {
1395     FILE *tf = fopen(appData.tourneyFile, "r+");
1396     char *p, *q, c, buf[MSG_SIZ];
1397     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1398     safeStrCpy(buf, lastMsg, MSG_SIZ);
1399     DisplayMessage(_("Pick new game"), "");
1400     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1401     ParseArgsFromFile(tf);
1402     p = q = appData.results;
1403     if(appData.debugMode) {
1404       char *r = appData.participants;
1405       fprintf(debugFP, "results = '%s'\n", p);
1406       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1407       fprintf(debugFP, "\n");
1408     }
1409     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1410     nextGame = q - p;
1411     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1412     safeStrCpy(q, p, strlen(p) + 2);
1413     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1414     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1415     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1416         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1417         q[nextGame] = '*';
1418     }
1419     fseek(tf, -(strlen(p)+4), SEEK_END);
1420     c = fgetc(tf);
1421     if(c != '"') // depending on DOS or Unix line endings we can be one off
1422          fseek(tf, -(strlen(p)+2), SEEK_END);
1423     else fseek(tf, -(strlen(p)+3), SEEK_END);
1424     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1425     DisplayMessage(buf, "");
1426     free(p); appData.results = q;
1427     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1428        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1429       int round = appData.defaultMatchGames * appData.tourneyType;
1430       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1431          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1432         UnloadEngine(&first);  // next game belongs to other pairing;
1433         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1434     }
1435     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1436 }
1437
1438 void
1439 MatchEvent (int mode)
1440 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1441         int dummy;
1442         if(matchMode) { // already in match mode: switch it off
1443             abortMatch = TRUE;
1444             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1445             return;
1446         }
1447 //      if(gameMode != BeginningOfGame) {
1448 //          DisplayError(_("You can only start a match from the initial position."), 0);
1449 //          return;
1450 //      }
1451         abortMatch = FALSE;
1452         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1453         /* Set up machine vs. machine match */
1454         nextGame = 0;
1455         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1456         if(appData.tourneyFile[0]) {
1457             ReserveGame(-1, 0);
1458             if(nextGame > appData.matchGames) {
1459                 char buf[MSG_SIZ];
1460                 if(strchr(appData.results, '*') == NULL) {
1461                     FILE *f;
1462                     appData.tourneyCycles++;
1463                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1464                         fclose(f);
1465                         NextTourneyGame(-1, &dummy);
1466                         ReserveGame(-1, 0);
1467                         if(nextGame <= appData.matchGames) {
1468                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1469                             matchMode = mode;
1470                             ScheduleDelayedEvent(NextMatchGame, 10000);
1471                             return;
1472                         }
1473                     }
1474                 }
1475                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1476                 DisplayError(buf, 0);
1477                 appData.tourneyFile[0] = 0;
1478                 return;
1479             }
1480         } else
1481         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1482             DisplayFatalError(_("Can't have a match with no chess programs"),
1483                               0, 2);
1484             return;
1485         }
1486         matchMode = mode;
1487         matchGame = roundNr = 1;
1488         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1489         NextMatchGame();
1490 }
1491
1492 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1493
1494 void
1495 InitBackEnd3 P((void))
1496 {
1497     GameMode initialMode;
1498     char buf[MSG_SIZ];
1499     int err, len;
1500
1501     InitChessProgram(&first, startedFromSetupPosition);
1502
1503     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1504         free(programVersion);
1505         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1506         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1507         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1508     }
1509
1510     if (appData.icsActive) {
1511 #ifdef WIN32
1512         /* [DM] Make a console window if needed [HGM] merged ifs */
1513         ConsoleCreate();
1514 #endif
1515         err = establish();
1516         if (err != 0)
1517           {
1518             if (*appData.icsCommPort != NULLCHAR)
1519               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1520                              appData.icsCommPort);
1521             else
1522               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1523                         appData.icsHost, appData.icsPort);
1524
1525             if( (len >= MSG_SIZ) && appData.debugMode )
1526               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1527
1528             DisplayFatalError(buf, err, 1);
1529             return;
1530         }
1531         SetICSMode();
1532         telnetISR =
1533           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1534         fromUserISR =
1535           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1536         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1537             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1538     } else if (appData.noChessProgram) {
1539         SetNCPMode();
1540     } else {
1541         SetGNUMode();
1542     }
1543
1544     if (*appData.cmailGameName != NULLCHAR) {
1545         SetCmailMode();
1546         OpenLoopback(&cmailPR);
1547         cmailISR =
1548           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1549     }
1550
1551     ThawUI();
1552     DisplayMessage("", "");
1553     if (StrCaseCmp(appData.initialMode, "") == 0) {
1554       initialMode = BeginningOfGame;
1555       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1556         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1557         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1558         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1559         ModeHighlight();
1560       }
1561     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1562       initialMode = TwoMachinesPlay;
1563     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1564       initialMode = AnalyzeFile;
1565     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1566       initialMode = AnalyzeMode;
1567     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1568       initialMode = MachinePlaysWhite;
1569     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1570       initialMode = MachinePlaysBlack;
1571     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1572       initialMode = EditGame;
1573     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1574       initialMode = EditPosition;
1575     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1576       initialMode = Training;
1577     } else {
1578       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1579       if( (len >= MSG_SIZ) && appData.debugMode )
1580         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1581
1582       DisplayFatalError(buf, 0, 2);
1583       return;
1584     }
1585
1586     if (appData.matchMode) {
1587         if(appData.tourneyFile[0]) { // start tourney from command line
1588             FILE *f;
1589             if(f = fopen(appData.tourneyFile, "r")) {
1590                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1591                 fclose(f);
1592                 appData.clockMode = TRUE;
1593                 SetGNUMode();
1594             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1595         }
1596         MatchEvent(TRUE);
1597     } else if (*appData.cmailGameName != NULLCHAR) {
1598         /* Set up cmail mode */
1599         ReloadCmailMsgEvent(TRUE);
1600     } else {
1601         /* Set up other modes */
1602         if (initialMode == AnalyzeFile) {
1603           if (*appData.loadGameFile == NULLCHAR) {
1604             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1605             return;
1606           }
1607         }
1608         if (*appData.loadGameFile != NULLCHAR) {
1609             (void) LoadGameFromFile(appData.loadGameFile,
1610                                     appData.loadGameIndex,
1611                                     appData.loadGameFile, TRUE);
1612         } else if (*appData.loadPositionFile != NULLCHAR) {
1613             (void) LoadPositionFromFile(appData.loadPositionFile,
1614                                         appData.loadPositionIndex,
1615                                         appData.loadPositionFile);
1616             /* [HGM] try to make self-starting even after FEN load */
1617             /* to allow automatic setup of fairy variants with wtm */
1618             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1619                 gameMode = BeginningOfGame;
1620                 setboardSpoiledMachineBlack = 1;
1621             }
1622             /* [HGM] loadPos: make that every new game uses the setup */
1623             /* from file as long as we do not switch variant          */
1624             if(!blackPlaysFirst) {
1625                 startedFromPositionFile = TRUE;
1626                 CopyBoard(filePosition, boards[0]);
1627             }
1628         }
1629         if (initialMode == AnalyzeMode) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1636             return;
1637           }
1638           AnalyzeModeEvent();
1639         } else if (initialMode == AnalyzeFile) {
1640           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1641           ShowThinkingEvent();
1642           AnalyzeFileEvent();
1643           AnalysisPeriodicEvent(1);
1644         } else if (initialMode == MachinePlaysWhite) {
1645           if (appData.noChessProgram) {
1646             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1647                               0, 2);
1648             return;
1649           }
1650           if (appData.icsActive) {
1651             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1652                               0, 2);
1653             return;
1654           }
1655           MachineWhiteEvent();
1656         } else if (initialMode == MachinePlaysBlack) {
1657           if (appData.noChessProgram) {
1658             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1659                               0, 2);
1660             return;
1661           }
1662           if (appData.icsActive) {
1663             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1664                               0, 2);
1665             return;
1666           }
1667           MachineBlackEvent();
1668         } else if (initialMode == TwoMachinesPlay) {
1669           if (appData.noChessProgram) {
1670             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1671                               0, 2);
1672             return;
1673           }
1674           if (appData.icsActive) {
1675             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1676                               0, 2);
1677             return;
1678           }
1679           TwoMachinesEvent();
1680         } else if (initialMode == EditGame) {
1681           EditGameEvent();
1682         } else if (initialMode == EditPosition) {
1683           EditPositionEvent();
1684         } else if (initialMode == Training) {
1685           if (*appData.loadGameFile == NULLCHAR) {
1686             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1687             return;
1688           }
1689           TrainingEvent();
1690         }
1691     }
1692 }
1693
1694 void
1695 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1696 {
1697     DisplayBook(current+1);
1698
1699     MoveHistorySet( movelist, first, last, current, pvInfoList );
1700
1701     EvalGraphSet( first, last, current, pvInfoList );
1702
1703     MakeEngineOutputTitle();
1704 }
1705
1706 /*
1707  * Establish will establish a contact to a remote host.port.
1708  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1709  *  used to talk to the host.
1710  * Returns 0 if okay, error code if not.
1711  */
1712 int
1713 establish ()
1714 {
1715     char buf[MSG_SIZ];
1716
1717     if (*appData.icsCommPort != NULLCHAR) {
1718         /* Talk to the host through a serial comm port */
1719         return OpenCommPort(appData.icsCommPort, &icsPR);
1720
1721     } else if (*appData.gateway != NULLCHAR) {
1722         if (*appData.remoteShell == NULLCHAR) {
1723             /* Use the rcmd protocol to run telnet program on a gateway host */
1724             snprintf(buf, sizeof(buf), "%s %s %s",
1725                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1726             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1727
1728         } else {
1729             /* Use the rsh program to run telnet program on a gateway host */
1730             if (*appData.remoteUser == NULLCHAR) {
1731                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1732                         appData.gateway, appData.telnetProgram,
1733                         appData.icsHost, appData.icsPort);
1734             } else {
1735                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1736                         appData.remoteShell, appData.gateway,
1737                         appData.remoteUser, appData.telnetProgram,
1738                         appData.icsHost, appData.icsPort);
1739             }
1740             return StartChildProcess(buf, "", &icsPR);
1741
1742         }
1743     } else if (appData.useTelnet) {
1744         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1745
1746     } else {
1747         /* TCP socket interface differs somewhat between
1748            Unix and NT; handle details in the front end.
1749            */
1750         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1751     }
1752 }
1753
1754 void
1755 EscapeExpand (char *p, char *q)
1756 {       // [HGM] initstring: routine to shape up string arguments
1757         while(*p++ = *q++) if(p[-1] == '\\')
1758             switch(*q++) {
1759                 case 'n': p[-1] = '\n'; break;
1760                 case 'r': p[-1] = '\r'; break;
1761                 case 't': p[-1] = '\t'; break;
1762                 case '\\': p[-1] = '\\'; break;
1763                 case 0: *p = 0; return;
1764                 default: p[-1] = q[-1]; break;
1765             }
1766 }
1767
1768 void
1769 show_bytes (FILE *fp, char *buf, int count)
1770 {
1771     while (count--) {
1772         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1773             fprintf(fp, "\\%03o", *buf & 0xff);
1774         } else {
1775             putc(*buf, fp);
1776         }
1777         buf++;
1778     }
1779     fflush(fp);
1780 }
1781
1782 /* Returns an errno value */
1783 int
1784 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1785 {
1786     char buf[8192], *p, *q, *buflim;
1787     int left, newcount, outcount;
1788
1789     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1790         *appData.gateway != NULLCHAR) {
1791         if (appData.debugMode) {
1792             fprintf(debugFP, ">ICS: ");
1793             show_bytes(debugFP, message, count);
1794             fprintf(debugFP, "\n");
1795         }
1796         return OutputToProcess(pr, message, count, outError);
1797     }
1798
1799     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1800     p = message;
1801     q = buf;
1802     left = count;
1803     newcount = 0;
1804     while (left) {
1805         if (q >= buflim) {
1806             if (appData.debugMode) {
1807                 fprintf(debugFP, ">ICS: ");
1808                 show_bytes(debugFP, buf, newcount);
1809                 fprintf(debugFP, "\n");
1810             }
1811             outcount = OutputToProcess(pr, buf, newcount, outError);
1812             if (outcount < newcount) return -1; /* to be sure */
1813             q = buf;
1814             newcount = 0;
1815         }
1816         if (*p == '\n') {
1817             *q++ = '\r';
1818             newcount++;
1819         } else if (((unsigned char) *p) == TN_IAC) {
1820             *q++ = (char) TN_IAC;
1821             newcount ++;
1822         }
1823         *q++ = *p++;
1824         newcount++;
1825         left--;
1826     }
1827     if (appData.debugMode) {
1828         fprintf(debugFP, ">ICS: ");
1829         show_bytes(debugFP, buf, newcount);
1830         fprintf(debugFP, "\n");
1831     }
1832     outcount = OutputToProcess(pr, buf, newcount, outError);
1833     if (outcount < newcount) return -1; /* to be sure */
1834     return count;
1835 }
1836
1837 void
1838 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1839 {
1840     int outError, outCount;
1841     static int gotEof = 0;
1842
1843     /* Pass data read from player on to ICS */
1844     if (count > 0) {
1845         gotEof = 0;
1846         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1847         if (outCount < count) {
1848             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1849         }
1850     } else if (count < 0) {
1851         RemoveInputSource(isr);
1852         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1853     } else if (gotEof++ > 0) {
1854         RemoveInputSource(isr);
1855         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1856     }
1857 }
1858
1859 void
1860 KeepAlive ()
1861 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1862     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1863     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1864     SendToICS("date\n");
1865     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1866 }
1867
1868 /* added routine for printf style output to ics */
1869 void
1870 ics_printf (char *format, ...)
1871 {
1872     char buffer[MSG_SIZ];
1873     va_list args;
1874
1875     va_start(args, format);
1876     vsnprintf(buffer, sizeof(buffer), format, args);
1877     buffer[sizeof(buffer)-1] = '\0';
1878     SendToICS(buffer);
1879     va_end(args);
1880 }
1881
1882 void
1883 SendToICS (char *s)
1884 {
1885     int count, outCount, outError;
1886
1887     if (icsPR == NoProc) return;
1888
1889     count = strlen(s);
1890     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1891     if (outCount < count) {
1892         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1893     }
1894 }
1895
1896 /* This is used for sending logon scripts to the ICS. Sending
1897    without a delay causes problems when using timestamp on ICC
1898    (at least on my machine). */
1899 void
1900 SendToICSDelayed (char *s, long msdelay)
1901 {
1902     int count, outCount, outError;
1903
1904     if (icsPR == NoProc) return;
1905
1906     count = strlen(s);
1907     if (appData.debugMode) {
1908         fprintf(debugFP, ">ICS: ");
1909         show_bytes(debugFP, s, count);
1910         fprintf(debugFP, "\n");
1911     }
1912     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1913                                       msdelay);
1914     if (outCount < count) {
1915         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916     }
1917 }
1918
1919
1920 /* Remove all highlighting escape sequences in s
1921    Also deletes any suffix starting with '('
1922    */
1923 char *
1924 StripHighlightAndTitle (char *s)
1925 {
1926     static char retbuf[MSG_SIZ];
1927     char *p = retbuf;
1928
1929     while (*s != NULLCHAR) {
1930         while (*s == '\033') {
1931             while (*s != NULLCHAR && !isalpha(*s)) s++;
1932             if (*s != NULLCHAR) s++;
1933         }
1934         while (*s != NULLCHAR && *s != '\033') {
1935             if (*s == '(' || *s == '[') {
1936                 *p = NULLCHAR;
1937                 return retbuf;
1938             }
1939             *p++ = *s++;
1940         }
1941     }
1942     *p = NULLCHAR;
1943     return retbuf;
1944 }
1945
1946 /* Remove all highlighting escape sequences in s */
1947 char *
1948 StripHighlight (char *s)
1949 {
1950     static char retbuf[MSG_SIZ];
1951     char *p = retbuf;
1952
1953     while (*s != NULLCHAR) {
1954         while (*s == '\033') {
1955             while (*s != NULLCHAR && !isalpha(*s)) s++;
1956             if (*s != NULLCHAR) s++;
1957         }
1958         while (*s != NULLCHAR && *s != '\033') {
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 char *variantNames[] = VARIANT_NAMES;
1967 char *
1968 VariantName (VariantClass v)
1969 {
1970     return variantNames[v];
1971 }
1972
1973
1974 /* Identify a variant from the strings the chess servers use or the
1975    PGN Variant tag names we use. */
1976 VariantClass
1977 StringToVariant (char *e)
1978 {
1979     char *p;
1980     int wnum = -1;
1981     VariantClass v = VariantNormal;
1982     int i, found = FALSE;
1983     char buf[MSG_SIZ];
1984     int len;
1985
1986     if (!e) return v;
1987
1988     /* [HGM] skip over optional board-size prefixes */
1989     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1990         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1991         while( *e++ != '_');
1992     }
1993
1994     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1995         v = VariantNormal;
1996         found = TRUE;
1997     } else
1998     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1999       if (StrCaseStr(e, variantNames[i])) {
2000         v = (VariantClass) i;
2001         found = TRUE;
2002         break;
2003       }
2004     }
2005
2006     if (!found) {
2007       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2008           || StrCaseStr(e, "wild/fr")
2009           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2010         v = VariantFischeRandom;
2011       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2012                  (i = 1, p = StrCaseStr(e, "w"))) {
2013         p += i;
2014         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2015         if (isdigit(*p)) {
2016           wnum = atoi(p);
2017         } else {
2018           wnum = -1;
2019         }
2020         switch (wnum) {
2021         case 0: /* FICS only, actually */
2022         case 1:
2023           /* Castling legal even if K starts on d-file */
2024           v = VariantWildCastle;
2025           break;
2026         case 2:
2027         case 3:
2028         case 4:
2029           /* Castling illegal even if K & R happen to start in
2030              normal positions. */
2031           v = VariantNoCastle;
2032           break;
2033         case 5:
2034         case 7:
2035         case 8:
2036         case 10:
2037         case 11:
2038         case 12:
2039         case 13:
2040         case 14:
2041         case 15:
2042         case 18:
2043         case 19:
2044           /* Castling legal iff K & R start in normal positions */
2045           v = VariantNormal;
2046           break;
2047         case 6:
2048         case 20:
2049         case 21:
2050           /* Special wilds for position setup; unclear what to do here */
2051           v = VariantLoadable;
2052           break;
2053         case 9:
2054           /* Bizarre ICC game */
2055           v = VariantTwoKings;
2056           break;
2057         case 16:
2058           v = VariantKriegspiel;
2059           break;
2060         case 17:
2061           v = VariantLosers;
2062           break;
2063         case 22:
2064           v = VariantFischeRandom;
2065           break;
2066         case 23:
2067           v = VariantCrazyhouse;
2068           break;
2069         case 24:
2070           v = VariantBughouse;
2071           break;
2072         case 25:
2073           v = Variant3Check;
2074           break;
2075         case 26:
2076           /* Not quite the same as FICS suicide! */
2077           v = VariantGiveaway;
2078           break;
2079         case 27:
2080           v = VariantAtomic;
2081           break;
2082         case 28:
2083           v = VariantShatranj;
2084           break;
2085
2086         /* Temporary names for future ICC types.  The name *will* change in
2087            the next xboard/WinBoard release after ICC defines it. */
2088         case 29:
2089           v = Variant29;
2090           break;
2091         case 30:
2092           v = Variant30;
2093           break;
2094         case 31:
2095           v = Variant31;
2096           break;
2097         case 32:
2098           v = Variant32;
2099           break;
2100         case 33:
2101           v = Variant33;
2102           break;
2103         case 34:
2104           v = Variant34;
2105           break;
2106         case 35:
2107           v = Variant35;
2108           break;
2109         case 36:
2110           v = Variant36;
2111           break;
2112         case 37:
2113           v = VariantShogi;
2114           break;
2115         case 38:
2116           v = VariantXiangqi;
2117           break;
2118         case 39:
2119           v = VariantCourier;
2120           break;
2121         case 40:
2122           v = VariantGothic;
2123           break;
2124         case 41:
2125           v = VariantCapablanca;
2126           break;
2127         case 42:
2128           v = VariantKnightmate;
2129           break;
2130         case 43:
2131           v = VariantFairy;
2132           break;
2133         case 44:
2134           v = VariantCylinder;
2135           break;
2136         case 45:
2137           v = VariantFalcon;
2138           break;
2139         case 46:
2140           v = VariantCapaRandom;
2141           break;
2142         case 47:
2143           v = VariantBerolina;
2144           break;
2145         case 48:
2146           v = VariantJanus;
2147           break;
2148         case 49:
2149           v = VariantSuper;
2150           break;
2151         case 50:
2152           v = VariantGreat;
2153           break;
2154         case -1:
2155           /* Found "wild" or "w" in the string but no number;
2156              must assume it's normal chess. */
2157           v = VariantNormal;
2158           break;
2159         default:
2160           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2161           if( (len >= MSG_SIZ) && appData.debugMode )
2162             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2163
2164           DisplayError(buf, 0);
2165           v = VariantUnknown;
2166           break;
2167         }
2168       }
2169     }
2170     if (appData.debugMode) {
2171       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2172               e, wnum, VariantName(v));
2173     }
2174     return v;
2175 }
2176
2177 static int leftover_start = 0, leftover_len = 0;
2178 char star_match[STAR_MATCH_N][MSG_SIZ];
2179
2180 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2181    advance *index beyond it, and set leftover_start to the new value of
2182    *index; else return FALSE.  If pattern contains the character '*', it
2183    matches any sequence of characters not containing '\r', '\n', or the
2184    character following the '*' (if any), and the matched sequence(s) are
2185    copied into star_match.
2186    */
2187 int
2188 looking_at ( char *buf, int *index, char *pattern)
2189 {
2190     char *bufp = &buf[*index], *patternp = pattern;
2191     int star_count = 0;
2192     char *matchp = star_match[0];
2193
2194     for (;;) {
2195         if (*patternp == NULLCHAR) {
2196             *index = leftover_start = bufp - buf;
2197             *matchp = NULLCHAR;
2198             return TRUE;
2199         }
2200         if (*bufp == NULLCHAR) return FALSE;
2201         if (*patternp == '*') {
2202             if (*bufp == *(patternp + 1)) {
2203                 *matchp = NULLCHAR;
2204                 matchp = star_match[++star_count];
2205                 patternp += 2;
2206                 bufp++;
2207                 continue;
2208             } else if (*bufp == '\n' || *bufp == '\r') {
2209                 patternp++;
2210                 if (*patternp == NULLCHAR)
2211                   continue;
2212                 else
2213                   return FALSE;
2214             } else {
2215                 *matchp++ = *bufp++;
2216                 continue;
2217             }
2218         }
2219         if (*patternp != *bufp) return FALSE;
2220         patternp++;
2221         bufp++;
2222     }
2223 }
2224
2225 void
2226 SendToPlayer (char *data, int length)
2227 {
2228     int error, outCount;
2229     outCount = OutputToProcess(NoProc, data, length, &error);
2230     if (outCount < length) {
2231         DisplayFatalError(_("Error writing to display"), error, 1);
2232     }
2233 }
2234
2235 void
2236 PackHolding (char packed[], char *holding)
2237 {
2238     char *p = holding;
2239     char *q = packed;
2240     int runlength = 0;
2241     int curr = 9999;
2242     do {
2243         if (*p == curr) {
2244             runlength++;
2245         } else {
2246             switch (runlength) {
2247               case 0:
2248                 break;
2249               case 1:
2250                 *q++ = curr;
2251                 break;
2252               case 2:
2253                 *q++ = curr;
2254                 *q++ = curr;
2255                 break;
2256               default:
2257                 sprintf(q, "%d", runlength);
2258                 while (*q) q++;
2259                 *q++ = curr;
2260                 break;
2261             }
2262             runlength = 1;
2263             curr = *p;
2264         }
2265     } while (*p++);
2266     *q = NULLCHAR;
2267 }
2268
2269 /* Telnet protocol requests from the front end */
2270 void
2271 TelnetRequest (unsigned char ddww, unsigned char option)
2272 {
2273     unsigned char msg[3];
2274     int outCount, outError;
2275
2276     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2277
2278     if (appData.debugMode) {
2279         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2280         switch (ddww) {
2281           case TN_DO:
2282             ddwwStr = "DO";
2283             break;
2284           case TN_DONT:
2285             ddwwStr = "DONT";
2286             break;
2287           case TN_WILL:
2288             ddwwStr = "WILL";
2289             break;
2290           case TN_WONT:
2291             ddwwStr = "WONT";
2292             break;
2293           default:
2294             ddwwStr = buf1;
2295             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2296             break;
2297         }
2298         switch (option) {
2299           case TN_ECHO:
2300             optionStr = "ECHO";
2301             break;
2302           default:
2303             optionStr = buf2;
2304             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2305             break;
2306         }
2307         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2308     }
2309     msg[0] = TN_IAC;
2310     msg[1] = ddww;
2311     msg[2] = option;
2312     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2313     if (outCount < 3) {
2314         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2315     }
2316 }
2317
2318 void
2319 DoEcho ()
2320 {
2321     if (!appData.icsActive) return;
2322     TelnetRequest(TN_DO, TN_ECHO);
2323 }
2324
2325 void
2326 DontEcho ()
2327 {
2328     if (!appData.icsActive) return;
2329     TelnetRequest(TN_DONT, TN_ECHO);
2330 }
2331
2332 void
2333 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2334 {
2335     /* put the holdings sent to us by the server on the board holdings area */
2336     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2337     char p;
2338     ChessSquare piece;
2339
2340     if(gameInfo.holdingsWidth < 2)  return;
2341     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2342         return; // prevent overwriting by pre-board holdings
2343
2344     if( (int)lowestPiece >= BlackPawn ) {
2345         holdingsColumn = 0;
2346         countsColumn = 1;
2347         holdingsStartRow = BOARD_HEIGHT-1;
2348         direction = -1;
2349     } else {
2350         holdingsColumn = BOARD_WIDTH-1;
2351         countsColumn = BOARD_WIDTH-2;
2352         holdingsStartRow = 0;
2353         direction = 1;
2354     }
2355
2356     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2357         board[i][holdingsColumn] = EmptySquare;
2358         board[i][countsColumn]   = (ChessSquare) 0;
2359     }
2360     while( (p=*holdings++) != NULLCHAR ) {
2361         piece = CharToPiece( ToUpper(p) );
2362         if(piece == EmptySquare) continue;
2363         /*j = (int) piece - (int) WhitePawn;*/
2364         j = PieceToNumber(piece);
2365         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2366         if(j < 0) continue;               /* should not happen */
2367         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2368         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2369         board[holdingsStartRow+j*direction][countsColumn]++;
2370     }
2371 }
2372
2373
2374 void
2375 VariantSwitch (Board board, VariantClass newVariant)
2376 {
2377    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2378    static Board oldBoard;
2379
2380    startedFromPositionFile = FALSE;
2381    if(gameInfo.variant == newVariant) return;
2382
2383    /* [HGM] This routine is called each time an assignment is made to
2384     * gameInfo.variant during a game, to make sure the board sizes
2385     * are set to match the new variant. If that means adding or deleting
2386     * holdings, we shift the playing board accordingly
2387     * This kludge is needed because in ICS observe mode, we get boards
2388     * of an ongoing game without knowing the variant, and learn about the
2389     * latter only later. This can be because of the move list we requested,
2390     * in which case the game history is refilled from the beginning anyway,
2391     * but also when receiving holdings of a crazyhouse game. In the latter
2392     * case we want to add those holdings to the already received position.
2393     */
2394
2395
2396    if (appData.debugMode) {
2397      fprintf(debugFP, "Switch board from %s to %s\n",
2398              VariantName(gameInfo.variant), VariantName(newVariant));
2399      setbuf(debugFP, NULL);
2400    }
2401    shuffleOpenings = 0;       /* [HGM] shuffle */
2402    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2403    switch(newVariant)
2404      {
2405      case VariantShogi:
2406        newWidth = 9;  newHeight = 9;
2407        gameInfo.holdingsSize = 7;
2408      case VariantBughouse:
2409      case VariantCrazyhouse:
2410        newHoldingsWidth = 2; break;
2411      case VariantGreat:
2412        newWidth = 10;
2413      case VariantSuper:
2414        newHoldingsWidth = 2;
2415        gameInfo.holdingsSize = 8;
2416        break;
2417      case VariantGothic:
2418      case VariantCapablanca:
2419      case VariantCapaRandom:
2420        newWidth = 10;
2421      default:
2422        newHoldingsWidth = gameInfo.holdingsSize = 0;
2423      };
2424
2425    if(newWidth  != gameInfo.boardWidth  ||
2426       newHeight != gameInfo.boardHeight ||
2427       newHoldingsWidth != gameInfo.holdingsWidth ) {
2428
2429      /* shift position to new playing area, if needed */
2430      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2431        for(i=0; i<BOARD_HEIGHT; i++)
2432          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2433            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2434              board[i][j];
2435        for(i=0; i<newHeight; i++) {
2436          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2437          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2438        }
2439      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2440        for(i=0; i<BOARD_HEIGHT; i++)
2441          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2442            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2443              board[i][j];
2444      }
2445      gameInfo.boardWidth  = newWidth;
2446      gameInfo.boardHeight = newHeight;
2447      gameInfo.holdingsWidth = newHoldingsWidth;
2448      gameInfo.variant = newVariant;
2449      InitDrawingSizes(-2, 0);
2450    } else gameInfo.variant = newVariant;
2451    CopyBoard(oldBoard, board);   // remember correctly formatted board
2452      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2453    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2454 }
2455
2456 static int loggedOn = FALSE;
2457
2458 /*-- Game start info cache: --*/
2459 int gs_gamenum;
2460 char gs_kind[MSG_SIZ];
2461 static char player1Name[128] = "";
2462 static char player2Name[128] = "";
2463 static char cont_seq[] = "\n\\   ";
2464 static int player1Rating = -1;
2465 static int player2Rating = -1;
2466 /*----------------------------*/
2467
2468 ColorClass curColor = ColorNormal;
2469 int suppressKibitz = 0;
2470
2471 // [HGM] seekgraph
2472 Boolean soughtPending = FALSE;
2473 Boolean seekGraphUp;
2474 #define MAX_SEEK_ADS 200
2475 #define SQUARE 0x80
2476 char *seekAdList[MAX_SEEK_ADS];
2477 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2478 float tcList[MAX_SEEK_ADS];
2479 char colorList[MAX_SEEK_ADS];
2480 int nrOfSeekAds = 0;
2481 int minRating = 1010, maxRating = 2800;
2482 int hMargin = 10, vMargin = 20, h, w;
2483 extern int squareSize, lineGap;
2484
2485 void
2486 PlotSeekAd (int i)
2487 {
2488         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2489         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2490         if(r < minRating+100 && r >=0 ) r = minRating+100;
2491         if(r > maxRating) r = maxRating;
2492         if(tc < 1.) tc = 1.;
2493         if(tc > 95.) tc = 95.;
2494         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2495         y = ((double)r - minRating)/(maxRating - minRating)
2496             * (h-vMargin-squareSize/8-1) + vMargin;
2497         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2498         if(strstr(seekAdList[i], " u ")) color = 1;
2499         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2500            !strstr(seekAdList[i], "bullet") &&
2501            !strstr(seekAdList[i], "blitz") &&
2502            !strstr(seekAdList[i], "standard") ) color = 2;
2503         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2504         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2505 }
2506
2507 void
2508 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2509 {
2510         char buf[MSG_SIZ], *ext = "";
2511         VariantClass v = StringToVariant(type);
2512         if(strstr(type, "wild")) {
2513             ext = type + 4; // append wild number
2514             if(v == VariantFischeRandom) type = "chess960"; else
2515             if(v == VariantLoadable) type = "setup"; else
2516             type = VariantName(v);
2517         }
2518         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2519         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2520             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2521             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2522             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2523             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2524             seekNrList[nrOfSeekAds] = nr;
2525             zList[nrOfSeekAds] = 0;
2526             seekAdList[nrOfSeekAds++] = StrSave(buf);
2527             if(plot) PlotSeekAd(nrOfSeekAds-1);
2528         }
2529 }
2530
2531 void
2532 EraseSeekDot (int i)
2533 {
2534     int x = xList[i], y = yList[i], d=squareSize/4, k;
2535     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2536     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2537     // now replot every dot that overlapped
2538     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2539         int xx = xList[k], yy = yList[k];
2540         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2541             DrawSeekDot(xx, yy, colorList[k]);
2542     }
2543 }
2544
2545 void
2546 RemoveSeekAd (int nr)
2547 {
2548         int i;
2549         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2550             EraseSeekDot(i);
2551             if(seekAdList[i]) free(seekAdList[i]);
2552             seekAdList[i] = seekAdList[--nrOfSeekAds];
2553             seekNrList[i] = seekNrList[nrOfSeekAds];
2554             ratingList[i] = ratingList[nrOfSeekAds];
2555             colorList[i]  = colorList[nrOfSeekAds];
2556             tcList[i] = tcList[nrOfSeekAds];
2557             xList[i]  = xList[nrOfSeekAds];
2558             yList[i]  = yList[nrOfSeekAds];
2559             zList[i]  = zList[nrOfSeekAds];
2560             seekAdList[nrOfSeekAds] = NULL;
2561             break;
2562         }
2563 }
2564
2565 Boolean
2566 MatchSoughtLine (char *line)
2567 {
2568     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2569     int nr, base, inc, u=0; char dummy;
2570
2571     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2573        (u=1) &&
2574        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2575         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2576         // match: compact and save the line
2577         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2578         return TRUE;
2579     }
2580     return FALSE;
2581 }
2582
2583 int
2584 DrawSeekGraph ()
2585 {
2586     int i;
2587     if(!seekGraphUp) return FALSE;
2588     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2589     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2590
2591     DrawSeekBackground(0, 0, w, h);
2592     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2593     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2594     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2595         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2596         yy = h-1-yy;
2597         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2598         if(i%500 == 0) {
2599             char buf[MSG_SIZ];
2600             snprintf(buf, MSG_SIZ, "%d", i);
2601             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2602         }
2603     }
2604     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2605     for(i=1; i<100; i+=(i<10?1:5)) {
2606         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2607         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2608         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2609             char buf[MSG_SIZ];
2610             snprintf(buf, MSG_SIZ, "%d", i);
2611             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2612         }
2613     }
2614     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2615     return TRUE;
2616 }
2617
2618 int
2619 SeekGraphClick (ClickType click, int x, int y, int moving)
2620 {
2621     static int lastDown = 0, displayed = 0, lastSecond;
2622     if(y < 0) return FALSE;
2623     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2624         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2625         if(!seekGraphUp) return FALSE;
2626         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2627         DrawPosition(TRUE, NULL);
2628         return TRUE;
2629     }
2630     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2631         if(click == Release || moving) return FALSE;
2632         nrOfSeekAds = 0;
2633         soughtPending = TRUE;
2634         SendToICS(ics_prefix);
2635         SendToICS("sought\n"); // should this be "sought all"?
2636     } else { // issue challenge based on clicked ad
2637         int dist = 10000; int i, closest = 0, second = 0;
2638         for(i=0; i<nrOfSeekAds; i++) {
2639             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2640             if(d < dist) { dist = d; closest = i; }
2641             second += (d - zList[i] < 120); // count in-range ads
2642             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2643         }
2644         if(dist < 120) {
2645             char buf[MSG_SIZ];
2646             second = (second > 1);
2647             if(displayed != closest || second != lastSecond) {
2648                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2649                 lastSecond = second; displayed = closest;
2650             }
2651             if(click == Press) {
2652                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2653                 lastDown = closest;
2654                 return TRUE;
2655             } // on press 'hit', only show info
2656             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2657             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2658             SendToICS(ics_prefix);
2659             SendToICS(buf);
2660             return TRUE; // let incoming board of started game pop down the graph
2661         } else if(click == Release) { // release 'miss' is ignored
2662             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2663             if(moving == 2) { // right up-click
2664                 nrOfSeekAds = 0; // refresh graph
2665                 soughtPending = TRUE;
2666                 SendToICS(ics_prefix);
2667                 SendToICS("sought\n"); // should this be "sought all"?
2668             }
2669             return TRUE;
2670         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2671         // press miss or release hit 'pop down' seek graph
2672         seekGraphUp = FALSE;
2673         DrawPosition(TRUE, NULL);
2674     }
2675     return TRUE;
2676 }
2677
2678 void
2679 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2680 {
2681 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2682 #define STARTED_NONE 0
2683 #define STARTED_MOVES 1
2684 #define STARTED_BOARD 2
2685 #define STARTED_OBSERVE 3
2686 #define STARTED_HOLDINGS 4
2687 #define STARTED_CHATTER 5
2688 #define STARTED_COMMENT 6
2689 #define STARTED_MOVES_NOHIDE 7
2690
2691     static int started = STARTED_NONE;
2692     static char parse[20000];
2693     static int parse_pos = 0;
2694     static char buf[BUF_SIZE + 1];
2695     static int firstTime = TRUE, intfSet = FALSE;
2696     static ColorClass prevColor = ColorNormal;
2697     static int savingComment = FALSE;
2698     static int cmatch = 0; // continuation sequence match
2699     char *bp;
2700     char str[MSG_SIZ];
2701     int i, oldi;
2702     int buf_len;
2703     int next_out;
2704     int tkind;
2705     int backup;    /* [DM] For zippy color lines */
2706     char *p;
2707     char talker[MSG_SIZ]; // [HGM] chat
2708     int channel;
2709
2710     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2711
2712     if (appData.debugMode) {
2713       if (!error) {
2714         fprintf(debugFP, "<ICS: ");
2715         show_bytes(debugFP, data, count);
2716         fprintf(debugFP, "\n");
2717       }
2718     }
2719
2720     if (appData.debugMode) { int f = forwardMostMove;
2721         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2722                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2723                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2724     }
2725     if (count > 0) {
2726         /* If last read ended with a partial line that we couldn't parse,
2727            prepend it to the new read and try again. */
2728         if (leftover_len > 0) {
2729             for (i=0; i<leftover_len; i++)
2730               buf[i] = buf[leftover_start + i];
2731         }
2732
2733     /* copy new characters into the buffer */
2734     bp = buf + leftover_len;
2735     buf_len=leftover_len;
2736     for (i=0; i<count; i++)
2737     {
2738         // ignore these
2739         if (data[i] == '\r')
2740             continue;
2741
2742         // join lines split by ICS?
2743         if (!appData.noJoin)
2744         {
2745             /*
2746                 Joining just consists of finding matches against the
2747                 continuation sequence, and discarding that sequence
2748                 if found instead of copying it.  So, until a match
2749                 fails, there's nothing to do since it might be the
2750                 complete sequence, and thus, something we don't want
2751                 copied.
2752             */
2753             if (data[i] == cont_seq[cmatch])
2754             {
2755                 cmatch++;
2756                 if (cmatch == strlen(cont_seq))
2757                 {
2758                     cmatch = 0; // complete match.  just reset the counter
2759
2760                     /*
2761                         it's possible for the ICS to not include the space
2762                         at the end of the last word, making our [correct]
2763                         join operation fuse two separate words.  the server
2764                         does this when the space occurs at the width setting.
2765                     */
2766                     if (!buf_len || buf[buf_len-1] != ' ')
2767                     {
2768                         *bp++ = ' ';
2769                         buf_len++;
2770                     }
2771                 }
2772                 continue;
2773             }
2774             else if (cmatch)
2775             {
2776                 /*
2777                     match failed, so we have to copy what matched before
2778                     falling through and copying this character.  In reality,
2779                     this will only ever be just the newline character, but
2780                     it doesn't hurt to be precise.
2781                 */
2782                 strncpy(bp, cont_seq, cmatch);
2783                 bp += cmatch;
2784                 buf_len += cmatch;
2785                 cmatch = 0;
2786             }
2787         }
2788
2789         // copy this char
2790         *bp++ = data[i];
2791         buf_len++;
2792     }
2793
2794         buf[buf_len] = NULLCHAR;
2795 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2796         next_out = 0;
2797         leftover_start = 0;
2798
2799         i = 0;
2800         while (i < buf_len) {
2801             /* Deal with part of the TELNET option negotiation
2802                protocol.  We refuse to do anything beyond the
2803                defaults, except that we allow the WILL ECHO option,
2804                which ICS uses to turn off password echoing when we are
2805                directly connected to it.  We reject this option
2806                if localLineEditing mode is on (always on in xboard)
2807                and we are talking to port 23, which might be a real
2808                telnet server that will try to keep WILL ECHO on permanently.
2809              */
2810             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2811                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2812                 unsigned char option;
2813                 oldi = i;
2814                 switch ((unsigned char) buf[++i]) {
2815                   case TN_WILL:
2816                     if (appData.debugMode)
2817                       fprintf(debugFP, "\n<WILL ");
2818                     switch (option = (unsigned char) buf[++i]) {
2819                       case TN_ECHO:
2820                         if (appData.debugMode)
2821                           fprintf(debugFP, "ECHO ");
2822                         /* Reply only if this is a change, according
2823                            to the protocol rules. */
2824                         if (remoteEchoOption) break;
2825                         if (appData.localLineEditing &&
2826                             atoi(appData.icsPort) == TN_PORT) {
2827                             TelnetRequest(TN_DONT, TN_ECHO);
2828                         } else {
2829                             EchoOff();
2830                             TelnetRequest(TN_DO, TN_ECHO);
2831                             remoteEchoOption = TRUE;
2832                         }
2833                         break;
2834                       default:
2835                         if (appData.debugMode)
2836                           fprintf(debugFP, "%d ", option);
2837                         /* Whatever this is, we don't want it. */
2838                         TelnetRequest(TN_DONT, option);
2839                         break;
2840                     }
2841                     break;
2842                   case TN_WONT:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WONT ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (!remoteEchoOption) break;
2852                         EchoOn();
2853                         TelnetRequest(TN_DONT, TN_ECHO);
2854                         remoteEchoOption = FALSE;
2855                         break;
2856                       default:
2857                         if (appData.debugMode)
2858                           fprintf(debugFP, "%d ", (unsigned char) option);
2859                         /* Whatever this is, it must already be turned
2860                            off, because we never agree to turn on
2861                            anything non-default, so according to the
2862                            protocol rules, we don't reply. */
2863                         break;
2864                     }
2865                     break;
2866                   case TN_DO:
2867                     if (appData.debugMode)
2868                       fprintf(debugFP, "\n<DO ");
2869                     switch (option = (unsigned char) buf[++i]) {
2870                       default:
2871                         /* Whatever this is, we refuse to do it. */
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "%d ", option);
2874                         TelnetRequest(TN_WONT, option);
2875                         break;
2876                     }
2877                     break;
2878                   case TN_DONT:
2879                     if (appData.debugMode)
2880                       fprintf(debugFP, "\n<DONT ");
2881                     switch (option = (unsigned char) buf[++i]) {
2882                       default:
2883                         if (appData.debugMode)
2884                           fprintf(debugFP, "%d ", option);
2885                         /* Whatever this is, we are already not doing
2886                            it, because we never agree to do anything
2887                            non-default, so according to the protocol
2888                            rules, we don't reply. */
2889                         break;
2890                     }
2891                     break;
2892                   case TN_IAC:
2893                     if (appData.debugMode)
2894                       fprintf(debugFP, "\n<IAC ");
2895                     /* Doubled IAC; pass it through */
2896                     i--;
2897                     break;
2898                   default:
2899                     if (appData.debugMode)
2900                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2901                     /* Drop all other telnet commands on the floor */
2902                     break;
2903                 }
2904                 if (oldi > next_out)
2905                   SendToPlayer(&buf[next_out], oldi - next_out);
2906                 if (++i > next_out)
2907                   next_out = i;
2908                 continue;
2909             }
2910
2911             /* OK, this at least will *usually* work */
2912             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2913                 loggedOn = TRUE;
2914             }
2915
2916             if (loggedOn && !intfSet) {
2917                 if (ics_type == ICS_ICC) {
2918                   snprintf(str, MSG_SIZ,
2919                           "/set-quietly interface %s\n/set-quietly style 12\n",
2920                           programVersion);
2921                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2923                 } else if (ics_type == ICS_CHESSNET) {
2924                   snprintf(str, MSG_SIZ, "/style 12\n");
2925                 } else {
2926                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2927                   strcat(str, programVersion);
2928                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2929                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2930                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2931 #ifdef WIN32
2932                   strcat(str, "$iset nohighlight 1\n");
2933 #endif
2934                   strcat(str, "$iset lock 1\n$style 12\n");
2935                 }
2936                 SendToICS(str);
2937                 NotifyFrontendLogin();
2938                 intfSet = TRUE;
2939             }
2940
2941             if (started == STARTED_COMMENT) {
2942                 /* Accumulate characters in comment */
2943                 parse[parse_pos++] = buf[i];
2944                 if (buf[i] == '\n') {
2945                     parse[parse_pos] = NULLCHAR;
2946                     if(chattingPartner>=0) {
2947                         char mess[MSG_SIZ];
2948                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2949                         OutputChatMessage(chattingPartner, mess);
2950                         chattingPartner = -1;
2951                         next_out = i+1; // [HGM] suppress printing in ICS window
2952                     } else
2953                     if(!suppressKibitz) // [HGM] kibitz
2954                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2955                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2956                         int nrDigit = 0, nrAlph = 0, j;
2957                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2958                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2959                         parse[parse_pos] = NULLCHAR;
2960                         // try to be smart: if it does not look like search info, it should go to
2961                         // ICS interaction window after all, not to engine-output window.
2962                         for(j=0; j<parse_pos; j++) { // count letters and digits
2963                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2964                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2965                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2966                         }
2967                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2968                             int depth=0; float score;
2969                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2970                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2971                                 pvInfoList[forwardMostMove-1].depth = depth;
2972                                 pvInfoList[forwardMostMove-1].score = 100*score;
2973                             }
2974                             OutputKibitz(suppressKibitz, parse);
2975                         } else {
2976                             char tmp[MSG_SIZ];
2977                             if(gameMode == IcsObserving) // restore original ICS messages
2978                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2979                             else
2980                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2981                             SendToPlayer(tmp, strlen(tmp));
2982                         }
2983                         next_out = i+1; // [HGM] suppress printing in ICS window
2984                     }
2985                     started = STARTED_NONE;
2986                 } else {
2987                     /* Don't match patterns against characters in comment */
2988                     i++;
2989                     continue;
2990                 }
2991             }
2992             if (started == STARTED_CHATTER) {
2993                 if (buf[i] != '\n') {
2994                     /* Don't match patterns against characters in chatter */
2995                     i++;
2996                     continue;
2997                 }
2998                 started = STARTED_NONE;
2999                 if(suppressKibitz) next_out = i+1;
3000             }
3001
3002             /* Kludge to deal with rcmd protocol */
3003             if (firstTime && looking_at(buf, &i, "\001*")) {
3004                 DisplayFatalError(&buf[1], 0, 1);
3005                 continue;
3006             } else {
3007                 firstTime = FALSE;
3008             }
3009
3010             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3011                 ics_type = ICS_ICC;
3012                 ics_prefix = "/";
3013                 if (appData.debugMode)
3014                   fprintf(debugFP, "ics_type %d\n", ics_type);
3015                 continue;
3016             }
3017             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3018                 ics_type = ICS_FICS;
3019                 ics_prefix = "$";
3020                 if (appData.debugMode)
3021                   fprintf(debugFP, "ics_type %d\n", ics_type);
3022                 continue;
3023             }
3024             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3025                 ics_type = ICS_CHESSNET;
3026                 ics_prefix = "/";
3027                 if (appData.debugMode)
3028                   fprintf(debugFP, "ics_type %d\n", ics_type);
3029                 continue;
3030             }
3031
3032             if (!loggedOn &&
3033                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3034                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3035                  looking_at(buf, &i, "will be \"*\""))) {
3036               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3037               continue;
3038             }
3039
3040             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3041               char buf[MSG_SIZ];
3042               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3043               DisplayIcsInteractionTitle(buf);
3044               have_set_title = TRUE;
3045             }
3046
3047             /* skip finger notes */
3048             if (started == STARTED_NONE &&
3049                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3050                  (buf[i] == '1' && buf[i+1] == '0')) &&
3051                 buf[i+2] == ':' && buf[i+3] == ' ') {
3052               started = STARTED_CHATTER;
3053               i += 3;
3054               continue;
3055             }
3056
3057             oldi = i;
3058             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3059             if(appData.seekGraph) {
3060                 if(soughtPending && MatchSoughtLine(buf+i)) {
3061                     i = strstr(buf+i, "rated") - buf;
3062                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                     next_out = leftover_start = i;
3064                     started = STARTED_CHATTER;
3065                     suppressKibitz = TRUE;
3066                     continue;
3067                 }
3068                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3069                         && looking_at(buf, &i, "* ads displayed")) {
3070                     soughtPending = FALSE;
3071                     seekGraphUp = TRUE;
3072                     DrawSeekGraph();
3073                     continue;
3074                 }
3075                 if(appData.autoRefresh) {
3076                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3077                         int s = (ics_type == ICS_ICC); // ICC format differs
3078                         if(seekGraphUp)
3079                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3080                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3081                         looking_at(buf, &i, "*% "); // eat prompt
3082                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i; // suppress
3085                         continue;
3086                     }
3087                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3088                         char *p = star_match[0];
3089                         while(*p) {
3090                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3091                             while(*p && *p++ != ' '); // next
3092                         }
3093                         looking_at(buf, &i, "*% "); // eat prompt
3094                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3095                         next_out = i;
3096                         continue;
3097                     }
3098                 }
3099             }
3100
3101             /* skip formula vars */
3102             if (started == STARTED_NONE &&
3103                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3104               started = STARTED_CHATTER;
3105               i += 3;
3106               continue;
3107             }
3108
3109             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3110             if (appData.autoKibitz && started == STARTED_NONE &&
3111                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3112                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3113                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3114                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3115                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3116                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3117                         suppressKibitz = TRUE;
3118                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                         next_out = i;
3120                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3121                                 && (gameMode == IcsPlayingWhite)) ||
3122                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3123                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3124                             started = STARTED_CHATTER; // own kibitz we simply discard
3125                         else {
3126                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3127                             parse_pos = 0; parse[0] = NULLCHAR;
3128                             savingComment = TRUE;
3129                             suppressKibitz = gameMode != IcsObserving ? 2 :
3130                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3131                         }
3132                         continue;
3133                 } else
3134                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3135                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3136                          && atoi(star_match[0])) {
3137                     // suppress the acknowledgements of our own autoKibitz
3138                     char *p;
3139                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3141                     SendToPlayer(star_match[0], strlen(star_match[0]));
3142                     if(looking_at(buf, &i, "*% ")) // eat prompt
3143                         suppressKibitz = FALSE;
3144                     next_out = i;
3145                     continue;
3146                 }
3147             } // [HGM] kibitz: end of patch
3148
3149             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3150
3151             // [HGM] chat: intercept tells by users for which we have an open chat window
3152             channel = -1;
3153             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3154                                            looking_at(buf, &i, "* whispers:") ||
3155                                            looking_at(buf, &i, "* kibitzes:") ||
3156                                            looking_at(buf, &i, "* shouts:") ||
3157                                            looking_at(buf, &i, "* c-shouts:") ||
3158                                            looking_at(buf, &i, "--> * ") ||
3159                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3160                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3161                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3162                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3163                 int p;
3164                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3165                 chattingPartner = -1;
3166
3167                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3168                 for(p=0; p<MAX_CHAT; p++) {
3169                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3170                     talker[0] = '['; strcat(talker, "] ");
3171                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3172                     chattingPartner = p; break;
3173                     }
3174                 } else
3175                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3176                 for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("kibitzes", chatPartner[p])) {
3178                         talker[0] = '['; strcat(talker, "] ");
3179                         chattingPartner = p; break;
3180                     }
3181                 } else
3182                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3183                 for(p=0; p<MAX_CHAT; p++) {
3184                     if(!strcmp("whispers", chatPartner[p])) {
3185                         talker[0] = '['; strcat(talker, "] ");
3186                         chattingPartner = p; break;
3187                     }
3188                 } else
3189                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3190                   if(buf[i-8] == '-' && buf[i-3] == 't')
3191                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3192                     if(!strcmp("c-shouts", chatPartner[p])) {
3193                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3194                         chattingPartner = p; break;
3195                     }
3196                   }
3197                   if(chattingPartner < 0)
3198                   for(p=0; p<MAX_CHAT; p++) {
3199                     if(!strcmp("shouts", chatPartner[p])) {
3200                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3201                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3202                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3203                         chattingPartner = p; break;
3204                     }
3205                   }
3206                 }
3207                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3208                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3209                     talker[0] = 0; Colorize(ColorTell, FALSE);
3210                     chattingPartner = p; break;
3211                 }
3212                 if(chattingPartner<0) i = oldi; else {
3213                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3214                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3215                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3216                     started = STARTED_COMMENT;
3217                     parse_pos = 0; parse[0] = NULLCHAR;
3218                     savingComment = 3 + chattingPartner; // counts as TRUE
3219                     suppressKibitz = TRUE;
3220                     continue;
3221                 }
3222             } // [HGM] chat: end of patch
3223
3224           backup = i;
3225             if (appData.zippyTalk || appData.zippyPlay) {
3226                 /* [DM] Backup address for color zippy lines */
3227 #if ZIPPY
3228                if (loggedOn == TRUE)
3229                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3230                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3231 #endif
3232             } // [DM] 'else { ' deleted
3233                 if (
3234                     /* Regular tells and says */
3235                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3236                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3237                     looking_at(buf, &i, "* says: ") ||
3238                     /* Don't color "message" or "messages" output */
3239                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3240                     looking_at(buf, &i, "*. * at *:*: ") ||
3241                     looking_at(buf, &i, "--* (*:*): ") ||
3242                     /* Message notifications (same color as tells) */
3243                     looking_at(buf, &i, "* has left a message ") ||
3244                     looking_at(buf, &i, "* just sent you a message:\n") ||
3245                     /* Whispers and kibitzes */
3246                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3247                     looking_at(buf, &i, "* kibitzes: ") ||
3248                     /* Channel tells */
3249                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3250
3251                   if (tkind == 1 && strchr(star_match[0], ':')) {
3252                       /* Avoid "tells you:" spoofs in channels */
3253                      tkind = 3;
3254                   }
3255                   if (star_match[0][0] == NULLCHAR ||
3256                       strchr(star_match[0], ' ') ||
3257                       (tkind == 3 && strchr(star_match[1], ' '))) {
3258                     /* Reject bogus matches */
3259                     i = oldi;
3260                   } else {
3261                     if (appData.colorize) {
3262                       if (oldi > next_out) {
3263                         SendToPlayer(&buf[next_out], oldi - next_out);
3264                         next_out = oldi;
3265                       }
3266                       switch (tkind) {
3267                       case 1:
3268                         Colorize(ColorTell, FALSE);
3269                         curColor = ColorTell;
3270                         break;
3271                       case 2:
3272                         Colorize(ColorKibitz, FALSE);
3273                         curColor = ColorKibitz;
3274                         break;
3275                       case 3:
3276                         p = strrchr(star_match[1], '(');
3277                         if (p == NULL) {
3278                           p = star_match[1];
3279                         } else {
3280                           p++;
3281                         }
3282                         if (atoi(p) == 1) {
3283                           Colorize(ColorChannel1, FALSE);
3284                           curColor = ColorChannel1;
3285                         } else {
3286                           Colorize(ColorChannel, FALSE);
3287                           curColor = ColorChannel;
3288                         }
3289                         break;
3290                       case 5:
3291                         curColor = ColorNormal;
3292                         break;
3293                       }
3294                     }
3295                     if (started == STARTED_NONE && appData.autoComment &&
3296                         (gameMode == IcsObserving ||
3297                          gameMode == IcsPlayingWhite ||
3298                          gameMode == IcsPlayingBlack)) {
3299                       parse_pos = i - oldi;
3300                       memcpy(parse, &buf[oldi], parse_pos);
3301                       parse[parse_pos] = NULLCHAR;
3302                       started = STARTED_COMMENT;
3303                       savingComment = TRUE;
3304                     } else {
3305                       started = STARTED_CHATTER;
3306                       savingComment = FALSE;
3307                     }
3308                     loggedOn = TRUE;
3309                     continue;
3310                   }
3311                 }
3312
3313                 if (looking_at(buf, &i, "* s-shouts: ") ||
3314                     looking_at(buf, &i, "* c-shouts: ")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorSShout, FALSE);
3321                         curColor = ColorSShout;
3322                     }
3323                     loggedOn = TRUE;
3324                     started = STARTED_CHATTER;
3325                     continue;
3326                 }
3327
3328                 if (looking_at(buf, &i, "--->")) {
3329                     loggedOn = TRUE;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "* shouts: ") ||
3334                     looking_at(buf, &i, "--> ")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorShout, FALSE);
3341                         curColor = ColorShout;
3342                     }
3343                     loggedOn = TRUE;
3344                     started = STARTED_CHATTER;
3345                     continue;
3346                 }
3347
3348                 if (looking_at( buf, &i, "Challenge:")) {
3349                     if (appData.colorize) {
3350                         if (oldi > next_out) {
3351                             SendToPlayer(&buf[next_out], oldi - next_out);
3352                             next_out = oldi;
3353                         }
3354                         Colorize(ColorChallenge, FALSE);
3355                         curColor = ColorChallenge;
3356                     }
3357                     loggedOn = TRUE;
3358                     continue;
3359                 }
3360
3361                 if (looking_at(buf, &i, "* offers you") ||
3362                     looking_at(buf, &i, "* offers to be") ||
3363                     looking_at(buf, &i, "* would like to") ||
3364                     looking_at(buf, &i, "* requests to") ||
3365                     looking_at(buf, &i, "Your opponent offers") ||
3366                     looking_at(buf, &i, "Your opponent requests")) {
3367
3368                     if (appData.colorize) {
3369                         if (oldi > next_out) {
3370                             SendToPlayer(&buf[next_out], oldi - next_out);
3371                             next_out = oldi;
3372                         }
3373                         Colorize(ColorRequest, FALSE);
3374                         curColor = ColorRequest;
3375                     }
3376                     continue;
3377                 }
3378
3379                 if (looking_at(buf, &i, "* (*) seeking")) {
3380                     if (appData.colorize) {
3381                         if (oldi > next_out) {
3382                             SendToPlayer(&buf[next_out], oldi - next_out);
3383                             next_out = oldi;
3384                         }
3385                         Colorize(ColorSeek, FALSE);
3386                         curColor = ColorSeek;
3387                     }
3388                     continue;
3389             }
3390
3391           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3392
3393             if (looking_at(buf, &i, "\\   ")) {
3394                 if (prevColor != ColorNormal) {
3395                     if (oldi > next_out) {
3396                         SendToPlayer(&buf[next_out], oldi - next_out);
3397                         next_out = oldi;
3398                     }
3399                     Colorize(prevColor, TRUE);
3400                     curColor = prevColor;
3401                 }
3402                 if (savingComment) {
3403                     parse_pos = i - oldi;
3404                     memcpy(parse, &buf[oldi], parse_pos);
3405                     parse[parse_pos] = NULLCHAR;
3406                     started = STARTED_COMMENT;
3407                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3408                         chattingPartner = savingComment - 3; // kludge to remember the box
3409                 } else {
3410                     started = STARTED_CHATTER;
3411                 }
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "Black Strength :") ||
3416                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3417                 looking_at(buf, &i, "<10>") ||
3418                 looking_at(buf, &i, "#@#")) {
3419                 /* Wrong board style */
3420                 loggedOn = TRUE;
3421                 SendToICS(ics_prefix);
3422                 SendToICS("set style 12\n");
3423                 SendToICS(ics_prefix);
3424                 SendToICS("refresh\n");
3425                 continue;
3426             }
3427
3428             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3429                 ICSInitScript();
3430                 have_sent_ICS_logon = 1;
3431                 continue;
3432             }
3433
3434             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3435                 (looking_at(buf, &i, "\n<12> ") ||
3436                  looking_at(buf, &i, "<12> "))) {
3437                 loggedOn = TRUE;
3438                 if (oldi > next_out) {
3439                     SendToPlayer(&buf[next_out], oldi - next_out);
3440                 }
3441                 next_out = i;
3442                 started = STARTED_BOARD;
3443                 parse_pos = 0;
3444                 continue;
3445             }
3446
3447             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3448                 looking_at(buf, &i, "<b1> ")) {
3449                 if (oldi > next_out) {
3450                     SendToPlayer(&buf[next_out], oldi - next_out);
3451                 }
3452                 next_out = i;
3453                 started = STARTED_HOLDINGS;
3454                 parse_pos = 0;
3455                 continue;
3456             }
3457
3458             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3459                 loggedOn = TRUE;
3460                 /* Header for a move list -- first line */
3461
3462                 switch (ics_getting_history) {
3463                   case H_FALSE:
3464                     switch (gameMode) {
3465                       case IcsIdle:
3466                       case BeginningOfGame:
3467                         /* User typed "moves" or "oldmoves" while we
3468                            were idle.  Pretend we asked for these
3469                            moves and soak them up so user can step
3470                            through them and/or save them.
3471                            */
3472                         Reset(FALSE, TRUE);
3473                         gameMode = IcsObserving;
3474                         ModeHighlight();
3475                         ics_gamenum = -1;
3476                         ics_getting_history = H_GOT_UNREQ_HEADER;
3477                         break;
3478                       case EditGame: /*?*/
3479                       case EditPosition: /*?*/
3480                         /* Should above feature work in these modes too? */
3481                         /* For now it doesn't */
3482                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3483                         break;
3484                       default:
3485                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3486                         break;
3487                     }
3488                     break;
3489                   case H_REQUESTED:
3490                     /* Is this the right one? */
3491                     if (gameInfo.white && gameInfo.black &&
3492                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3493                         strcmp(gameInfo.black, star_match[2]) == 0) {
3494                         /* All is well */
3495                         ics_getting_history = H_GOT_REQ_HEADER;
3496                     }
3497                     break;
3498                   case H_GOT_REQ_HEADER:
3499                   case H_GOT_UNREQ_HEADER:
3500                   case H_GOT_UNWANTED_HEADER:
3501                   case H_GETTING_MOVES:
3502                     /* Should not happen */
3503                     DisplayError(_("Error gathering move list: two headers"), 0);
3504                     ics_getting_history = H_FALSE;
3505                     break;
3506                 }
3507
3508                 /* Save player ratings into gameInfo if needed */
3509                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3510                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3511                     (gameInfo.whiteRating == -1 ||
3512                      gameInfo.blackRating == -1)) {
3513
3514                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3515                     gameInfo.blackRating = string_to_rating(star_match[3]);
3516                     if (appData.debugMode)
3517                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3518                               gameInfo.whiteRating, gameInfo.blackRating);
3519                 }
3520                 continue;
3521             }
3522
3523             if (looking_at(buf, &i,
3524               "* * match, initial time: * minute*, increment: * second")) {
3525                 /* Header for a move list -- second line */
3526                 /* Initial board will follow if this is a wild game */
3527                 if (gameInfo.event != NULL) free(gameInfo.event);
3528                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3529                 gameInfo.event = StrSave(str);
3530                 /* [HGM] we switched variant. Translate boards if needed. */
3531                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i, "Move  ")) {
3536                 /* Beginning of a move list */
3537                 switch (ics_getting_history) {
3538                   case H_FALSE:
3539                     /* Normally should not happen */
3540                     /* Maybe user hit reset while we were parsing */
3541                     break;
3542                   case H_REQUESTED:
3543                     /* Happens if we are ignoring a move list that is not
3544                      * the one we just requested.  Common if the user
3545                      * tries to observe two games without turning off
3546                      * getMoveList */
3547                     break;
3548                   case H_GETTING_MOVES:
3549                     /* Should not happen */
3550                     DisplayError(_("Error gathering move list: nested"), 0);
3551                     ics_getting_history = H_FALSE;
3552                     break;
3553                   case H_GOT_REQ_HEADER:
3554                     ics_getting_history = H_GETTING_MOVES;
3555                     started = STARTED_MOVES;
3556                     parse_pos = 0;
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                     }
3560                     break;
3561                   case H_GOT_UNREQ_HEADER:
3562                     ics_getting_history = H_GETTING_MOVES;
3563                     started = STARTED_MOVES_NOHIDE;
3564                     parse_pos = 0;
3565                     break;
3566                   case H_GOT_UNWANTED_HEADER:
3567                     ics_getting_history = H_FALSE;
3568                     break;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "% ") ||
3574                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3575                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3576                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3577                     soughtPending = FALSE;
3578                     seekGraphUp = TRUE;
3579                     DrawSeekGraph();
3580                 }
3581                 if(suppressKibitz) next_out = i;
3582                 savingComment = FALSE;
3583                 suppressKibitz = 0;
3584                 switch (started) {
3585                   case STARTED_MOVES:
3586                   case STARTED_MOVES_NOHIDE:
3587                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3588                     parse[parse_pos + i - oldi] = NULLCHAR;
3589                     ParseGameHistory(parse);
3590 #if ZIPPY
3591                     if (appData.zippyPlay && first.initDone) {
3592                         FeedMovesToProgram(&first, forwardMostMove);
3593                         if (gameMode == IcsPlayingWhite) {
3594                             if (WhiteOnMove(forwardMostMove)) {
3595                                 if (first.sendTime) {
3596                                   if (first.useColors) {
3597                                     SendToProgram("black\n", &first);
3598                                   }
3599                                   SendTimeRemaining(&first, TRUE);
3600                                 }
3601                                 if (first.useColors) {
3602                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3603                                 }
3604                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3605                                 first.maybeThinking = TRUE;
3606                             } else {
3607                                 if (first.usePlayother) {
3608                                   if (first.sendTime) {
3609                                     SendTimeRemaining(&first, TRUE);
3610                                   }
3611                                   SendToProgram("playother\n", &first);
3612                                   firstMove = FALSE;
3613                                 } else {
3614                                   firstMove = TRUE;
3615                                 }
3616                             }
3617                         } else if (gameMode == IcsPlayingBlack) {
3618                             if (!WhiteOnMove(forwardMostMove)) {
3619                                 if (first.sendTime) {
3620                                   if (first.useColors) {
3621                                     SendToProgram("white\n", &first);
3622                                   }
3623                                   SendTimeRemaining(&first, FALSE);
3624                                 }
3625                                 if (first.useColors) {
3626                                   SendToProgram("black\n", &first);
3627                                 }
3628                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3629                                 first.maybeThinking = TRUE;
3630                             } else {
3631                                 if (first.usePlayother) {
3632                                   if (first.sendTime) {
3633                                     SendTimeRemaining(&first, FALSE);
3634                                   }
3635                                   SendToProgram("playother\n", &first);
3636                                   firstMove = FALSE;
3637                                 } else {
3638                                   firstMove = TRUE;
3639                                 }
3640                             }
3641                         }
3642                     }
3643 #endif
3644                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3645                         /* Moves came from oldmoves or moves command
3646                            while we weren't doing anything else.
3647                            */
3648                         currentMove = forwardMostMove;
3649                         ClearHighlights();/*!!could figure this out*/
3650                         flipView = appData.flipView;
3651                         DrawPosition(TRUE, boards[currentMove]);
3652                         DisplayBothClocks();
3653                         snprintf(str, MSG_SIZ, "%s %s %s",
3654                                 gameInfo.white, _("vs."),  gameInfo.black);
3655                         DisplayTitle(str);
3656                         gameMode = IcsIdle;
3657                     } else {
3658                         /* Moves were history of an active game */
3659                         if (gameInfo.resultDetails != NULL) {
3660                             free(gameInfo.resultDetails);
3661                             gameInfo.resultDetails = NULL;
3662                         }
3663                     }
3664                     HistorySet(parseList, backwardMostMove,
3665                                forwardMostMove, currentMove-1);
3666                     DisplayMove(currentMove - 1);
3667                     if (started == STARTED_MOVES) next_out = i;
3668                     started = STARTED_NONE;
3669                     ics_getting_history = H_FALSE;
3670                     break;
3671
3672                   case STARTED_OBSERVE:
3673                     started = STARTED_NONE;
3674                     SendToICS(ics_prefix);
3675                     SendToICS("refresh\n");
3676                     break;
3677
3678                   default:
3679                     break;
3680                 }
3681                 if(bookHit) { // [HGM] book: simulate book reply
3682                     static char bookMove[MSG_SIZ]; // a bit generous?
3683
3684                     programStats.nodes = programStats.depth = programStats.time =
3685                     programStats.score = programStats.got_only_move = 0;
3686                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3687
3688                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3689                     strcat(bookMove, bookHit);
3690                     HandleMachineMove(bookMove, &first);
3691                 }
3692                 continue;
3693             }
3694
3695             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3696                  started == STARTED_HOLDINGS ||
3697                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3698                 /* Accumulate characters in move list or board */
3699                 parse[parse_pos++] = buf[i];
3700             }
3701
3702             /* Start of game messages.  Mostly we detect start of game
3703                when the first board image arrives.  On some versions
3704                of the ICS, though, we need to do a "refresh" after starting
3705                to observe in order to get the current board right away. */
3706             if (looking_at(buf, &i, "Adding game * to observation list")) {
3707                 started = STARTED_OBSERVE;
3708                 continue;
3709             }
3710
3711             /* Handle auto-observe */
3712             if (appData.autoObserve &&
3713                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3714                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3715                 char *player;
3716                 /* Choose the player that was highlighted, if any. */
3717                 if (star_match[0][0] == '\033' ||
3718                     star_match[1][0] != '\033') {
3719                     player = star_match[0];
3720                 } else {
3721                     player = star_match[2];
3722                 }
3723                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3724                         ics_prefix, StripHighlightAndTitle(player));
3725                 SendToICS(str);
3726
3727                 /* Save ratings from notify string */
3728                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3729                 player1Rating = string_to_rating(star_match[1]);
3730                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3731                 player2Rating = string_to_rating(star_match[3]);
3732
3733                 if (appData.debugMode)
3734                   fprintf(debugFP,
3735                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3736                           player1Name, player1Rating,
3737                           player2Name, player2Rating);
3738
3739                 continue;
3740             }
3741
3742             /* Deal with automatic examine mode after a game,
3743                and with IcsObserving -> IcsExamining transition */
3744             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3745                 looking_at(buf, &i, "has made you an examiner of game *")) {
3746
3747                 int gamenum = atoi(star_match[0]);
3748                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3749                     gamenum == ics_gamenum) {
3750                     /* We were already playing or observing this game;
3751                        no need to refetch history */
3752                     gameMode = IcsExamining;
3753                     if (pausing) {
3754                         pauseExamForwardMostMove = forwardMostMove;
3755                     } else if (currentMove < forwardMostMove) {
3756                         ForwardInner(forwardMostMove);
3757                     }
3758                 } else {
3759                     /* I don't think this case really can happen */
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                 }
3763                 continue;
3764             }
3765
3766             /* Error messages */
3767 //          if (ics_user_moved) {
3768             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3769                 if (looking_at(buf, &i, "Illegal move") ||
3770                     looking_at(buf, &i, "Not a legal move") ||
3771                     looking_at(buf, &i, "Your king is in check") ||
3772                     looking_at(buf, &i, "It isn't your turn") ||
3773                     looking_at(buf, &i, "It is not your move")) {
3774                     /* Illegal move */
3775                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3776                         currentMove = forwardMostMove-1;
3777                         DisplayMove(currentMove - 1); /* before DMError */
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         SwitchClocks(forwardMostMove-1); // [HGM] race
3780                         DisplayBothClocks();
3781                     }
3782                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3783                     ics_user_moved = 0;
3784                     continue;
3785                 }
3786             }
3787
3788             if (looking_at(buf, &i, "still have time") ||
3789                 looking_at(buf, &i, "not out of time") ||
3790                 looking_at(buf, &i, "either player is out of time") ||
3791                 looking_at(buf, &i, "has timeseal; checking")) {
3792                 /* We must have called his flag a little too soon */
3793                 whiteFlag = blackFlag = FALSE;
3794                 continue;
3795             }
3796
3797             if (looking_at(buf, &i, "added * seconds to") ||
3798                 looking_at(buf, &i, "seconds were added to")) {
3799                 /* Update the clocks */
3800                 SendToICS(ics_prefix);
3801                 SendToICS("refresh\n");
3802                 continue;
3803             }
3804
3805             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3806                 ics_clock_paused = TRUE;
3807                 StopClocks();
3808                 continue;
3809             }
3810
3811             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3812                 ics_clock_paused = FALSE;
3813                 StartClocks();
3814                 continue;
3815             }
3816
3817             /* Grab player ratings from the Creating: message.
3818                Note we have to check for the special case when
3819                the ICS inserts things like [white] or [black]. */
3820             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3821                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3822                 /* star_matches:
3823                    0    player 1 name (not necessarily white)
3824                    1    player 1 rating
3825                    2    empty, white, or black (IGNORED)
3826                    3    player 2 name (not necessarily black)
3827                    4    player 2 rating
3828
3829                    The names/ratings are sorted out when the game
3830                    actually starts (below).
3831                 */
3832                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3833                 player1Rating = string_to_rating(star_match[1]);
3834                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3835                 player2Rating = string_to_rating(star_match[4]);
3836
3837                 if (appData.debugMode)
3838                   fprintf(debugFP,
3839                           "Ratings from 'Creating:' %s %d, %s %d\n",
3840                           player1Name, player1Rating,
3841                           player2Name, player2Rating);
3842
3843                 continue;
3844             }
3845
3846             /* Improved generic start/end-of-game messages */
3847             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3848                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3849                 /* If tkind == 0: */
3850                 /* star_match[0] is the game number */
3851                 /*           [1] is the white player's name */
3852                 /*           [2] is the black player's name */
3853                 /* For end-of-game: */
3854                 /*           [3] is the reason for the game end */
3855                 /*           [4] is a PGN end game-token, preceded by " " */
3856                 /* For start-of-game: */
3857                 /*           [3] begins with "Creating" or "Continuing" */
3858                 /*           [4] is " *" or empty (don't care). */
3859                 int gamenum = atoi(star_match[0]);
3860                 char *whitename, *blackname, *why, *endtoken;
3861                 ChessMove endtype = EndOfFile;
3862
3863                 if (tkind == 0) {
3864                   whitename = star_match[1];
3865                   blackname = star_match[2];
3866                   why = star_match[3];
3867                   endtoken = star_match[4];
3868                 } else {
3869                   whitename = star_match[1];
3870                   blackname = star_match[3];
3871                   why = star_match[5];
3872                   endtoken = star_match[6];
3873                 }
3874
3875                 /* Game start messages */
3876                 if (strncmp(why, "Creating ", 9) == 0 ||
3877                     strncmp(why, "Continuing ", 11) == 0) {
3878                     gs_gamenum = gamenum;
3879                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3880                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3881                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3882 #if ZIPPY
3883                     if (appData.zippyPlay) {
3884                         ZippyGameStart(whitename, blackname);
3885                     }
3886 #endif /*ZIPPY*/
3887                     partnerBoardValid = FALSE; // [HGM] bughouse
3888                     continue;
3889                 }
3890
3891                 /* Game end messages */
3892                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3893                     ics_gamenum != gamenum) {
3894                     continue;
3895                 }
3896                 while (endtoken[0] == ' ') endtoken++;
3897                 switch (endtoken[0]) {
3898                   case '*':
3899                   default:
3900                     endtype = GameUnfinished;
3901                     break;
3902                   case '0':
3903                     endtype = BlackWins;
3904                     break;
3905                   case '1':
3906                     if (endtoken[1] == '/')
3907                       endtype = GameIsDrawn;
3908                     else
3909                       endtype = WhiteWins;
3910                     break;
3911                 }
3912                 GameEnds(endtype, why, GE_ICS);
3913 #if ZIPPY
3914                 if (appData.zippyPlay && first.initDone) {
3915                     ZippyGameEnd(endtype, why);
3916                     if (first.pr == NoProc) {
3917                       /* Start the next process early so that we'll
3918                          be ready for the next challenge */
3919                       StartChessProgram(&first);
3920                     }
3921                     /* Send "new" early, in case this command takes
3922                        a long time to finish, so that we'll be ready
3923                        for the next challenge. */
3924                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3925                     Reset(TRUE, TRUE);
3926                 }
3927 #endif /*ZIPPY*/
3928                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3929                 continue;
3930             }
3931
3932             if (looking_at(buf, &i, "Removing game * from observation") ||
3933                 looking_at(buf, &i, "no longer observing game *") ||
3934                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3935                 if (gameMode == IcsObserving &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       /* icsEngineAnalyze */
3939                       if (appData.icsEngineAnalyze) {
3940                             ExitAnalyzeMode();
3941                             ModeHighlight();
3942                       }
3943                       StopClocks();
3944                       gameMode = IcsIdle;
3945                       ics_gamenum = -1;
3946                       ics_user_moved = FALSE;
3947                   }
3948                 continue;
3949             }
3950
3951             if (looking_at(buf, &i, "no longer examining game *")) {
3952                 if (gameMode == IcsExamining &&
3953                     atoi(star_match[0]) == ics_gamenum)
3954                   {
3955                       gameMode = IcsIdle;
3956                       ics_gamenum = -1;
3957                       ics_user_moved = FALSE;
3958                   }
3959                 continue;
3960             }
3961
3962             /* Advance leftover_start past any newlines we find,
3963                so only partial lines can get reparsed */
3964             if (looking_at(buf, &i, "\n")) {
3965                 prevColor = curColor;
3966                 if (curColor != ColorNormal) {
3967                     if (oldi > next_out) {
3968                         SendToPlayer(&buf[next_out], oldi - next_out);
3969                         next_out = oldi;
3970                     }
3971                     Colorize(ColorNormal, FALSE);
3972                     curColor = ColorNormal;
3973                 }
3974                 if (started == STARTED_BOARD) {
3975                     started = STARTED_NONE;
3976                     parse[parse_pos] = NULLCHAR;
3977                     ParseBoard12(parse);
3978                     ics_user_moved = 0;
3979
3980                     /* Send premove here */
3981                     if (appData.premove) {
3982                       char str[MSG_SIZ];
3983                       if (currentMove == 0 &&
3984                           gameMode == IcsPlayingWhite &&
3985                           appData.premoveWhite) {
3986                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3987                         if (appData.debugMode)
3988                           fprintf(debugFP, "Sending premove:\n");
3989                         SendToICS(str);
3990                       } else if (currentMove == 1 &&
3991                                  gameMode == IcsPlayingBlack &&
3992                                  appData.premoveBlack) {
3993                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3994                         if (appData.debugMode)
3995                           fprintf(debugFP, "Sending premove:\n");
3996                         SendToICS(str);
3997                       } else if (gotPremove) {
3998                         gotPremove = 0;
3999                         ClearPremoveHighlights();
4000                         if (appData.debugMode)
4001                           fprintf(debugFP, "Sending premove:\n");
4002                           UserMoveEvent(premoveFromX, premoveFromY,
4003                                         premoveToX, premoveToY,
4004                                         premovePromoChar);
4005                       }
4006                     }
4007
4008                     /* Usually suppress following prompt */
4009                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4010                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4011                         if (looking_at(buf, &i, "*% ")) {
4012                             savingComment = FALSE;
4013                             suppressKibitz = 0;
4014                         }
4015                     }
4016                     next_out = i;
4017                 } else if (started == STARTED_HOLDINGS) {
4018                     int gamenum;
4019                     char new_piece[MSG_SIZ];
4020                     started = STARTED_NONE;
4021                     parse[parse_pos] = NULLCHAR;
4022                     if (appData.debugMode)
4023                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4024                                                         parse, currentMove);
4025                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4026                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4027                         if (gameInfo.variant == VariantNormal) {
4028                           /* [HGM] We seem to switch variant during a game!
4029                            * Presumably no holdings were displayed, so we have
4030                            * to move the position two files to the right to
4031                            * create room for them!
4032                            */
4033                           VariantClass newVariant;
4034                           switch(gameInfo.boardWidth) { // base guess on board width
4035                                 case 9:  newVariant = VariantShogi; break;
4036                                 case 10: newVariant = VariantGreat; break;
4037                                 default: newVariant = VariantCrazyhouse; break;
4038                           }
4039                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4040                           /* Get a move list just to see the header, which
4041                              will tell us whether this is really bug or zh */
4042                           if (ics_getting_history == H_FALSE) {
4043                             ics_getting_history = H_REQUESTED;
4044                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4045                             SendToICS(str);
4046                           }
4047                         }
4048                         new_piece[0] = NULLCHAR;
4049                         sscanf(parse, "game %d white [%s black [%s <- %s",
4050                                &gamenum, white_holding, black_holding,
4051                                new_piece);
4052                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4053                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4054                         /* [HGM] copy holdings to board holdings area */
4055                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4056                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4057                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4058 #if ZIPPY
4059                         if (appData.zippyPlay && first.initDone) {
4060                             ZippyHoldings(white_holding, black_holding,
4061                                           new_piece);
4062                         }
4063 #endif /*ZIPPY*/
4064                         if (tinyLayout || smallLayout) {
4065                             char wh[16], bh[16];
4066                             PackHolding(wh, white_holding);
4067                             PackHolding(bh, black_holding);
4068                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4069                                     gameInfo.white, gameInfo.black);
4070                         } else {
4071                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4072                                     gameInfo.white, white_holding, _("vs."),
4073                                     gameInfo.black, black_holding);
4074                         }
4075                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4076                         DrawPosition(FALSE, boards[currentMove]);
4077                         DisplayTitle(str);
4078                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4079                         sscanf(parse, "game %d white [%s black [%s <- %s",
4080                                &gamenum, white_holding, black_holding,
4081                                new_piece);
4082                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4083                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4084                         /* [HGM] copy holdings to partner-board holdings area */
4085                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4086                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4087                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4088                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4089                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4090                       }
4091                     }
4092                     /* Suppress following prompt */
4093                     if (looking_at(buf, &i, "*% ")) {
4094                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4095                         savingComment = FALSE;
4096                         suppressKibitz = 0;
4097                     }
4098                     next_out = i;
4099                 }
4100                 continue;
4101             }
4102
4103             i++;                /* skip unparsed character and loop back */
4104         }
4105
4106         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4107 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4108 //          SendToPlayer(&buf[next_out], i - next_out);
4109             started != STARTED_HOLDINGS && leftover_start > next_out) {
4110             SendToPlayer(&buf[next_out], leftover_start - next_out);
4111             next_out = i;
4112         }
4113
4114         leftover_len = buf_len - leftover_start;
4115         /* if buffer ends with something we couldn't parse,
4116            reparse it after appending the next read */
4117
4118     } else if (count == 0) {
4119         RemoveInputSource(isr);
4120         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4121     } else {
4122         DisplayFatalError(_("Error reading from ICS"), error, 1);
4123     }
4124 }
4125
4126
4127 /* Board style 12 looks like this:
4128
4129    <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
4130
4131  * The "<12> " is stripped before it gets to this routine.  The two
4132  * trailing 0's (flip state and clock ticking) are later addition, and
4133  * some chess servers may not have them, or may have only the first.
4134  * Additional trailing fields may be added in the future.
4135  */
4136
4137 #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"
4138
4139 #define RELATION_OBSERVING_PLAYED    0
4140 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4141 #define RELATION_PLAYING_MYMOVE      1
4142 #define RELATION_PLAYING_NOTMYMOVE  -1
4143 #define RELATION_EXAMINING           2
4144 #define RELATION_ISOLATED_BOARD     -3
4145 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4146
4147 void
4148 ParseBoard12 (char *string)
4149 {
4150     GameMode newGameMode;
4151     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4152     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4153     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4154     char to_play, board_chars[200];
4155     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4156     char black[32], white[32];
4157     Board board;
4158     int prevMove = currentMove;
4159     int ticking = 2;
4160     ChessMove moveType;
4161     int fromX, fromY, toX, toY;
4162     char promoChar;
4163     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4164     char *bookHit = NULL; // [HGM] book
4165     Boolean weird = FALSE, reqFlag = FALSE;
4166
4167     fromX = fromY = toX = toY = -1;
4168
4169     newGame = FALSE;
4170
4171     if (appData.debugMode)
4172       fprintf(debugFP, _("Parsing board: %s\n"), string);
4173
4174     move_str[0] = NULLCHAR;
4175     elapsed_time[0] = NULLCHAR;
4176     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4177         int  i = 0, j;
4178         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4179             if(string[i] == ' ') { ranks++; files = 0; }
4180             else files++;
4181             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4182             i++;
4183         }
4184         for(j = 0; j <i; j++) board_chars[j] = string[j];
4185         board_chars[i] = '\0';
4186         string += i + 1;
4187     }
4188     n = sscanf(string, PATTERN, &to_play, &double_push,
4189                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4190                &gamenum, white, black, &relation, &basetime, &increment,
4191                &white_stren, &black_stren, &white_time, &black_time,
4192                &moveNum, str, elapsed_time, move_str, &ics_flip,
4193                &ticking);
4194
4195     if (n < 21) {
4196         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4197         DisplayError(str, 0);
4198         return;
4199     }
4200
4201     /* Convert the move number to internal form */
4202     moveNum = (moveNum - 1) * 2;
4203     if (to_play == 'B') moveNum++;
4204     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4205       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4206                         0, 1);
4207       return;
4208     }
4209
4210     switch (relation) {
4211       case RELATION_OBSERVING_PLAYED:
4212       case RELATION_OBSERVING_STATIC:
4213         if (gamenum == -1) {
4214             /* Old ICC buglet */
4215             relation = RELATION_OBSERVING_STATIC;
4216         }
4217         newGameMode = IcsObserving;
4218         break;
4219       case RELATION_PLAYING_MYMOVE:
4220       case RELATION_PLAYING_NOTMYMOVE:
4221         newGameMode =
4222           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4223             IcsPlayingWhite : IcsPlayingBlack;
4224         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4225         break;
4226       case RELATION_EXAMINING:
4227         newGameMode = IcsExamining;
4228         break;
4229       case RELATION_ISOLATED_BOARD:
4230       default:
4231         /* Just display this board.  If user was doing something else,
4232            we will forget about it until the next board comes. */
4233         newGameMode = IcsIdle;
4234         break;
4235       case RELATION_STARTING_POSITION:
4236         newGameMode = gameMode;
4237         break;
4238     }
4239
4240     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4241          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4242       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4243       char *toSqr;
4244       for (k = 0; k < ranks; k++) {
4245         for (j = 0; j < files; j++)
4246           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4247         if(gameInfo.holdingsWidth > 1) {
4248              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4249              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4250         }
4251       }
4252       CopyBoard(partnerBoard, board);
4253       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4254         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4255         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4256       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4257       if(toSqr = strchr(str, '-')) {
4258         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4259         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4260       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4261       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4262       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4263       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4264       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4265       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4266                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4267       DisplayMessage(partnerStatus, "");
4268         partnerBoardValid = TRUE;
4269       return;
4270     }
4271
4272     /* Modify behavior for initial board display on move listing
4273        of wild games.
4274        */
4275     switch (ics_getting_history) {
4276       case H_FALSE:
4277       case H_REQUESTED:
4278         break;
4279       case H_GOT_REQ_HEADER:
4280       case H_GOT_UNREQ_HEADER:
4281         /* This is the initial position of the current game */
4282         gamenum = ics_gamenum;
4283         moveNum = 0;            /* old ICS bug workaround */
4284         if (to_play == 'B') {
4285           startedFromSetupPosition = TRUE;
4286           blackPlaysFirst = TRUE;
4287           moveNum = 1;
4288           if (forwardMostMove == 0) forwardMostMove = 1;
4289           if (backwardMostMove == 0) backwardMostMove = 1;
4290           if (currentMove == 0) currentMove = 1;
4291         }
4292         newGameMode = gameMode;
4293         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4294         break;
4295       case H_GOT_UNWANTED_HEADER:
4296         /* This is an initial board that we don't want */
4297         return;
4298       case H_GETTING_MOVES:
4299         /* Should not happen */
4300         DisplayError(_("Error gathering move list: extra board"), 0);
4301         ics_getting_history = H_FALSE;
4302         return;
4303     }
4304
4305    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4306                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4307      /* [HGM] We seem to have switched variant unexpectedly
4308       * Try to guess new variant from board size
4309       */
4310           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4311           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4312           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4313           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4314           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4315           if(!weird) newVariant = VariantNormal;
4316           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4317           /* Get a move list just to see the header, which
4318              will tell us whether this is really bug or zh */
4319           if (ics_getting_history == H_FALSE) {
4320             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4321             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4322             SendToICS(str);
4323           }
4324     }
4325
4326     /* Take action if this is the first board of a new game, or of a
4327        different game than is currently being displayed.  */
4328     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4329         relation == RELATION_ISOLATED_BOARD) {
4330
4331         /* Forget the old game and get the history (if any) of the new one */
4332         if (gameMode != BeginningOfGame) {
4333           Reset(TRUE, TRUE);
4334         }
4335         newGame = TRUE;
4336         if (appData.autoRaiseBoard) BoardToTop();
4337         prevMove = -3;
4338         if (gamenum == -1) {
4339             newGameMode = IcsIdle;
4340         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4341                    appData.getMoveList && !reqFlag) {
4342             /* Need to get game history */
4343             ics_getting_history = H_REQUESTED;
4344             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4345             SendToICS(str);
4346         }
4347
4348         /* Initially flip the board to have black on the bottom if playing
4349            black or if the ICS flip flag is set, but let the user change
4350            it with the Flip View button. */
4351         flipView = appData.autoFlipView ?
4352           (newGameMode == IcsPlayingBlack) || ics_flip :
4353           appData.flipView;
4354
4355         /* Done with values from previous mode; copy in new ones */
4356         gameMode = newGameMode;
4357         ModeHighlight();
4358         ics_gamenum = gamenum;
4359         if (gamenum == gs_gamenum) {
4360             int klen = strlen(gs_kind);
4361             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4362             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4363             gameInfo.event = StrSave(str);
4364         } else {
4365             gameInfo.event = StrSave("ICS game");
4366         }
4367         gameInfo.site = StrSave(appData.icsHost);
4368         gameInfo.date = PGNDate();
4369         gameInfo.round = StrSave("-");
4370         gameInfo.white = StrSave(white);
4371         gameInfo.black = StrSave(black);
4372         timeControl = basetime * 60 * 1000;
4373         timeControl_2 = 0;
4374         timeIncrement = increment * 1000;
4375         movesPerSession = 0;
4376         gameInfo.timeControl = TimeControlTagValue();
4377         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4378   if (appData.debugMode) {
4379     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4380     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4381     setbuf(debugFP, NULL);
4382   }
4383
4384         gameInfo.outOfBook = NULL;
4385
4386         /* Do we have the ratings? */
4387         if (strcmp(player1Name, white) == 0 &&
4388             strcmp(player2Name, black) == 0) {
4389             if (appData.debugMode)
4390               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4391                       player1Rating, player2Rating);
4392             gameInfo.whiteRating = player1Rating;
4393             gameInfo.blackRating = player2Rating;
4394         } else if (strcmp(player2Name, white) == 0 &&
4395                    strcmp(player1Name, black) == 0) {
4396             if (appData.debugMode)
4397               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398                       player2Rating, player1Rating);
4399             gameInfo.whiteRating = player2Rating;
4400             gameInfo.blackRating = player1Rating;
4401         }
4402         player1Name[0] = player2Name[0] = NULLCHAR;
4403
4404         /* Silence shouts if requested */
4405         if (appData.quietPlay &&
4406             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4407             SendToICS(ics_prefix);
4408             SendToICS("set shout 0\n");
4409         }
4410     }
4411
4412     /* Deal with midgame name changes */
4413     if (!newGame) {
4414         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4415             if (gameInfo.white) free(gameInfo.white);
4416             gameInfo.white = StrSave(white);
4417         }
4418         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4419             if (gameInfo.black) free(gameInfo.black);
4420             gameInfo.black = StrSave(black);
4421         }
4422     }
4423
4424     /* Throw away game result if anything actually changes in examine mode */
4425     if (gameMode == IcsExamining && !newGame) {
4426         gameInfo.result = GameUnfinished;
4427         if (gameInfo.resultDetails != NULL) {
4428             free(gameInfo.resultDetails);
4429             gameInfo.resultDetails = NULL;
4430         }
4431     }
4432
4433     /* In pausing && IcsExamining mode, we ignore boards coming
4434        in if they are in a different variation than we are. */
4435     if (pauseExamInvalid) return;
4436     if (pausing && gameMode == IcsExamining) {
4437         if (moveNum <= pauseExamForwardMostMove) {
4438             pauseExamInvalid = TRUE;
4439             forwardMostMove = pauseExamForwardMostMove;
4440             return;
4441         }
4442     }
4443
4444   if (appData.debugMode) {
4445     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4446   }
4447     /* Parse the board */
4448     for (k = 0; k < ranks; k++) {
4449       for (j = 0; j < files; j++)
4450         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4451       if(gameInfo.holdingsWidth > 1) {
4452            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4453            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4454       }
4455     }
4456     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4457       board[5][BOARD_RGHT+1] = WhiteAngel;
4458       board[6][BOARD_RGHT+1] = WhiteMarshall;
4459       board[1][0] = BlackMarshall;
4460       board[2][0] = BlackAngel;
4461       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4462     }
4463     CopyBoard(boards[moveNum], board);
4464     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4465     if (moveNum == 0) {
4466         startedFromSetupPosition =
4467           !CompareBoards(board, initialPosition);
4468         if(startedFromSetupPosition)
4469             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4470     }
4471
4472     /* [HGM] Set castling rights. Take the outermost Rooks,
4473        to make it also work for FRC opening positions. Note that board12
4474        is really defective for later FRC positions, as it has no way to
4475        indicate which Rook can castle if they are on the same side of King.
4476        For the initial position we grant rights to the outermost Rooks,
4477        and remember thos rights, and we then copy them on positions
4478        later in an FRC game. This means WB might not recognize castlings with
4479        Rooks that have moved back to their original position as illegal,
4480        but in ICS mode that is not its job anyway.
4481     */
4482     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4483     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4484
4485         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4486             if(board[0][i] == WhiteRook) j = i;
4487         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4488         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4489             if(board[0][i] == WhiteRook) j = i;
4490         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4491         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4492             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4493         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4494         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4495             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4496         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4497
4498         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4499         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4500         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4501             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4502         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4503             if(board[BOARD_HEIGHT-1][k] == bKing)
4504                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4505         if(gameInfo.variant == VariantTwoKings) {
4506             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4507             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4508             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4509         }
4510     } else { int r;
4511         r = boards[moveNum][CASTLING][0] = initialRights[0];
4512         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4513         r = boards[moveNum][CASTLING][1] = initialRights[1];
4514         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4515         r = boards[moveNum][CASTLING][3] = initialRights[3];
4516         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4517         r = boards[moveNum][CASTLING][4] = initialRights[4];
4518         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4519         /* wildcastle kludge: always assume King has rights */
4520         r = boards[moveNum][CASTLING][2] = initialRights[2];
4521         r = boards[moveNum][CASTLING][5] = initialRights[5];
4522     }
4523     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4524     boards[moveNum][EP_STATUS] = EP_NONE;
4525     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4526     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4527     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4528
4529
4530     if (ics_getting_history == H_GOT_REQ_HEADER ||
4531         ics_getting_history == H_GOT_UNREQ_HEADER) {
4532         /* This was an initial position from a move list, not
4533            the current position */
4534         return;
4535     }
4536
4537     /* Update currentMove and known move number limits */
4538     newMove = newGame || moveNum > forwardMostMove;
4539
4540     if (newGame) {
4541         forwardMostMove = backwardMostMove = currentMove = moveNum;
4542         if (gameMode == IcsExamining && moveNum == 0) {
4543           /* Workaround for ICS limitation: we are not told the wild
4544              type when starting to examine a game.  But if we ask for
4545              the move list, the move list header will tell us */
4546             ics_getting_history = H_REQUESTED;
4547             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4548             SendToICS(str);
4549         }
4550     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4551                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4552 #if ZIPPY
4553         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4554         /* [HGM] applied this also to an engine that is silently watching        */
4555         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4556             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4557             gameInfo.variant == currentlyInitializedVariant) {
4558           takeback = forwardMostMove - moveNum;
4559           for (i = 0; i < takeback; i++) {
4560             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4561             SendToProgram("undo\n", &first);
4562           }
4563         }
4564 #endif
4565
4566         forwardMostMove = moveNum;
4567         if (!pausing || currentMove > forwardMostMove)
4568           currentMove = forwardMostMove;
4569     } else {
4570         /* New part of history that is not contiguous with old part */
4571         if (pausing && gameMode == IcsExamining) {
4572             pauseExamInvalid = TRUE;
4573             forwardMostMove = pauseExamForwardMostMove;
4574             return;
4575         }
4576         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4577 #if ZIPPY
4578             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4579                 // [HGM] when we will receive the move list we now request, it will be
4580                 // fed to the engine from the first move on. So if the engine is not
4581                 // in the initial position now, bring it there.
4582                 InitChessProgram(&first, 0);
4583             }
4584 #endif
4585             ics_getting_history = H_REQUESTED;
4586             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4587             SendToICS(str);
4588         }
4589         forwardMostMove = backwardMostMove = currentMove = moveNum;
4590     }
4591
4592     /* Update the clocks */
4593     if (strchr(elapsed_time, '.')) {
4594       /* Time is in ms */
4595       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4596       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4597     } else {
4598       /* Time is in seconds */
4599       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4600       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4601     }
4602
4603
4604 #if ZIPPY
4605     if (appData.zippyPlay && newGame &&
4606         gameMode != IcsObserving && gameMode != IcsIdle &&
4607         gameMode != IcsExamining)
4608       ZippyFirstBoard(moveNum, basetime, increment);
4609 #endif
4610
4611     /* Put the move on the move list, first converting
4612        to canonical algebraic form. */
4613     if (moveNum > 0) {
4614   if (appData.debugMode) {
4615     if (appData.debugMode) { int f = forwardMostMove;
4616         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4617                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4618                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4619     }
4620     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4621     fprintf(debugFP, "moveNum = %d\n", moveNum);
4622     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4623     setbuf(debugFP, NULL);
4624   }
4625         if (moveNum <= backwardMostMove) {
4626             /* We don't know what the board looked like before
4627                this move.  Punt. */
4628           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4629             strcat(parseList[moveNum - 1], " ");
4630             strcat(parseList[moveNum - 1], elapsed_time);
4631             moveList[moveNum - 1][0] = NULLCHAR;
4632         } else if (strcmp(move_str, "none") == 0) {
4633             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4634             /* Again, we don't know what the board looked like;
4635                this is really the start of the game. */
4636             parseList[moveNum - 1][0] = NULLCHAR;
4637             moveList[moveNum - 1][0] = NULLCHAR;
4638             backwardMostMove = moveNum;
4639             startedFromSetupPosition = TRUE;
4640             fromX = fromY = toX = toY = -1;
4641         } else {
4642           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4643           //                 So we parse the long-algebraic move string in stead of the SAN move
4644           int valid; char buf[MSG_SIZ], *prom;
4645
4646           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4647                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4648           // str looks something like "Q/a1-a2"; kill the slash
4649           if(str[1] == '/')
4650             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4651           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4652           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4653                 strcat(buf, prom); // long move lacks promo specification!
4654           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4655                 if(appData.debugMode)
4656                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4657                 safeStrCpy(move_str, buf, MSG_SIZ);
4658           }
4659           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4660                                 &fromX, &fromY, &toX, &toY, &promoChar)
4661                || ParseOneMove(buf, moveNum - 1, &moveType,
4662                                 &fromX, &fromY, &toX, &toY, &promoChar);
4663           // end of long SAN patch
4664           if (valid) {
4665             (void) CoordsToAlgebraic(boards[moveNum - 1],
4666                                      PosFlags(moveNum - 1),
4667                                      fromY, fromX, toY, toX, promoChar,
4668                                      parseList[moveNum-1]);
4669             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4670               case MT_NONE:
4671               case MT_STALEMATE:
4672               default:
4673                 break;
4674               case MT_CHECK:
4675                 if(gameInfo.variant != VariantShogi)
4676                     strcat(parseList[moveNum - 1], "+");
4677                 break;
4678               case MT_CHECKMATE:
4679               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4680                 strcat(parseList[moveNum - 1], "#");
4681                 break;
4682             }
4683             strcat(parseList[moveNum - 1], " ");
4684             strcat(parseList[moveNum - 1], elapsed_time);
4685             /* currentMoveString is set as a side-effect of ParseOneMove */
4686             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4687             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4688             strcat(moveList[moveNum - 1], "\n");
4689
4690             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4691                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4692               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4693                 ChessSquare old, new = boards[moveNum][k][j];
4694                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4695                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4696                   if(old == new) continue;
4697                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4698                   else if(new == WhiteWazir || new == BlackWazir) {
4699                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4700                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4701                       else boards[moveNum][k][j] = old; // preserve type of Gold
4702                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4703                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4704               }
4705           } else {
4706             /* Move from ICS was illegal!?  Punt. */
4707             if (appData.debugMode) {
4708               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4709               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4710             }
4711             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4712             strcat(parseList[moveNum - 1], " ");
4713             strcat(parseList[moveNum - 1], elapsed_time);
4714             moveList[moveNum - 1][0] = NULLCHAR;
4715             fromX = fromY = toX = toY = -1;
4716           }
4717         }
4718   if (appData.debugMode) {
4719     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4720     setbuf(debugFP, NULL);
4721   }
4722
4723 #if ZIPPY
4724         /* Send move to chess program (BEFORE animating it). */
4725         if (appData.zippyPlay && !newGame && newMove &&
4726            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4727
4728             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4729                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4730                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4731                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4732                             move_str);
4733                     DisplayError(str, 0);
4734                 } else {
4735                     if (first.sendTime) {
4736                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4737                     }
4738                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4739                     if (firstMove && !bookHit) {
4740                         firstMove = FALSE;
4741                         if (first.useColors) {
4742                           SendToProgram(gameMode == IcsPlayingWhite ?
4743                                         "white\ngo\n" :
4744                                         "black\ngo\n", &first);
4745                         } else {
4746                           SendToProgram("go\n", &first);
4747                         }
4748                         first.maybeThinking = TRUE;
4749                     }
4750                 }
4751             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4752               if (moveList[moveNum - 1][0] == NULLCHAR) {
4753                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4754                 DisplayError(str, 0);
4755               } else {
4756                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4757                 SendMoveToProgram(moveNum - 1, &first);
4758               }
4759             }
4760         }
4761 #endif
4762     }
4763
4764     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4765         /* If move comes from a remote source, animate it.  If it
4766            isn't remote, it will have already been animated. */
4767         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4768             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4769         }
4770         if (!pausing && appData.highlightLastMove) {
4771             SetHighlights(fromX, fromY, toX, toY);
4772         }
4773     }
4774
4775     /* Start the clocks */
4776     whiteFlag = blackFlag = FALSE;
4777     appData.clockMode = !(basetime == 0 && increment == 0);
4778     if (ticking == 0) {
4779       ics_clock_paused = TRUE;
4780       StopClocks();
4781     } else if (ticking == 1) {
4782       ics_clock_paused = FALSE;
4783     }
4784     if (gameMode == IcsIdle ||
4785         relation == RELATION_OBSERVING_STATIC ||
4786         relation == RELATION_EXAMINING ||
4787         ics_clock_paused)
4788       DisplayBothClocks();
4789     else
4790       StartClocks();
4791
4792     /* Display opponents and material strengths */
4793     if (gameInfo.variant != VariantBughouse &&
4794         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4795         if (tinyLayout || smallLayout) {
4796             if(gameInfo.variant == VariantNormal)
4797               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4798                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4799                     basetime, increment);
4800             else
4801               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4802                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4803                     basetime, increment, (int) gameInfo.variant);
4804         } else {
4805             if(gameInfo.variant == VariantNormal)
4806               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4807                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4808                     basetime, increment);
4809             else
4810               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4811                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4812                     basetime, increment, VariantName(gameInfo.variant));
4813         }
4814         DisplayTitle(str);
4815   if (appData.debugMode) {
4816     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4817   }
4818     }
4819
4820
4821     /* Display the board */
4822     if (!pausing && !appData.noGUI) {
4823
4824       if (appData.premove)
4825           if (!gotPremove ||
4826              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4827              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4828               ClearPremoveHighlights();
4829
4830       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4831         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4832       DrawPosition(j, boards[currentMove]);
4833
4834       DisplayMove(moveNum - 1);
4835       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4836             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4837               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4838         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4839       }
4840     }
4841
4842     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4843 #if ZIPPY
4844     if(bookHit) { // [HGM] book: simulate book reply
4845         static char bookMove[MSG_SIZ]; // a bit generous?
4846
4847         programStats.nodes = programStats.depth = programStats.time =
4848         programStats.score = programStats.got_only_move = 0;
4849         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4850
4851         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4852         strcat(bookMove, bookHit);
4853         HandleMachineMove(bookMove, &first);
4854     }
4855 #endif
4856 }
4857
4858 void
4859 GetMoveListEvent ()
4860 {
4861     char buf[MSG_SIZ];
4862     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4863         ics_getting_history = H_REQUESTED;
4864         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4865         SendToICS(buf);
4866     }
4867 }
4868
4869 void
4870 AnalysisPeriodicEvent (int force)
4871 {
4872     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4873          && !force) || !appData.periodicUpdates)
4874       return;
4875
4876     /* Send . command to Crafty to collect stats */
4877     SendToProgram(".\n", &first);
4878
4879     /* Don't send another until we get a response (this makes
4880        us stop sending to old Crafty's which don't understand
4881        the "." command (sending illegal cmds resets node count & time,
4882        which looks bad)) */
4883     programStats.ok_to_send = 0;
4884 }
4885
4886 void
4887 ics_update_width (int new_width)
4888 {
4889         ics_printf("set width %d\n", new_width);
4890 }
4891
4892 void
4893 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4894 {
4895     char buf[MSG_SIZ];
4896
4897     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4898         // null move in variant where engine does not understand it (for analysis purposes)
4899         SendBoard(cps, moveNum + 1); // send position after move in stead.
4900         return;
4901     }
4902     if (cps->useUsermove) {
4903       SendToProgram("usermove ", cps);
4904     }
4905     if (cps->useSAN) {
4906       char *space;
4907       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4908         int len = space - parseList[moveNum];
4909         memcpy(buf, parseList[moveNum], len);
4910         buf[len++] = '\n';
4911         buf[len] = NULLCHAR;
4912       } else {
4913         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4914       }
4915       SendToProgram(buf, cps);
4916     } else {
4917       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4918         AlphaRank(moveList[moveNum], 4);
4919         SendToProgram(moveList[moveNum], cps);
4920         AlphaRank(moveList[moveNum], 4); // and back
4921       } else
4922       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4923        * the engine. It would be nice to have a better way to identify castle
4924        * moves here. */
4925       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4926                                                                          && cps->useOOCastle) {
4927         int fromX = moveList[moveNum][0] - AAA;
4928         int fromY = moveList[moveNum][1] - ONE;
4929         int toX = moveList[moveNum][2] - AAA;
4930         int toY = moveList[moveNum][3] - ONE;
4931         if((boards[moveNum][fromY][fromX] == WhiteKing
4932             && boards[moveNum][toY][toX] == WhiteRook)
4933            || (boards[moveNum][fromY][fromX] == BlackKing
4934                && boards[moveNum][toY][toX] == BlackRook)) {
4935           if(toX > fromX) SendToProgram("O-O\n", cps);
4936           else SendToProgram("O-O-O\n", cps);
4937         }
4938         else SendToProgram(moveList[moveNum], cps);
4939       } else
4940       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4941         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4942           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4943           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4944                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4945         } else
4946           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4947                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4948         SendToProgram(buf, cps);
4949       }
4950       else SendToProgram(moveList[moveNum], cps);
4951       /* End of additions by Tord */
4952     }
4953
4954     /* [HGM] setting up the opening has brought engine in force mode! */
4955     /*       Send 'go' if we are in a mode where machine should play. */
4956     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4957         (gameMode == TwoMachinesPlay   ||
4958 #if ZIPPY
4959          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4960 #endif
4961          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4962         SendToProgram("go\n", cps);
4963   if (appData.debugMode) {
4964     fprintf(debugFP, "(extra)\n");
4965   }
4966     }
4967     setboardSpoiledMachineBlack = 0;
4968 }
4969
4970 void
4971 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4972 {
4973     char user_move[MSG_SIZ];
4974     char suffix[4];
4975
4976     if(gameInfo.variant == VariantSChess && promoChar) {
4977         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4978         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4979     } else suffix[0] = NULLCHAR;
4980
4981     switch (moveType) {
4982       default:
4983         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4984                 (int)moveType, fromX, fromY, toX, toY);
4985         DisplayError(user_move + strlen("say "), 0);
4986         break;
4987       case WhiteKingSideCastle:
4988       case BlackKingSideCastle:
4989       case WhiteQueenSideCastleWild:
4990       case BlackQueenSideCastleWild:
4991       /* PUSH Fabien */
4992       case WhiteHSideCastleFR:
4993       case BlackHSideCastleFR:
4994       /* POP Fabien */
4995         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4996         break;
4997       case WhiteQueenSideCastle:
4998       case BlackQueenSideCastle:
4999       case WhiteKingSideCastleWild:
5000       case BlackKingSideCastleWild:
5001       /* PUSH Fabien */
5002       case WhiteASideCastleFR:
5003       case BlackASideCastleFR:
5004       /* POP Fabien */
5005         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5006         break;
5007       case WhiteNonPromotion:
5008       case BlackNonPromotion:
5009         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5010         break;
5011       case WhitePromotion:
5012       case BlackPromotion:
5013         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5014           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5015                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5016                 PieceToChar(WhiteFerz));
5017         else if(gameInfo.variant == VariantGreat)
5018           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020                 PieceToChar(WhiteMan));
5021         else
5022           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5023                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5024                 promoChar);
5025         break;
5026       case WhiteDrop:
5027       case BlackDrop:
5028       drop:
5029         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5030                  ToUpper(PieceToChar((ChessSquare) fromX)),
5031                  AAA + toX, ONE + toY);
5032         break;
5033       case IllegalMove:  /* could be a variant we don't quite understand */
5034         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5035       case NormalMove:
5036       case WhiteCapturesEnPassant:
5037       case BlackCapturesEnPassant:
5038         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5039                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5040         break;
5041     }
5042     SendToICS(user_move);
5043     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5044         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5045 }
5046
5047 void
5048 UploadGameEvent ()
5049 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5050     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5051     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5052     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5053       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5054       return;
5055     }
5056     if(gameMode != IcsExamining) { // is this ever not the case?
5057         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5058
5059         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5060           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5061         } else { // on FICS we must first go to general examine mode
5062           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5063         }
5064         if(gameInfo.variant != VariantNormal) {
5065             // try figure out wild number, as xboard names are not always valid on ICS
5066             for(i=1; i<=36; i++) {
5067               snprintf(buf, MSG_SIZ, "wild/%d", i);
5068                 if(StringToVariant(buf) == gameInfo.variant) break;
5069             }
5070             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5071             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5072             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5073         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5074         SendToICS(ics_prefix);
5075         SendToICS(buf);
5076         if(startedFromSetupPosition || backwardMostMove != 0) {
5077           fen = PositionToFEN(backwardMostMove, NULL);
5078           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5079             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5080             SendToICS(buf);
5081           } else { // FICS: everything has to set by separate bsetup commands
5082             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5083             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5084             SendToICS(buf);
5085             if(!WhiteOnMove(backwardMostMove)) {
5086                 SendToICS("bsetup tomove black\n");
5087             }
5088             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5089             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5090             SendToICS(buf);
5091             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5092             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5093             SendToICS(buf);
5094             i = boards[backwardMostMove][EP_STATUS];
5095             if(i >= 0) { // set e.p.
5096               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5097                 SendToICS(buf);
5098             }
5099             bsetup++;
5100           }
5101         }
5102       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5103             SendToICS("bsetup done\n"); // switch to normal examining.
5104     }
5105     for(i = backwardMostMove; i<last; i++) {
5106         char buf[20];
5107         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5108         SendToICS(buf);
5109     }
5110     SendToICS(ics_prefix);
5111     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5112 }
5113
5114 void
5115 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5116 {
5117     if (rf == DROP_RANK) {
5118       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5119       sprintf(move, "%c@%c%c\n",
5120                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5121     } else {
5122         if (promoChar == 'x' || promoChar == NULLCHAR) {
5123           sprintf(move, "%c%c%c%c\n",
5124                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5125         } else {
5126             sprintf(move, "%c%c%c%c%c\n",
5127                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5128         }
5129     }
5130 }
5131
5132 void
5133 ProcessICSInitScript (FILE *f)
5134 {
5135     char buf[MSG_SIZ];
5136
5137     while (fgets(buf, MSG_SIZ, f)) {
5138         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5139     }
5140
5141     fclose(f);
5142 }
5143
5144
5145 static int lastX, lastY, selectFlag, dragging;
5146
5147 void
5148 Sweep (int step)
5149 {
5150     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5151     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5152     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5153     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5154     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5155     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5156     do {
5157         promoSweep -= step;
5158         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5159         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5160         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5161         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5162         if(!step) step = -1;
5163     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5164             appData.testLegality && (promoSweep == king ||
5165             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5166     ChangeDragPiece(promoSweep);
5167 }
5168
5169 int
5170 PromoScroll (int x, int y)
5171 {
5172   int step = 0;
5173
5174   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5175   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5176   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5177   if(!step) return FALSE;
5178   lastX = x; lastY = y;
5179   if((promoSweep < BlackPawn) == flipView) step = -step;
5180   if(step > 0) selectFlag = 1;
5181   if(!selectFlag) Sweep(step);
5182   return FALSE;
5183 }
5184
5185 void
5186 NextPiece (int step)
5187 {
5188     ChessSquare piece = boards[currentMove][toY][toX];
5189     do {
5190         pieceSweep -= step;
5191         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5192         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5193         if(!step) step = -1;
5194     } while(PieceToChar(pieceSweep) == '.');
5195     boards[currentMove][toY][toX] = pieceSweep;
5196     DrawPosition(FALSE, boards[currentMove]);
5197     boards[currentMove][toY][toX] = piece;
5198 }
5199 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5200 void
5201 AlphaRank (char *move, int n)
5202 {
5203 //    char *p = move, c; int x, y;
5204
5205     if (appData.debugMode) {
5206         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5207     }
5208
5209     if(move[1]=='*' &&
5210        move[2]>='0' && move[2]<='9' &&
5211        move[3]>='a' && move[3]<='x'    ) {
5212         move[1] = '@';
5213         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5215     } else
5216     if(move[0]>='0' && move[0]<='9' &&
5217        move[1]>='a' && move[1]<='x' &&
5218        move[2]>='0' && move[2]<='9' &&
5219        move[3]>='a' && move[3]<='x'    ) {
5220         /* input move, Shogi -> normal */
5221         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5223         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5225     } else
5226     if(move[1]=='@' &&
5227        move[3]>='0' && move[3]<='9' &&
5228        move[2]>='a' && move[2]<='x'    ) {
5229         move[1] = '*';
5230         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5231         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5232     } else
5233     if(
5234        move[0]>='a' && move[0]<='x' &&
5235        move[3]>='0' && move[3]<='9' &&
5236        move[2]>='a' && move[2]<='x'    ) {
5237          /* output move, normal -> Shogi */
5238         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5239         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5240         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5241         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5242         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5243     }
5244     if (appData.debugMode) {
5245         fprintf(debugFP, "   out = '%s'\n", move);
5246     }
5247 }
5248
5249 char yy_textstr[8000];
5250
5251 /* Parser for moves from gnuchess, ICS, or user typein box */
5252 Boolean
5253 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5254 {
5255     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5256
5257     switch (*moveType) {
5258       case WhitePromotion:
5259       case BlackPromotion:
5260       case WhiteNonPromotion:
5261       case BlackNonPromotion:
5262       case NormalMove:
5263       case WhiteCapturesEnPassant:
5264       case BlackCapturesEnPassant:
5265       case WhiteKingSideCastle:
5266       case WhiteQueenSideCastle:
5267       case BlackKingSideCastle:
5268       case BlackQueenSideCastle:
5269       case WhiteKingSideCastleWild:
5270       case WhiteQueenSideCastleWild:
5271       case BlackKingSideCastleWild:
5272       case BlackQueenSideCastleWild:
5273       /* Code added by Tord: */
5274       case WhiteHSideCastleFR:
5275       case WhiteASideCastleFR:
5276       case BlackHSideCastleFR:
5277       case BlackASideCastleFR:
5278       /* End of code added by Tord */
5279       case IllegalMove:         /* bug or odd chess variant */
5280         *fromX = currentMoveString[0] - AAA;
5281         *fromY = currentMoveString[1] - ONE;
5282         *toX = currentMoveString[2] - AAA;
5283         *toY = currentMoveString[3] - ONE;
5284         *promoChar = currentMoveString[4];
5285         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5286             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5287     if (appData.debugMode) {
5288         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5289     }
5290             *fromX = *fromY = *toX = *toY = 0;
5291             return FALSE;
5292         }
5293         if (appData.testLegality) {
5294           return (*moveType != IllegalMove);
5295         } else {
5296           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5297                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5298         }
5299
5300       case WhiteDrop:
5301       case BlackDrop:
5302         *fromX = *moveType == WhiteDrop ?
5303           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5304           (int) CharToPiece(ToLower(currentMoveString[0]));
5305         *fromY = DROP_RANK;
5306         *toX = currentMoveString[2] - AAA;
5307         *toY = currentMoveString[3] - ONE;
5308         *promoChar = NULLCHAR;
5309         return TRUE;
5310
5311       case AmbiguousMove:
5312       case ImpossibleMove:
5313       case EndOfFile:
5314       case ElapsedTime:
5315       case Comment:
5316       case PGNTag:
5317       case NAG:
5318       case WhiteWins:
5319       case BlackWins:
5320       case GameIsDrawn:
5321       default:
5322     if (appData.debugMode) {
5323         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5324     }
5325         /* bug? */
5326         *fromX = *fromY = *toX = *toY = 0;
5327         *promoChar = NULLCHAR;
5328         return FALSE;
5329     }
5330 }
5331
5332 Boolean pushed = FALSE;
5333 char *lastParseAttempt;
5334
5335 void
5336 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5337 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5338   int fromX, fromY, toX, toY; char promoChar;
5339   ChessMove moveType;
5340   Boolean valid;
5341   int nr = 0;
5342
5343   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5344     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5345     pushed = TRUE;
5346   }
5347   endPV = forwardMostMove;
5348   do {
5349     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5350     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5351     lastParseAttempt = pv;
5352     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5353     if(!valid && nr == 0 &&
5354        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5355         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5356         // Hande case where played move is different from leading PV move
5357         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5358         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5359         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5360         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5361           endPV += 2; // if position different, keep this
5362           moveList[endPV-1][0] = fromX + AAA;
5363           moveList[endPV-1][1] = fromY + ONE;
5364           moveList[endPV-1][2] = toX + AAA;
5365           moveList[endPV-1][3] = toY + ONE;
5366           parseList[endPV-1][0] = NULLCHAR;
5367           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5368         }
5369       }
5370     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5371     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5372     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5373     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5374         valid++; // allow comments in PV
5375         continue;
5376     }
5377     nr++;
5378     if(endPV+1 > framePtr) break; // no space, truncate
5379     if(!valid) break;
5380     endPV++;
5381     CopyBoard(boards[endPV], boards[endPV-1]);
5382     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5383     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5384     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5385     CoordsToAlgebraic(boards[endPV - 1],
5386                              PosFlags(endPV - 1),
5387                              fromY, fromX, toY, toX, promoChar,
5388                              parseList[endPV - 1]);
5389   } while(valid);
5390   if(atEnd == 2) return; // used hidden, for PV conversion
5391   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5392   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5393   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5394                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5395   DrawPosition(TRUE, boards[currentMove]);
5396 }
5397
5398 int
5399 MultiPV (ChessProgramState *cps)
5400 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5401         int i;
5402         for(i=0; i<cps->nrOptions; i++)
5403             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5404                 return i;
5405         return -1;
5406 }
5407
5408 Boolean
5409 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5410 {
5411         int startPV, multi, lineStart, origIndex = index;
5412         char *p, buf2[MSG_SIZ];
5413
5414         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5415         lastX = x; lastY = y;
5416         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5417         lineStart = startPV = index;
5418         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5419         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5420         index = startPV;
5421         do{ while(buf[index] && buf[index] != '\n') index++;
5422         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5423         buf[index] = 0;
5424         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5425                 int n = first.option[multi].value;
5426                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5427                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5428                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5429                 first.option[multi].value = n;
5430                 *start = *end = 0;
5431                 return FALSE;
5432         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5433                 ExcludeClick(origIndex - lineStart);
5434                 return FALSE;
5435         }
5436         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5437         *start = startPV; *end = index-1;
5438         return TRUE;
5439 }
5440
5441 char *
5442 PvToSAN (char *pv)
5443 {
5444         static char buf[10*MSG_SIZ];
5445         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5446         *buf = NULLCHAR;
5447         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5448         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5449         for(i = forwardMostMove; i<endPV; i++){
5450             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5451             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5452             k += strlen(buf+k);
5453         }
5454         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5455         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5456         endPV = savedEnd;
5457         return buf;
5458 }
5459
5460 Boolean
5461 LoadPV (int x, int y)
5462 { // called on right mouse click to load PV
5463   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5464   lastX = x; lastY = y;
5465   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5466   return TRUE;
5467 }
5468
5469 void
5470 UnLoadPV ()
5471 {
5472   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5473   if(endPV < 0) return;
5474   if(appData.autoCopyPV) CopyFENToClipboard();
5475   endPV = -1;
5476   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5477         Boolean saveAnimate = appData.animate;
5478         if(pushed) {
5479             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5480                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5481             } else storedGames--; // abandon shelved tail of original game
5482         }
5483         pushed = FALSE;
5484         forwardMostMove = currentMove;
5485         currentMove = oldFMM;
5486         appData.animate = FALSE;
5487         ToNrEvent(forwardMostMove);
5488         appData.animate = saveAnimate;
5489   }
5490   currentMove = forwardMostMove;
5491   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5492   ClearPremoveHighlights();
5493   DrawPosition(TRUE, boards[currentMove]);
5494 }
5495
5496 void
5497 MovePV (int x, int y, int h)
5498 { // step through PV based on mouse coordinates (called on mouse move)
5499   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5500
5501   // we must somehow check if right button is still down (might be released off board!)
5502   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5503   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5504   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5505   if(!step) return;
5506   lastX = x; lastY = y;
5507
5508   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5509   if(endPV < 0) return;
5510   if(y < margin) step = 1; else
5511   if(y > h - margin) step = -1;
5512   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5513   currentMove += step;
5514   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5515   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5516                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5517   DrawPosition(FALSE, boards[currentMove]);
5518 }
5519
5520
5521 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5522 // All positions will have equal probability, but the current method will not provide a unique
5523 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5524 #define DARK 1
5525 #define LITE 2
5526 #define ANY 3
5527
5528 int squaresLeft[4];
5529 int piecesLeft[(int)BlackPawn];
5530 int seed, nrOfShuffles;
5531
5532 void
5533 GetPositionNumber ()
5534 {       // sets global variable seed
5535         int i;
5536
5537         seed = appData.defaultFrcPosition;
5538         if(seed < 0) { // randomize based on time for negative FRC position numbers
5539                 for(i=0; i<50; i++) seed += random();
5540                 seed = random() ^ random() >> 8 ^ random() << 8;
5541                 if(seed<0) seed = -seed;
5542         }
5543 }
5544
5545 int
5546 put (Board board, int pieceType, int rank, int n, int shade)
5547 // put the piece on the (n-1)-th empty squares of the given shade
5548 {
5549         int i;
5550
5551         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5552                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5553                         board[rank][i] = (ChessSquare) pieceType;
5554                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5555                         squaresLeft[ANY]--;
5556                         piecesLeft[pieceType]--;
5557                         return i;
5558                 }
5559         }
5560         return -1;
5561 }
5562
5563
5564 void
5565 AddOnePiece (Board board, int pieceType, int rank, int shade)
5566 // calculate where the next piece goes, (any empty square), and put it there
5567 {
5568         int i;
5569
5570         i = seed % squaresLeft[shade];
5571         nrOfShuffles *= squaresLeft[shade];
5572         seed /= squaresLeft[shade];
5573         put(board, pieceType, rank, i, shade);
5574 }
5575
5576 void
5577 AddTwoPieces (Board board, int pieceType, int rank)
5578 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5579 {
5580         int i, n=squaresLeft[ANY], j=n-1, k;
5581
5582         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5583         i = seed % k;  // pick one
5584         nrOfShuffles *= k;
5585         seed /= k;
5586         while(i >= j) i -= j--;
5587         j = n - 1 - j; i += j;
5588         put(board, pieceType, rank, j, ANY);
5589         put(board, pieceType, rank, i, ANY);
5590 }
5591
5592 void
5593 SetUpShuffle (Board board, int number)
5594 {
5595         int i, p, first=1;
5596
5597         GetPositionNumber(); nrOfShuffles = 1;
5598
5599         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5600         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5601         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5602
5603         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5604
5605         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5606             p = (int) board[0][i];
5607             if(p < (int) BlackPawn) piecesLeft[p] ++;
5608             board[0][i] = EmptySquare;
5609         }
5610
5611         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5612             // shuffles restricted to allow normal castling put KRR first
5613             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5614                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5615             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5616                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5617             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5618                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5619             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5620                 put(board, WhiteRook, 0, 0, ANY);
5621             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5622         }
5623
5624         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5625             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5626             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5627                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5628                 while(piecesLeft[p] >= 2) {
5629                     AddOnePiece(board, p, 0, LITE);
5630                     AddOnePiece(board, p, 0, DARK);
5631                 }
5632                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5633             }
5634
5635         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5636             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5637             // but we leave King and Rooks for last, to possibly obey FRC restriction
5638             if(p == (int)WhiteRook) continue;
5639             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5640             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5641         }
5642
5643         // now everything is placed, except perhaps King (Unicorn) and Rooks
5644
5645         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5646             // Last King gets castling rights
5647             while(piecesLeft[(int)WhiteUnicorn]) {
5648                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5649                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5650             }
5651
5652             while(piecesLeft[(int)WhiteKing]) {
5653                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5654                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5655             }
5656
5657
5658         } else {
5659             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5660             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5661         }
5662
5663         // Only Rooks can be left; simply place them all
5664         while(piecesLeft[(int)WhiteRook]) {
5665                 i = put(board, WhiteRook, 0, 0, ANY);
5666                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5667                         if(first) {
5668                                 first=0;
5669                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5670                         }
5671                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5672                 }
5673         }
5674         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5675             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5676         }
5677
5678         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5679 }
5680
5681 int
5682 SetCharTable (char *table, const char * map)
5683 /* [HGM] moved here from winboard.c because of its general usefulness */
5684 /*       Basically a safe strcpy that uses the last character as King */
5685 {
5686     int result = FALSE; int NrPieces;
5687
5688     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5689                     && NrPieces >= 12 && !(NrPieces&1)) {
5690         int i; /* [HGM] Accept even length from 12 to 34 */
5691
5692         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5693         for( i=0; i<NrPieces/2-1; i++ ) {
5694             table[i] = map[i];
5695             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5696         }
5697         table[(int) WhiteKing]  = map[NrPieces/2-1];
5698         table[(int) BlackKing]  = map[NrPieces-1];
5699
5700         result = TRUE;
5701     }
5702
5703     return result;
5704 }
5705
5706 void
5707 Prelude (Board board)
5708 {       // [HGM] superchess: random selection of exo-pieces
5709         int i, j, k; ChessSquare p;
5710         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5711
5712         GetPositionNumber(); // use FRC position number
5713
5714         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5715             SetCharTable(pieceToChar, appData.pieceToCharTable);
5716             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5717                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5718         }
5719
5720         j = seed%4;                 seed /= 4;
5721         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5722         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5723         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5724         j = seed%3 + (seed%3 >= j); seed /= 3;
5725         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5726         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5727         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5728         j = seed%3;                 seed /= 3;
5729         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5730         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5731         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5732         j = seed%2 + (seed%2 >= j); seed /= 2;
5733         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5734         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5735         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5736         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5737         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5738         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5739         put(board, exoPieces[0],    0, 0, ANY);
5740         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5741 }
5742
5743 void
5744 InitPosition (int redraw)
5745 {
5746     ChessSquare (* pieces)[BOARD_FILES];
5747     int i, j, pawnRow, overrule,
5748     oldx = gameInfo.boardWidth,
5749     oldy = gameInfo.boardHeight,
5750     oldh = gameInfo.holdingsWidth;
5751     static int oldv;
5752
5753     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5754
5755     /* [AS] Initialize pv info list [HGM] and game status */
5756     {
5757         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5758             pvInfoList[i].depth = 0;
5759             boards[i][EP_STATUS] = EP_NONE;
5760             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5761         }
5762
5763         initialRulePlies = 0; /* 50-move counter start */
5764
5765         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5766         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5767     }
5768
5769
5770     /* [HGM] logic here is completely changed. In stead of full positions */
5771     /* the initialized data only consist of the two backranks. The switch */
5772     /* selects which one we will use, which is than copied to the Board   */
5773     /* initialPosition, which for the rest is initialized by Pawns and    */
5774     /* empty squares. This initial position is then copied to boards[0],  */
5775     /* possibly after shuffling, so that it remains available.            */
5776
5777     gameInfo.holdingsWidth = 0; /* default board sizes */
5778     gameInfo.boardWidth    = 8;
5779     gameInfo.boardHeight   = 8;
5780     gameInfo.holdingsSize  = 0;
5781     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5782     for(i=0; i<BOARD_FILES-2; i++)
5783       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5784     initialPosition[EP_STATUS] = EP_NONE;
5785     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5786     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5787          SetCharTable(pieceNickName, appData.pieceNickNames);
5788     else SetCharTable(pieceNickName, "............");
5789     pieces = FIDEArray;
5790
5791     switch (gameInfo.variant) {
5792     case VariantFischeRandom:
5793       shuffleOpenings = TRUE;
5794     default:
5795       break;
5796     case VariantShatranj:
5797       pieces = ShatranjArray;
5798       nrCastlingRights = 0;
5799       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5800       break;
5801     case VariantMakruk:
5802       pieces = makrukArray;
5803       nrCastlingRights = 0;
5804       startedFromSetupPosition = TRUE;
5805       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5806       break;
5807     case VariantTwoKings:
5808       pieces = twoKingsArray;
5809       break;
5810     case VariantGrand:
5811       pieces = GrandArray;
5812       nrCastlingRights = 0;
5813       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5814       gameInfo.boardWidth = 10;
5815       gameInfo.boardHeight = 10;
5816       gameInfo.holdingsSize = 7;
5817       break;
5818     case VariantCapaRandom:
5819       shuffleOpenings = TRUE;
5820     case VariantCapablanca:
5821       pieces = CapablancaArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5824       break;
5825     case VariantGothic:
5826       pieces = GothicArray;
5827       gameInfo.boardWidth = 10;
5828       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5829       break;
5830     case VariantSChess:
5831       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5832       gameInfo.holdingsSize = 7;
5833       break;
5834     case VariantJanus:
5835       pieces = JanusArray;
5836       gameInfo.boardWidth = 10;
5837       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5838       nrCastlingRights = 6;
5839         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5840         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5841         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5842         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5843         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5844         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5845       break;
5846     case VariantFalcon:
5847       pieces = FalconArray;
5848       gameInfo.boardWidth = 10;
5849       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5850       break;
5851     case VariantXiangqi:
5852       pieces = XiangqiArray;
5853       gameInfo.boardWidth  = 9;
5854       gameInfo.boardHeight = 10;
5855       nrCastlingRights = 0;
5856       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5857       break;
5858     case VariantShogi:
5859       pieces = ShogiArray;
5860       gameInfo.boardWidth  = 9;
5861       gameInfo.boardHeight = 9;
5862       gameInfo.holdingsSize = 7;
5863       nrCastlingRights = 0;
5864       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5865       break;
5866     case VariantCourier:
5867       pieces = CourierArray;
5868       gameInfo.boardWidth  = 12;
5869       nrCastlingRights = 0;
5870       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5871       break;
5872     case VariantKnightmate:
5873       pieces = KnightmateArray;
5874       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5875       break;
5876     case VariantSpartan:
5877       pieces = SpartanArray;
5878       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5879       break;
5880     case VariantFairy:
5881       pieces = fairyArray;
5882       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5883       break;
5884     case VariantGreat:
5885       pieces = GreatArray;
5886       gameInfo.boardWidth = 10;
5887       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5888       gameInfo.holdingsSize = 8;
5889       break;
5890     case VariantSuper:
5891       pieces = FIDEArray;
5892       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5893       gameInfo.holdingsSize = 8;
5894       startedFromSetupPosition = TRUE;
5895       break;
5896     case VariantCrazyhouse:
5897     case VariantBughouse:
5898       pieces = FIDEArray;
5899       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5900       gameInfo.holdingsSize = 5;
5901       break;
5902     case VariantWildCastle:
5903       pieces = FIDEArray;
5904       /* !!?shuffle with kings guaranteed to be on d or e file */
5905       shuffleOpenings = 1;
5906       break;
5907     case VariantNoCastle:
5908       pieces = FIDEArray;
5909       nrCastlingRights = 0;
5910       /* !!?unconstrained back-rank shuffle */
5911       shuffleOpenings = 1;
5912       break;
5913     }
5914
5915     overrule = 0;
5916     if(appData.NrFiles >= 0) {
5917         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5918         gameInfo.boardWidth = appData.NrFiles;
5919     }
5920     if(appData.NrRanks >= 0) {
5921         gameInfo.boardHeight = appData.NrRanks;
5922     }
5923     if(appData.holdingsSize >= 0) {
5924         i = appData.holdingsSize;
5925         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5926         gameInfo.holdingsSize = i;
5927     }
5928     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5929     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5930         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5931
5932     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5933     if(pawnRow < 1) pawnRow = 1;
5934     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5935
5936     /* User pieceToChar list overrules defaults */
5937     if(appData.pieceToCharTable != NULL)
5938         SetCharTable(pieceToChar, appData.pieceToCharTable);
5939
5940     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5941
5942         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5943             s = (ChessSquare) 0; /* account holding counts in guard band */
5944         for( i=0; i<BOARD_HEIGHT; i++ )
5945             initialPosition[i][j] = s;
5946
5947         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5948         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5949         initialPosition[pawnRow][j] = WhitePawn;
5950         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5951         if(gameInfo.variant == VariantXiangqi) {
5952             if(j&1) {
5953                 initialPosition[pawnRow][j] =
5954                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5955                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5956                    initialPosition[2][j] = WhiteCannon;
5957                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5958                 }
5959             }
5960         }
5961         if(gameInfo.variant == VariantGrand) {
5962             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5963                initialPosition[0][j] = WhiteRook;
5964                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5965             }
5966         }
5967         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5968     }
5969     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5970
5971             j=BOARD_LEFT+1;
5972             initialPosition[1][j] = WhiteBishop;
5973             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5974             j=BOARD_RGHT-2;
5975             initialPosition[1][j] = WhiteRook;
5976             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5977     }
5978
5979     if( nrCastlingRights == -1) {
5980         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5981         /*       This sets default castling rights from none to normal corners   */
5982         /* Variants with other castling rights must set them themselves above    */
5983         nrCastlingRights = 6;
5984
5985         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5986         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5987         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5988         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5989         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5990         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5991      }
5992
5993      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5994      if(gameInfo.variant == VariantGreat) { // promotion commoners
5995         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5996         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5997         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5998         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5999      }
6000      if( gameInfo.variant == VariantSChess ) {
6001       initialPosition[1][0] = BlackMarshall;
6002       initialPosition[2][0] = BlackAngel;
6003       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6004       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6005       initialPosition[1][1] = initialPosition[2][1] = 
6006       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6007      }
6008   if (appData.debugMode) {
6009     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6010   }
6011     if(shuffleOpenings) {
6012         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6013         startedFromSetupPosition = TRUE;
6014     }
6015     if(startedFromPositionFile) {
6016       /* [HGM] loadPos: use PositionFile for every new game */
6017       CopyBoard(initialPosition, filePosition);
6018       for(i=0; i<nrCastlingRights; i++)
6019           initialRights[i] = filePosition[CASTLING][i];
6020       startedFromSetupPosition = TRUE;
6021     }
6022
6023     CopyBoard(boards[0], initialPosition);
6024
6025     if(oldx != gameInfo.boardWidth ||
6026        oldy != gameInfo.boardHeight ||
6027        oldv != gameInfo.variant ||
6028        oldh != gameInfo.holdingsWidth
6029                                          )
6030             InitDrawingSizes(-2 ,0);
6031
6032     oldv = gameInfo.variant;
6033     if (redraw)
6034       DrawPosition(TRUE, boards[currentMove]);
6035 }
6036
6037 void
6038 SendBoard (ChessProgramState *cps, int moveNum)
6039 {
6040     char message[MSG_SIZ];
6041
6042     if (cps->useSetboard) {
6043       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6044       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6045       SendToProgram(message, cps);
6046       free(fen);
6047
6048     } else {
6049       ChessSquare *bp;
6050       int i, j, left=0, right=BOARD_WIDTH;
6051       /* Kludge to set black to move, avoiding the troublesome and now
6052        * deprecated "black" command.
6053        */
6054       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6055         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6056
6057       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6058
6059       SendToProgram("edit\n", cps);
6060       SendToProgram("#\n", cps);
6061       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6062         bp = &boards[moveNum][i][left];
6063         for (j = left; j < right; j++, bp++) {
6064           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6065           if ((int) *bp < (int) BlackPawn) {
6066             if(j == BOARD_RGHT+1)
6067                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6068             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6069             if(message[0] == '+' || message[0] == '~') {
6070               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6071                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6072                         AAA + j, ONE + i);
6073             }
6074             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6075                 message[1] = BOARD_RGHT   - 1 - j + '1';
6076                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6077             }
6078             SendToProgram(message, cps);
6079           }
6080         }
6081       }
6082
6083       SendToProgram("c\n", cps);
6084       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6085         bp = &boards[moveNum][i][left];
6086         for (j = left; j < right; j++, bp++) {
6087           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6088           if (((int) *bp != (int) EmptySquare)
6089               && ((int) *bp >= (int) BlackPawn)) {
6090             if(j == BOARD_LEFT-2)
6091                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6092             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6093                     AAA + j, ONE + i);
6094             if(message[0] == '+' || message[0] == '~') {
6095               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6096                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6097                         AAA + j, ONE + i);
6098             }
6099             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6100                 message[1] = BOARD_RGHT   - 1 - j + '1';
6101                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6102             }
6103             SendToProgram(message, cps);
6104           }
6105         }
6106       }
6107
6108       SendToProgram(".\n", cps);
6109     }
6110     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6111 }
6112
6113 char exclusionHeader[MSG_SIZ];
6114 int exCnt, excludePtr;
6115 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6116 static Exclusion excluTab[200];
6117 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6118
6119 static void
6120 WriteMap (int s)
6121 {
6122     int j;
6123     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6124     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6125 }
6126
6127 static void
6128 ClearMap ()
6129 {
6130     int j;
6131     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6132     excludePtr = 24; exCnt = 0;
6133     WriteMap(0);
6134 }
6135
6136 static void
6137 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6138 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6139     char buf[2*MOVE_LEN], *p;
6140     Exclusion *e = excluTab;
6141     int i;
6142     for(i=0; i<exCnt; i++)
6143         if(e[i].ff == fromX && e[i].fr == fromY &&
6144            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6145     if(i == exCnt) { // was not in exclude list; add it
6146         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6147         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6148             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6149             return; // abort
6150         }
6151         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6152         excludePtr++; e[i].mark = excludePtr++;
6153         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6154         exCnt++;
6155     }
6156     exclusionHeader[e[i].mark] = state;
6157 }
6158
6159 static int
6160 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6161 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6162     char *p, buf[MSG_SIZ];
6163     int j, k;
6164     ChessMove moveType;
6165     if(promoChar == -1) { // kludge to indicate best move
6166         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6167             return 1; // if unparsable, abort
6168     }
6169     // update exclusion map (resolving toggle by consulting existing state)
6170     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6171     j = k%8; k >>= 3;
6172     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6173     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6174          excludeMap[k] |=   1<<j;
6175     else excludeMap[k] &= ~(1<<j);
6176     // update header
6177     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6178     // inform engine
6179     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6180     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6181     SendToProgram(buf, &first);
6182     return (state == '+');
6183 }
6184
6185 static void
6186 ExcludeClick (int index)
6187 {
6188     int i, j;
6189     char buf[MSG_SIZ];
6190     Exclusion *e = excluTab;
6191     if(index < 25) { // none, best or tail clicked
6192         if(index < 13) { // none: include all
6193             WriteMap(0); // clear map
6194             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6195             SendToProgram("include all\n", &first); // and inform engine
6196         } else if(index > 18) { // tail
6197             if(exclusionHeader[19] == '-') { // tail was excluded
6198                 SendToProgram("include all\n", &first);
6199                 WriteMap(0); // clear map completely
6200                 // now re-exclude selected moves
6201                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6202                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6203             } else { // tail was included or in mixed state
6204                 SendToProgram("exclude all\n", &first);
6205                 WriteMap(0xFF); // fill map completely
6206                 // now re-include selected moves
6207                 j = 0; // count them
6208                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6209                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6210                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6211             }
6212         } else { // best
6213             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6214         }
6215     } else {
6216         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6217             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6218             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6219             break;
6220         }
6221     }
6222 }
6223
6224 ChessSquare
6225 DefaultPromoChoice (int white)
6226 {
6227     ChessSquare result;
6228     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6229         result = WhiteFerz; // no choice
6230     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6231         result= WhiteKing; // in Suicide Q is the last thing we want
6232     else if(gameInfo.variant == VariantSpartan)
6233         result = white ? WhiteQueen : WhiteAngel;
6234     else result = WhiteQueen;
6235     if(!white) result = WHITE_TO_BLACK result;
6236     return result;
6237 }
6238
6239 static int autoQueen; // [HGM] oneclick
6240
6241 int
6242 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6243 {
6244     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6245     /* [HGM] add Shogi promotions */
6246     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6247     ChessSquare piece;
6248     ChessMove moveType;
6249     Boolean premove;
6250
6251     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6252     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6253
6254     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6255       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6256         return FALSE;
6257
6258     piece = boards[currentMove][fromY][fromX];
6259     if(gameInfo.variant == VariantShogi) {
6260         promotionZoneSize = BOARD_HEIGHT/3;
6261         highestPromotingPiece = (int)WhiteFerz;
6262     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6263         promotionZoneSize = 3;
6264     }
6265
6266     // Treat Lance as Pawn when it is not representing Amazon
6267     if(gameInfo.variant != VariantSuper) {
6268         if(piece == WhiteLance) piece = WhitePawn; else
6269         if(piece == BlackLance) piece = BlackPawn;
6270     }
6271
6272     // next weed out all moves that do not touch the promotion zone at all
6273     if((int)piece >= BlackPawn) {
6274         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6275              return FALSE;
6276         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6277     } else {
6278         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6279            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6280     }
6281
6282     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6283
6284     // weed out mandatory Shogi promotions
6285     if(gameInfo.variant == VariantShogi) {
6286         if(piece >= BlackPawn) {
6287             if(toY == 0 && piece == BlackPawn ||
6288                toY == 0 && piece == BlackQueen ||
6289                toY <= 1 && piece == BlackKnight) {
6290                 *promoChoice = '+';
6291                 return FALSE;
6292             }
6293         } else {
6294             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6295                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6296                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6297                 *promoChoice = '+';
6298                 return FALSE;
6299             }
6300         }
6301     }
6302
6303     // weed out obviously illegal Pawn moves
6304     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6305         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6306         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6307         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6308         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6309         // note we are not allowed to test for valid (non-)capture, due to premove
6310     }
6311
6312     // we either have a choice what to promote to, or (in Shogi) whether to promote
6313     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6314         *promoChoice = PieceToChar(BlackFerz);  // no choice
6315         return FALSE;
6316     }
6317     // no sense asking what we must promote to if it is going to explode...
6318     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6319         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6320         return FALSE;
6321     }
6322     // give caller the default choice even if we will not make it
6323     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6324     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6325     if(        sweepSelect && gameInfo.variant != VariantGreat
6326                            && gameInfo.variant != VariantGrand
6327                            && gameInfo.variant != VariantSuper) return FALSE;
6328     if(autoQueen) return FALSE; // predetermined
6329
6330     // suppress promotion popup on illegal moves that are not premoves
6331     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6332               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6333     if(appData.testLegality && !premove) {
6334         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6335                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6336         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6337             return FALSE;
6338     }
6339
6340     return TRUE;
6341 }
6342
6343 int
6344 InPalace (int row, int column)
6345 {   /* [HGM] for Xiangqi */
6346     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6347          column < (BOARD_WIDTH + 4)/2 &&
6348          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6349     return FALSE;
6350 }
6351
6352 int
6353 PieceForSquare (int x, int y)
6354 {
6355   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6356      return -1;
6357   else
6358      return boards[currentMove][y][x];
6359 }
6360
6361 int
6362 OKToStartUserMove (int x, int y)
6363 {
6364     ChessSquare from_piece;
6365     int white_piece;
6366
6367     if (matchMode) return FALSE;
6368     if (gameMode == EditPosition) return TRUE;
6369
6370     if (x >= 0 && y >= 0)
6371       from_piece = boards[currentMove][y][x];
6372     else
6373       from_piece = EmptySquare;
6374
6375     if (from_piece == EmptySquare) return FALSE;
6376
6377     white_piece = (int)from_piece >= (int)WhitePawn &&
6378       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6379
6380     switch (gameMode) {
6381       case AnalyzeFile:
6382       case TwoMachinesPlay:
6383       case EndOfGame:
6384         return FALSE;
6385
6386       case IcsObserving:
6387       case IcsIdle:
6388         return FALSE;
6389
6390       case MachinePlaysWhite:
6391       case IcsPlayingBlack:
6392         if (appData.zippyPlay) return FALSE;
6393         if (white_piece) {
6394             DisplayMoveError(_("You are playing Black"));
6395             return FALSE;
6396         }
6397         break;
6398
6399       case MachinePlaysBlack:
6400       case IcsPlayingWhite:
6401         if (appData.zippyPlay) return FALSE;
6402         if (!white_piece) {
6403             DisplayMoveError(_("You are playing White"));
6404             return FALSE;
6405         }
6406         break;
6407
6408       case PlayFromGameFile:
6409             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6410       case EditGame:
6411         if (!white_piece && WhiteOnMove(currentMove)) {
6412             DisplayMoveError(_("It is White's turn"));
6413             return FALSE;
6414         }
6415         if (white_piece && !WhiteOnMove(currentMove)) {
6416             DisplayMoveError(_("It is Black's turn"));
6417             return FALSE;
6418         }
6419         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6420             /* Editing correspondence game history */
6421             /* Could disallow this or prompt for confirmation */
6422             cmailOldMove = -1;
6423         }
6424         break;
6425
6426       case BeginningOfGame:
6427         if (appData.icsActive) return FALSE;
6428         if (!appData.noChessProgram) {
6429             if (!white_piece) {
6430                 DisplayMoveError(_("You are playing White"));
6431                 return FALSE;
6432             }
6433         }
6434         break;
6435
6436       case Training:
6437         if (!white_piece && WhiteOnMove(currentMove)) {
6438             DisplayMoveError(_("It is White's turn"));
6439             return FALSE;
6440         }
6441         if (white_piece && !WhiteOnMove(currentMove)) {
6442             DisplayMoveError(_("It is Black's turn"));
6443             return FALSE;
6444         }
6445         break;
6446
6447       default:
6448       case IcsExamining:
6449         break;
6450     }
6451     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6452         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6453         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6454         && gameMode != AnalyzeFile && gameMode != Training) {
6455         DisplayMoveError(_("Displayed position is not current"));
6456         return FALSE;
6457     }
6458     return TRUE;
6459 }
6460
6461 Boolean
6462 OnlyMove (int *x, int *y, Boolean captures) 
6463 {
6464     DisambiguateClosure cl;
6465     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6466     switch(gameMode) {
6467       case MachinePlaysBlack:
6468       case IcsPlayingWhite:
6469       case BeginningOfGame:
6470         if(!WhiteOnMove(currentMove)) return FALSE;
6471         break;
6472       case MachinePlaysWhite:
6473       case IcsPlayingBlack:
6474         if(WhiteOnMove(currentMove)) return FALSE;
6475         break;
6476       case EditGame:
6477         break;
6478       default:
6479         return FALSE;
6480     }
6481     cl.pieceIn = EmptySquare;
6482     cl.rfIn = *y;
6483     cl.ffIn = *x;
6484     cl.rtIn = -1;
6485     cl.ftIn = -1;
6486     cl.promoCharIn = NULLCHAR;
6487     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6488     if( cl.kind == NormalMove ||
6489         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6490         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6491         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6492       fromX = cl.ff;
6493       fromY = cl.rf;
6494       *x = cl.ft;
6495       *y = cl.rt;
6496       return TRUE;
6497     }
6498     if(cl.kind != ImpossibleMove) return FALSE;
6499     cl.pieceIn = EmptySquare;
6500     cl.rfIn = -1;
6501     cl.ffIn = -1;
6502     cl.rtIn = *y;
6503     cl.ftIn = *x;
6504     cl.promoCharIn = NULLCHAR;
6505     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6506     if( cl.kind == NormalMove ||
6507         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6508         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6509         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6510       fromX = cl.ff;
6511       fromY = cl.rf;
6512       *x = cl.ft;
6513       *y = cl.rt;
6514       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6515       return TRUE;
6516     }
6517     return FALSE;
6518 }
6519
6520 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6521 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6522 int lastLoadGameUseList = FALSE;
6523 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6524 ChessMove lastLoadGameStart = EndOfFile;
6525 int doubleClick;
6526
6527 void
6528 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6529 {
6530     ChessMove moveType;
6531     ChessSquare pdown, pup;
6532     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6533
6534
6535     /* Check if the user is playing in turn.  This is complicated because we
6536        let the user "pick up" a piece before it is his turn.  So the piece he
6537        tried to pick up may have been captured by the time he puts it down!
6538        Therefore we use the color the user is supposed to be playing in this
6539        test, not the color of the piece that is currently on the starting
6540        square---except in EditGame mode, where the user is playing both
6541        sides; fortunately there the capture race can't happen.  (It can
6542        now happen in IcsExamining mode, but that's just too bad.  The user
6543        will get a somewhat confusing message in that case.)
6544        */
6545
6546     switch (gameMode) {
6547       case AnalyzeFile:
6548       case TwoMachinesPlay:
6549       case EndOfGame:
6550       case IcsObserving:
6551       case IcsIdle:
6552         /* We switched into a game mode where moves are not accepted,
6553            perhaps while the mouse button was down. */
6554         return;
6555
6556       case MachinePlaysWhite:
6557         /* User is moving for Black */
6558         if (WhiteOnMove(currentMove)) {
6559             DisplayMoveError(_("It is White's turn"));
6560             return;
6561         }
6562         break;
6563
6564       case MachinePlaysBlack:
6565         /* User is moving for White */
6566         if (!WhiteOnMove(currentMove)) {
6567             DisplayMoveError(_("It is Black's turn"));
6568             return;
6569         }
6570         break;
6571
6572       case PlayFromGameFile:
6573             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6574       case EditGame:
6575       case IcsExamining:
6576       case BeginningOfGame:
6577       case AnalyzeMode:
6578       case Training:
6579         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6580         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6581             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6582             /* User is moving for Black */
6583             if (WhiteOnMove(currentMove)) {
6584                 DisplayMoveError(_("It is White's turn"));
6585                 return;
6586             }
6587         } else {
6588             /* User is moving for White */
6589             if (!WhiteOnMove(currentMove)) {
6590                 DisplayMoveError(_("It is Black's turn"));
6591                 return;
6592             }
6593         }
6594         break;
6595
6596       case IcsPlayingBlack:
6597         /* User is moving for Black */
6598         if (WhiteOnMove(currentMove)) {
6599             if (!appData.premove) {
6600                 DisplayMoveError(_("It is White's turn"));
6601             } else if (toX >= 0 && toY >= 0) {
6602                 premoveToX = toX;
6603                 premoveToY = toY;
6604                 premoveFromX = fromX;
6605                 premoveFromY = fromY;
6606                 premovePromoChar = promoChar;
6607                 gotPremove = 1;
6608                 if (appData.debugMode)
6609                     fprintf(debugFP, "Got premove: fromX %d,"
6610                             "fromY %d, toX %d, toY %d\n",
6611                             fromX, fromY, toX, toY);
6612             }
6613             return;
6614         }
6615         break;
6616
6617       case IcsPlayingWhite:
6618         /* User is moving for White */
6619         if (!WhiteOnMove(currentMove)) {
6620             if (!appData.premove) {
6621                 DisplayMoveError(_("It is Black's turn"));
6622             } else if (toX >= 0 && toY >= 0) {
6623                 premoveToX = toX;
6624                 premoveToY = toY;
6625                 premoveFromX = fromX;
6626                 premoveFromY = fromY;
6627                 premovePromoChar = promoChar;
6628                 gotPremove = 1;
6629                 if (appData.debugMode)
6630                     fprintf(debugFP, "Got premove: fromX %d,"
6631                             "fromY %d, toX %d, toY %d\n",
6632                             fromX, fromY, toX, toY);
6633             }
6634             return;
6635         }
6636         break;
6637
6638       default:
6639         break;
6640
6641       case EditPosition:
6642         /* EditPosition, empty square, or different color piece;
6643            click-click move is possible */
6644         if (toX == -2 || toY == -2) {
6645             boards[0][fromY][fromX] = EmptySquare;
6646             DrawPosition(FALSE, boards[currentMove]);
6647             return;
6648         } else if (toX >= 0 && toY >= 0) {
6649             boards[0][toY][toX] = boards[0][fromY][fromX];
6650             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6651                 if(boards[0][fromY][0] != EmptySquare) {
6652                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6653                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6654                 }
6655             } else
6656             if(fromX == BOARD_RGHT+1) {
6657                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6658                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6659                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6660                 }
6661             } else
6662             boards[0][fromY][fromX] = EmptySquare;
6663             DrawPosition(FALSE, boards[currentMove]);
6664             return;
6665         }
6666         return;
6667     }
6668
6669     if(toX < 0 || toY < 0) return;
6670     pdown = boards[currentMove][fromY][fromX];
6671     pup = boards[currentMove][toY][toX];
6672
6673     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6674     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6675          if( pup != EmptySquare ) return;
6676          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6677            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6678                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6679            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6680            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6681            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6682            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6683          fromY = DROP_RANK;
6684     }
6685
6686     /* [HGM] always test for legality, to get promotion info */
6687     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6688                                          fromY, fromX, toY, toX, promoChar);
6689
6690     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6691
6692     /* [HGM] but possibly ignore an IllegalMove result */
6693     if (appData.testLegality) {
6694         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6695             DisplayMoveError(_("Illegal move"));
6696             return;
6697         }
6698     }
6699
6700     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6701         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6702              ClearPremoveHighlights(); // was included
6703         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6704         return;
6705     }
6706
6707     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6708 }
6709
6710 /* Common tail of UserMoveEvent and DropMenuEvent */
6711 int
6712 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6713 {
6714     char *bookHit = 0;
6715
6716     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6717         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6718         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6719         if(WhiteOnMove(currentMove)) {
6720             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6721         } else {
6722             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6723         }
6724     }
6725
6726     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6727        move type in caller when we know the move is a legal promotion */
6728     if(moveType == NormalMove && promoChar)
6729         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6730
6731     /* [HGM] <popupFix> The following if has been moved here from
6732        UserMoveEvent(). Because it seemed to belong here (why not allow
6733        piece drops in training games?), and because it can only be
6734        performed after it is known to what we promote. */
6735     if (gameMode == Training) {
6736       /* compare the move played on the board to the next move in the
6737        * game. If they match, display the move and the opponent's response.
6738        * If they don't match, display an error message.
6739        */
6740       int saveAnimate;
6741       Board testBoard;
6742       CopyBoard(testBoard, boards[currentMove]);
6743       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6744
6745       if (CompareBoards(testBoard, boards[currentMove+1])) {
6746         ForwardInner(currentMove+1);
6747
6748         /* Autoplay the opponent's response.
6749          * if appData.animate was TRUE when Training mode was entered,
6750          * the response will be animated.
6751          */
6752         saveAnimate = appData.animate;
6753         appData.animate = animateTraining;
6754         ForwardInner(currentMove+1);
6755         appData.animate = saveAnimate;
6756
6757         /* check for the end of the game */
6758         if (currentMove >= forwardMostMove) {
6759           gameMode = PlayFromGameFile;
6760           ModeHighlight();
6761           SetTrainingModeOff();
6762           DisplayInformation(_("End of game"));
6763         }
6764       } else {
6765         DisplayError(_("Incorrect move"), 0);
6766       }
6767       return 1;
6768     }
6769
6770   /* Ok, now we know that the move is good, so we can kill
6771      the previous line in Analysis Mode */
6772   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6773                                 && currentMove < forwardMostMove) {
6774     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6775     else forwardMostMove = currentMove;
6776   }
6777
6778   ClearMap();
6779
6780   /* If we need the chess program but it's dead, restart it */
6781   ResurrectChessProgram();
6782
6783   /* A user move restarts a paused game*/
6784   if (pausing)
6785     PauseEvent();
6786
6787   thinkOutput[0] = NULLCHAR;
6788
6789   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6790
6791   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6792     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6793     return 1;
6794   }
6795
6796   if (gameMode == BeginningOfGame) {
6797     if (appData.noChessProgram) {
6798       gameMode = EditGame;
6799       SetGameInfo();
6800     } else {
6801       char buf[MSG_SIZ];
6802       gameMode = MachinePlaysBlack;
6803       StartClocks();
6804       SetGameInfo();
6805       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6806       DisplayTitle(buf);
6807       if (first.sendName) {
6808         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6809         SendToProgram(buf, &first);
6810       }
6811       StartClocks();
6812     }
6813     ModeHighlight();
6814   }
6815
6816   /* Relay move to ICS or chess engine */
6817   if (appData.icsActive) {
6818     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6819         gameMode == IcsExamining) {
6820       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6821         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6822         SendToICS("draw ");
6823         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6824       }
6825       // also send plain move, in case ICS does not understand atomic claims
6826       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6827       ics_user_moved = 1;
6828     }
6829   } else {
6830     if (first.sendTime && (gameMode == BeginningOfGame ||
6831                            gameMode == MachinePlaysWhite ||
6832                            gameMode == MachinePlaysBlack)) {
6833       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6834     }
6835     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6836          // [HGM] book: if program might be playing, let it use book
6837         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6838         first.maybeThinking = TRUE;
6839     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6840         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6841         SendBoard(&first, currentMove+1);
6842     } else SendMoveToProgram(forwardMostMove-1, &first);
6843     if (currentMove == cmailOldMove + 1) {
6844       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6845     }
6846   }
6847
6848   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6849
6850   switch (gameMode) {
6851   case EditGame:
6852     if(appData.testLegality)
6853     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6854     case MT_NONE:
6855     case MT_CHECK:
6856       break;
6857     case MT_CHECKMATE:
6858     case MT_STAINMATE:
6859       if (WhiteOnMove(currentMove)) {
6860         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6861       } else {
6862         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6863       }
6864       break;
6865     case MT_STALEMATE:
6866       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6867       break;
6868     }
6869     break;
6870
6871   case MachinePlaysBlack:
6872   case MachinePlaysWhite:
6873     /* disable certain menu options while machine is thinking */
6874     SetMachineThinkingEnables();
6875     break;
6876
6877   default:
6878     break;
6879   }
6880
6881   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6882   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6883
6884   if(bookHit) { // [HGM] book: simulate book reply
6885         static char bookMove[MSG_SIZ]; // a bit generous?
6886
6887         programStats.nodes = programStats.depth = programStats.time =
6888         programStats.score = programStats.got_only_move = 0;
6889         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6890
6891         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6892         strcat(bookMove, bookHit);
6893         HandleMachineMove(bookMove, &first);
6894   }
6895   return 1;
6896 }
6897
6898 void
6899 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6900 {
6901     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6902     Markers *m = (Markers *) closure;
6903     if(rf == fromY && ff == fromX)
6904         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6905                          || kind == WhiteCapturesEnPassant
6906                          || kind == BlackCapturesEnPassant);
6907     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6908 }
6909
6910 void
6911 MarkTargetSquares (int clear)
6912 {
6913   int x, y;
6914   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6915      !appData.testLegality || gameMode == EditPosition) return;
6916   if(clear) {
6917     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6918   } else {
6919     int capt = 0;
6920     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6921     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6922       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6923       if(capt)
6924       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6925     }
6926   }
6927   DrawPosition(TRUE, NULL);
6928 }
6929
6930 int
6931 Explode (Board board, int fromX, int fromY, int toX, int toY)
6932 {
6933     if(gameInfo.variant == VariantAtomic &&
6934        (board[toY][toX] != EmptySquare ||                     // capture?
6935         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6936                          board[fromY][fromX] == BlackPawn   )
6937       )) {
6938         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6939         return TRUE;
6940     }
6941     return FALSE;
6942 }
6943
6944 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6945
6946 int
6947 CanPromote (ChessSquare piece, int y)
6948 {
6949         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6950         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6951         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6952            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6953            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6954                                                   gameInfo.variant == VariantMakruk) return FALSE;
6955         return (piece == BlackPawn && y == 1 ||
6956                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6957                 piece == BlackLance && y == 1 ||
6958                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6959 }
6960
6961 void
6962 LeftClick (ClickType clickType, int xPix, int yPix)
6963 {
6964     int x, y;
6965     Boolean saveAnimate;
6966     static int second = 0, promotionChoice = 0, clearFlag = 0;
6967     char promoChoice = NULLCHAR;
6968     ChessSquare piece;
6969     static TimeMark lastClickTime, prevClickTime;
6970
6971     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6972
6973     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6974
6975     if (clickType == Press) ErrorPopDown();
6976
6977     x = EventToSquare(xPix, BOARD_WIDTH);
6978     y = EventToSquare(yPix, BOARD_HEIGHT);
6979     if (!flipView && y >= 0) {
6980         y = BOARD_HEIGHT - 1 - y;
6981     }
6982     if (flipView && x >= 0) {
6983         x = BOARD_WIDTH - 1 - x;
6984     }
6985
6986     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6987         defaultPromoChoice = promoSweep;
6988         promoSweep = EmptySquare;   // terminate sweep
6989         promoDefaultAltered = TRUE;
6990         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6991     }
6992
6993     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6994         if(clickType == Release) return; // ignore upclick of click-click destination
6995         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6996         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6997         if(gameInfo.holdingsWidth &&
6998                 (WhiteOnMove(currentMove)
6999                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7000                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7001             // click in right holdings, for determining promotion piece
7002             ChessSquare p = boards[currentMove][y][x];
7003             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7004             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7005             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7006                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7007                 fromX = fromY = -1;
7008                 return;
7009             }
7010         }
7011         DrawPosition(FALSE, boards[currentMove]);
7012         return;
7013     }
7014
7015     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7016     if(clickType == Press
7017             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7018               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7019               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7020         return;
7021
7022     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7023         // could be static click on premove from-square: abort premove
7024         gotPremove = 0;
7025         ClearPremoveHighlights();
7026     }
7027
7028     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7029         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7030
7031     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7032         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7033                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7034         defaultPromoChoice = DefaultPromoChoice(side);
7035     }
7036
7037     autoQueen = appData.alwaysPromoteToQueen;
7038
7039     if (fromX == -1) {
7040       int originalY = y;
7041       gatingPiece = EmptySquare;
7042       if (clickType != Press) {
7043         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7044             DragPieceEnd(xPix, yPix); dragging = 0;
7045             DrawPosition(FALSE, NULL);
7046         }
7047         return;
7048       }
7049       doubleClick = FALSE;
7050       fromX = x; fromY = y; toX = toY = -1;
7051       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7052          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7053          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7054             /* First square */
7055             if (OKToStartUserMove(fromX, fromY)) {
7056                 second = 0;
7057                 MarkTargetSquares(0);
7058                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7059                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7060                     promoSweep = defaultPromoChoice;
7061                     selectFlag = 0; lastX = xPix; lastY = yPix;
7062                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7063                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7064                 }
7065                 if (appData.highlightDragging) {
7066                     SetHighlights(fromX, fromY, -1, -1);
7067                 }
7068             } else fromX = fromY = -1;
7069             return;
7070         }
7071     }
7072
7073     /* fromX != -1 */
7074     if (clickType == Press && gameMode != EditPosition) {
7075         ChessSquare fromP;
7076         ChessSquare toP;
7077         int frc;
7078
7079         // ignore off-board to clicks
7080         if(y < 0 || x < 0) return;
7081
7082         /* Check if clicking again on the same color piece */
7083         fromP = boards[currentMove][fromY][fromX];
7084         toP = boards[currentMove][y][x];
7085         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7086         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7087              WhitePawn <= toP && toP <= WhiteKing &&
7088              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7089              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7090             (BlackPawn <= fromP && fromP <= BlackKing &&
7091              BlackPawn <= toP && toP <= BlackKing &&
7092              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7093              !(fromP == BlackKing && toP == BlackRook && frc))) {
7094             /* Clicked again on same color piece -- changed his mind */
7095             second = (x == fromX && y == fromY);
7096             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7097                 second = FALSE; // first double-click rather than scond click
7098                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7099             }
7100             promoDefaultAltered = FALSE;
7101             MarkTargetSquares(1);
7102            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7103             if (appData.highlightDragging) {
7104                 SetHighlights(x, y, -1, -1);
7105             } else {
7106                 ClearHighlights();
7107             }
7108             if (OKToStartUserMove(x, y)) {
7109                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7110                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7111                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7112                  gatingPiece = boards[currentMove][fromY][fromX];
7113                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7114                 fromX = x;
7115                 fromY = y; dragging = 1;
7116                 MarkTargetSquares(0);
7117                 DragPieceBegin(xPix, yPix, FALSE);
7118                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7119                     promoSweep = defaultPromoChoice;
7120                     selectFlag = 0; lastX = xPix; lastY = yPix;
7121                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7122                 }
7123             }
7124            }
7125            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7126            second = FALSE; 
7127         }
7128         // ignore clicks on holdings
7129         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7130     }
7131
7132     if (clickType == Release && x == fromX && y == fromY) {
7133         DragPieceEnd(xPix, yPix); dragging = 0;
7134         if(clearFlag) {
7135             // a deferred attempt to click-click move an empty square on top of a piece
7136             boards[currentMove][y][x] = EmptySquare;
7137             ClearHighlights();
7138             DrawPosition(FALSE, boards[currentMove]);
7139             fromX = fromY = -1; clearFlag = 0;
7140             return;
7141         }
7142         if (appData.animateDragging) {
7143             /* Undo animation damage if any */
7144             DrawPosition(FALSE, NULL);
7145         }
7146         if (second) {
7147             /* Second up/down in same square; just abort move */
7148             second = 0;
7149             fromX = fromY = -1;
7150             gatingPiece = EmptySquare;
7151             ClearHighlights();
7152             gotPremove = 0;
7153             ClearPremoveHighlights();
7154         } else {
7155             /* First upclick in same square; start click-click mode */
7156             SetHighlights(x, y, -1, -1);
7157         }
7158         return;
7159     }
7160
7161     clearFlag = 0;
7162
7163     /* we now have a different from- and (possibly off-board) to-square */
7164     /* Completed move */
7165     toX = x;
7166     toY = y;
7167     saveAnimate = appData.animate;
7168     MarkTargetSquares(1);
7169     if (clickType == Press) {
7170         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7171             // must be Edit Position mode with empty-square selected
7172             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7173             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7174             return;
7175         }
7176         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7177             ChessSquare piece = boards[currentMove][fromY][fromX];
7178             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7179             promoSweep = defaultPromoChoice;
7180             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7181             selectFlag = 0; lastX = xPix; lastY = yPix;
7182             Sweep(0); // Pawn that is going to promote: preview promotion piece
7183             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7184             DrawPosition(FALSE, boards[currentMove]);
7185             return;
7186         }
7187         /* Finish clickclick move */
7188         if (appData.animate || appData.highlightLastMove) {
7189             SetHighlights(fromX, fromY, toX, toY);
7190         } else {
7191             ClearHighlights();
7192         }
7193     } else {
7194         /* Finish drag move */
7195         if (appData.highlightLastMove) {
7196             SetHighlights(fromX, fromY, toX, toY);
7197         } else {
7198             ClearHighlights();
7199         }
7200         DragPieceEnd(xPix, yPix); dragging = 0;
7201         /* Don't animate move and drag both */
7202         appData.animate = FALSE;
7203     }
7204
7205     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7206     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7207         ChessSquare piece = boards[currentMove][fromY][fromX];
7208         if(gameMode == EditPosition && piece != EmptySquare &&
7209            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7210             int n;
7211
7212             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7213                 n = PieceToNumber(piece - (int)BlackPawn);
7214                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7215                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7216                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7217             } else
7218             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7219                 n = PieceToNumber(piece);
7220                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7221                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7222                 boards[currentMove][n][BOARD_WIDTH-2]++;
7223             }
7224             boards[currentMove][fromY][fromX] = EmptySquare;
7225         }
7226         ClearHighlights();
7227         fromX = fromY = -1;
7228         DrawPosition(TRUE, boards[currentMove]);
7229         return;
7230     }
7231
7232     // off-board moves should not be highlighted
7233     if(x < 0 || y < 0) ClearHighlights();
7234
7235     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7236
7237     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7238         SetHighlights(fromX, fromY, toX, toY);
7239         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7240             // [HGM] super: promotion to captured piece selected from holdings
7241             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7242             promotionChoice = TRUE;
7243             // kludge follows to temporarily execute move on display, without promoting yet
7244             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7245             boards[currentMove][toY][toX] = p;
7246             DrawPosition(FALSE, boards[currentMove]);
7247             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7248             boards[currentMove][toY][toX] = q;
7249             DisplayMessage("Click in holdings to choose piece", "");
7250             return;
7251         }
7252         PromotionPopUp();
7253     } else {
7254         int oldMove = currentMove;
7255         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7256         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7257         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7258         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7259            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7260             DrawPosition(TRUE, boards[currentMove]);
7261         fromX = fromY = -1;
7262     }
7263     appData.animate = saveAnimate;
7264     if (appData.animate || appData.animateDragging) {
7265         /* Undo animation damage if needed */
7266         DrawPosition(FALSE, NULL);
7267     }
7268 }
7269
7270 int
7271 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7272 {   // front-end-free part taken out of PieceMenuPopup
7273     int whichMenu; int xSqr, ySqr;
7274
7275     if(seekGraphUp) { // [HGM] seekgraph
7276         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7277         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7278         return -2;
7279     }
7280
7281     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7282          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7283         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7284         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7285         if(action == Press)   {
7286             originalFlip = flipView;
7287             flipView = !flipView; // temporarily flip board to see game from partners perspective
7288             DrawPosition(TRUE, partnerBoard);
7289             DisplayMessage(partnerStatus, "");
7290             partnerUp = TRUE;
7291         } else if(action == Release) {
7292             flipView = originalFlip;
7293             DrawPosition(TRUE, boards[currentMove]);
7294             partnerUp = FALSE;
7295         }
7296         return -2;
7297     }
7298
7299     xSqr = EventToSquare(x, BOARD_WIDTH);
7300     ySqr = EventToSquare(y, BOARD_HEIGHT);
7301     if (action == Release) {
7302         if(pieceSweep != EmptySquare) {
7303             EditPositionMenuEvent(pieceSweep, toX, toY);
7304             pieceSweep = EmptySquare;
7305         } else UnLoadPV(); // [HGM] pv
7306     }
7307     if (action != Press) return -2; // return code to be ignored
7308     switch (gameMode) {
7309       case IcsExamining:
7310         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7311       case EditPosition:
7312         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7313         if (xSqr < 0 || ySqr < 0) return -1;
7314         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7315         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7316         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7317         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7318         NextPiece(0);
7319         return 2; // grab
7320       case IcsObserving:
7321         if(!appData.icsEngineAnalyze) return -1;
7322       case IcsPlayingWhite:
7323       case IcsPlayingBlack:
7324         if(!appData.zippyPlay) goto noZip;
7325       case AnalyzeMode:
7326       case AnalyzeFile:
7327       case MachinePlaysWhite:
7328       case MachinePlaysBlack:
7329       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7330         if (!appData.dropMenu) {
7331           LoadPV(x, y);
7332           return 2; // flag front-end to grab mouse events
7333         }
7334         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7335            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7336       case EditGame:
7337       noZip:
7338         if (xSqr < 0 || ySqr < 0) return -1;
7339         if (!appData.dropMenu || appData.testLegality &&
7340             gameInfo.variant != VariantBughouse &&
7341             gameInfo.variant != VariantCrazyhouse) return -1;
7342         whichMenu = 1; // drop menu
7343         break;
7344       default:
7345         return -1;
7346     }
7347
7348     if (((*fromX = xSqr) < 0) ||
7349         ((*fromY = ySqr) < 0)) {
7350         *fromX = *fromY = -1;
7351         return -1;
7352     }
7353     if (flipView)
7354       *fromX = BOARD_WIDTH - 1 - *fromX;
7355     else
7356       *fromY = BOARD_HEIGHT - 1 - *fromY;
7357
7358     return whichMenu;
7359 }
7360
7361 void
7362 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7363 {
7364 //    char * hint = lastHint;
7365     FrontEndProgramStats stats;
7366
7367     stats.which = cps == &first ? 0 : 1;
7368     stats.depth = cpstats->depth;
7369     stats.nodes = cpstats->nodes;
7370     stats.score = cpstats->score;
7371     stats.time = cpstats->time;
7372     stats.pv = cpstats->movelist;
7373     stats.hint = lastHint;
7374     stats.an_move_index = 0;
7375     stats.an_move_count = 0;
7376
7377     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7378         stats.hint = cpstats->move_name;
7379         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7380         stats.an_move_count = cpstats->nr_moves;
7381     }
7382
7383     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
7384
7385     SetProgramStats( &stats );
7386 }
7387
7388 void
7389 ClearEngineOutputPane (int which)
7390 {
7391     static FrontEndProgramStats dummyStats;
7392     dummyStats.which = which;
7393     dummyStats.pv = "#";
7394     SetProgramStats( &dummyStats );
7395 }
7396
7397 #define MAXPLAYERS 500
7398
7399 char *
7400 TourneyStandings (int display)
7401 {
7402     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7403     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7404     char result, *p, *names[MAXPLAYERS];
7405
7406     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7407         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7408     names[0] = p = strdup(appData.participants);
7409     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7410
7411     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7412
7413     while(result = appData.results[nr]) {
7414         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7415         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7416         wScore = bScore = 0;
7417         switch(result) {
7418           case '+': wScore = 2; break;
7419           case '-': bScore = 2; break;
7420           case '=': wScore = bScore = 1; break;
7421           case ' ':
7422           case '*': return strdup("busy"); // tourney not finished
7423         }
7424         score[w] += wScore;
7425         score[b] += bScore;
7426         games[w]++;
7427         games[b]++;
7428         nr++;
7429     }
7430     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7431     for(w=0; w<nPlayers; w++) {
7432         bScore = -1;
7433         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7434         ranking[w] = b; points[w] = bScore; score[b] = -2;
7435     }
7436     p = malloc(nPlayers*34+1);
7437     for(w=0; w<nPlayers && w<display; w++)
7438         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7439     free(names[0]);
7440     return p;
7441 }
7442
7443 void
7444 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7445 {       // count all piece types
7446         int p, f, r;
7447         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7448         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7449         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7450                 p = board[r][f];
7451                 pCnt[p]++;
7452                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7453                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7454                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7455                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7456                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7457                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7458         }
7459 }
7460
7461 int
7462 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7463 {
7464         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7465         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7466
7467         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7468         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7469         if(myPawns == 2 && nMine == 3) // KPP
7470             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7471         if(myPawns == 1 && nMine == 2) // KP
7472             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7473         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7474             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7475         if(myPawns) return FALSE;
7476         if(pCnt[WhiteRook+side])
7477             return pCnt[BlackRook-side] ||
7478                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7479                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7480                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7481         if(pCnt[WhiteCannon+side]) {
7482             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7483             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7484         }
7485         if(pCnt[WhiteKnight+side])
7486             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7487         return FALSE;
7488 }
7489
7490 int
7491 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7492 {
7493         VariantClass v = gameInfo.variant;
7494
7495         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7496         if(v == VariantShatranj) return TRUE; // always winnable through baring
7497         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7498         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7499
7500         if(v == VariantXiangqi) {
7501                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7502
7503                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7504                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7505                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7506                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7507                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7508                 if(stale) // we have at least one last-rank P plus perhaps C
7509                     return majors // KPKX
7510                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7511                 else // KCA*E*
7512                     return pCnt[WhiteFerz+side] // KCAK
7513                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7514                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7515                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7516
7517         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7518                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7519
7520                 if(nMine == 1) return FALSE; // bare King
7521                 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
7522                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7523                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7524                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7525                 if(pCnt[WhiteKnight+side])
7526                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7527                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7528                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7529                 if(nBishops)
7530                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7531                 if(pCnt[WhiteAlfil+side])
7532                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7533                 if(pCnt[WhiteWazir+side])
7534                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7535         }
7536
7537         return TRUE;
7538 }
7539
7540 int
7541 CompareWithRights (Board b1, Board b2)
7542 {
7543     int rights = 0;
7544     if(!CompareBoards(b1, b2)) return FALSE;
7545     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7546     /* compare castling rights */
7547     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7548            rights++; /* King lost rights, while rook still had them */
7549     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7550         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7551            rights++; /* but at least one rook lost them */
7552     }
7553     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7554            rights++;
7555     if( b1[CASTLING][5] != NoRights ) {
7556         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7557            rights++;
7558     }
7559     return rights == 0;
7560 }
7561
7562 int
7563 Adjudicate (ChessProgramState *cps)
7564 {       // [HGM] some adjudications useful with buggy engines
7565         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7566         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7567         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7568         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7569         int k, count = 0; static int bare = 1;
7570         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7571         Boolean canAdjudicate = !appData.icsActive;
7572
7573         // most tests only when we understand the game, i.e. legality-checking on
7574             if( appData.testLegality )
7575             {   /* [HGM] Some more adjudications for obstinate engines */
7576                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7577                 static int moveCount = 6;
7578                 ChessMove result;
7579                 char *reason = NULL;
7580
7581                 /* Count what is on board. */
7582                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7583
7584                 /* Some material-based adjudications that have to be made before stalemate test */
7585                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7586                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7587                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7588                      if(canAdjudicate && appData.checkMates) {
7589                          if(engineOpponent)
7590                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7591                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7592                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7593                          return 1;
7594                      }
7595                 }
7596
7597                 /* Bare King in Shatranj (loses) or Losers (wins) */
7598                 if( nrW == 1 || nrB == 1) {
7599                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7600                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7601                      if(canAdjudicate && appData.checkMates) {
7602                          if(engineOpponent)
7603                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7604                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7605                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7606                          return 1;
7607                      }
7608                   } else
7609                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7610                   {    /* bare King */
7611                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7612                         if(canAdjudicate && appData.checkMates) {
7613                             /* but only adjudicate if adjudication enabled */
7614                             if(engineOpponent)
7615                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7616                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7617                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7618                             return 1;
7619                         }
7620                   }
7621                 } else bare = 1;
7622
7623
7624             // don't wait for engine to announce game end if we can judge ourselves
7625             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7626               case MT_CHECK:
7627                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7628                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7629                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7630                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7631                             checkCnt++;
7632                         if(checkCnt >= 2) {
7633                             reason = "Xboard adjudication: 3rd check";
7634                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7635                             break;
7636                         }
7637                     }
7638                 }
7639               case MT_NONE:
7640               default:
7641                 break;
7642               case MT_STALEMATE:
7643               case MT_STAINMATE:
7644                 reason = "Xboard adjudication: Stalemate";
7645                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7646                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7647                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7648                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7649                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7650                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7651                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7652                                                                         EP_CHECKMATE : EP_WINS);
7653                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7654                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7655                 }
7656                 break;
7657               case MT_CHECKMATE:
7658                 reason = "Xboard adjudication: Checkmate";
7659                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7660                 break;
7661             }
7662
7663                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7664                     case EP_STALEMATE:
7665                         result = GameIsDrawn; break;
7666                     case EP_CHECKMATE:
7667                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7668                     case EP_WINS:
7669                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7670                     default:
7671                         result = EndOfFile;
7672                 }
7673                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7674                     if(engineOpponent)
7675                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7676                     GameEnds( result, reason, GE_XBOARD );
7677                     return 1;
7678                 }
7679
7680                 /* Next absolutely insufficient mating material. */
7681                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7682                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7683                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7684
7685                      /* always flag draws, for judging claims */
7686                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7687
7688                      if(canAdjudicate && appData.materialDraws) {
7689                          /* but only adjudicate them if adjudication enabled */
7690                          if(engineOpponent) {
7691                            SendToProgram("force\n", engineOpponent); // suppress reply
7692                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7693                          }
7694                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7695                          return 1;
7696                      }
7697                 }
7698
7699                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7700                 if(gameInfo.variant == VariantXiangqi ?
7701                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7702                  : nrW + nrB == 4 &&
7703                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7704                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7705                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7706                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7707                    ) ) {
7708                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7709                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7710                           if(engineOpponent) {
7711                             SendToProgram("force\n", engineOpponent); // suppress reply
7712                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7713                           }
7714                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7715                           return 1;
7716                      }
7717                 } else moveCount = 6;
7718             }
7719
7720         // Repetition draws and 50-move rule can be applied independently of legality testing
7721
7722                 /* Check for rep-draws */
7723                 count = 0;
7724                 for(k = forwardMostMove-2;
7725                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7726                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7727                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7728                     k-=2)
7729                 {   int rights=0;
7730                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7731                         /* compare castling rights */
7732                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7733                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7734                                 rights++; /* King lost rights, while rook still had them */
7735                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7736                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7737                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7738                                    rights++; /* but at least one rook lost them */
7739                         }
7740                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7741                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7742                                 rights++;
7743                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7744                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7745                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7746                                    rights++;
7747                         }
7748                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7749                             && appData.drawRepeats > 1) {
7750                              /* adjudicate after user-specified nr of repeats */
7751                              int result = GameIsDrawn;
7752                              char *details = "XBoard adjudication: repetition draw";
7753                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7754                                 // [HGM] xiangqi: check for forbidden perpetuals
7755                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7756                                 for(m=forwardMostMove; m>k; m-=2) {
7757                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7758                                         ourPerpetual = 0; // the current mover did not always check
7759                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7760                                         hisPerpetual = 0; // the opponent did not always check
7761                                 }
7762                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7763                                                                         ourPerpetual, hisPerpetual);
7764                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7765                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7766                                     details = "Xboard adjudication: perpetual checking";
7767                                 } else
7768                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7769                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7770                                 } else
7771                                 // Now check for perpetual chases
7772                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7773                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7774                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7775                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7776                                         static char resdet[MSG_SIZ];
7777                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7778                                         details = resdet;
7779                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7780                                     } else
7781                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7782                                         break; // Abort repetition-checking loop.
7783                                 }
7784                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7785                              }
7786                              if(engineOpponent) {
7787                                SendToProgram("force\n", engineOpponent); // suppress reply
7788                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7789                              }
7790                              GameEnds( result, details, GE_XBOARD );
7791                              return 1;
7792                         }
7793                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7794                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7795                     }
7796                 }
7797
7798                 /* Now we test for 50-move draws. Determine ply count */
7799                 count = forwardMostMove;
7800                 /* look for last irreversble move */
7801                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7802                     count--;
7803                 /* if we hit starting position, add initial plies */
7804                 if( count == backwardMostMove )
7805                     count -= initialRulePlies;
7806                 count = forwardMostMove - count;
7807                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7808                         // adjust reversible move counter for checks in Xiangqi
7809                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7810                         if(i < backwardMostMove) i = backwardMostMove;
7811                         while(i <= forwardMostMove) {
7812                                 lastCheck = inCheck; // check evasion does not count
7813                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7814                                 if(inCheck || lastCheck) count--; // check does not count
7815                                 i++;
7816                         }
7817                 }
7818                 if( count >= 100)
7819                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7820                          /* this is used to judge if draw claims are legal */
7821                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7822                          if(engineOpponent) {
7823                            SendToProgram("force\n", engineOpponent); // suppress reply
7824                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7825                          }
7826                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7827                          return 1;
7828                 }
7829
7830                 /* if draw offer is pending, treat it as a draw claim
7831                  * when draw condition present, to allow engines a way to
7832                  * claim draws before making their move to avoid a race
7833                  * condition occurring after their move
7834                  */
7835                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7836                          char *p = NULL;
7837                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7838                              p = "Draw claim: 50-move rule";
7839                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7840                              p = "Draw claim: 3-fold repetition";
7841                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7842                              p = "Draw claim: insufficient mating material";
7843                          if( p != NULL && canAdjudicate) {
7844                              if(engineOpponent) {
7845                                SendToProgram("force\n", engineOpponent); // suppress reply
7846                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7847                              }
7848                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7849                              return 1;
7850                          }
7851                 }
7852
7853                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7854                     if(engineOpponent) {
7855                       SendToProgram("force\n", engineOpponent); // suppress reply
7856                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7857                     }
7858                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7859                     return 1;
7860                 }
7861         return 0;
7862 }
7863
7864 char *
7865 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7866 {   // [HGM] book: this routine intercepts moves to simulate book replies
7867     char *bookHit = NULL;
7868
7869     //first determine if the incoming move brings opponent into his book
7870     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7871         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7872     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7873     if(bookHit != NULL && !cps->bookSuspend) {
7874         // make sure opponent is not going to reply after receiving move to book position
7875         SendToProgram("force\n", cps);
7876         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7877     }
7878     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7879     // now arrange restart after book miss
7880     if(bookHit) {
7881         // after a book hit we never send 'go', and the code after the call to this routine
7882         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7883         char buf[MSG_SIZ], *move = bookHit;
7884         if(cps->useSAN) {
7885             int fromX, fromY, toX, toY;
7886             char promoChar;
7887             ChessMove moveType;
7888             move = buf + 30;
7889             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7890                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7891                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7892                                     PosFlags(forwardMostMove),
7893                                     fromY, fromX, toY, toX, promoChar, move);
7894             } else {
7895                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7896                 bookHit = NULL;
7897             }
7898         }
7899         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7900         SendToProgram(buf, cps);
7901         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7902     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7903         SendToProgram("go\n", cps);
7904         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7905     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7906         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7907             SendToProgram("go\n", cps);
7908         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7909     }
7910     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7911 }
7912
7913 char *savedMessage;
7914 ChessProgramState *savedState;
7915 void
7916 DeferredBookMove (void)
7917 {
7918         if(savedState->lastPing != savedState->lastPong)
7919                     ScheduleDelayedEvent(DeferredBookMove, 10);
7920         else
7921         HandleMachineMove(savedMessage, savedState);
7922 }
7923
7924 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7925
7926 void
7927 HandleMachineMove (char *message, ChessProgramState *cps)
7928 {
7929     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7930     char realname[MSG_SIZ];
7931     int fromX, fromY, toX, toY;
7932     ChessMove moveType;
7933     char promoChar;
7934     char *p, *pv=buf1;
7935     int machineWhite;
7936     char *bookHit;
7937
7938     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7939         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7940         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7941             DisplayError(_("Invalid pairing from pairing engine"), 0);
7942             return;
7943         }
7944         pairingReceived = 1;
7945         NextMatchGame();
7946         return; // Skim the pairing messages here.
7947     }
7948
7949     cps->userError = 0;
7950
7951 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7952     /*
7953      * Kludge to ignore BEL characters
7954      */
7955     while (*message == '\007') message++;
7956
7957     /*
7958      * [HGM] engine debug message: ignore lines starting with '#' character
7959      */
7960     if(cps->debug && *message == '#') return;
7961
7962     /*
7963      * Look for book output
7964      */
7965     if (cps == &first && bookRequested) {
7966         if (message[0] == '\t' || message[0] == ' ') {
7967             /* Part of the book output is here; append it */
7968             strcat(bookOutput, message);
7969             strcat(bookOutput, "  \n");
7970             return;
7971         } else if (bookOutput[0] != NULLCHAR) {
7972             /* All of book output has arrived; display it */
7973             char *p = bookOutput;
7974             while (*p != NULLCHAR) {
7975                 if (*p == '\t') *p = ' ';
7976                 p++;
7977             }
7978             DisplayInformation(bookOutput);
7979             bookRequested = FALSE;
7980             /* Fall through to parse the current output */
7981         }
7982     }
7983
7984     /*
7985      * Look for machine move.
7986      */
7987     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7988         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7989     {
7990         /* This method is only useful on engines that support ping */
7991         if (cps->lastPing != cps->lastPong) {
7992           if (gameMode == BeginningOfGame) {
7993             /* Extra move from before last new; ignore */
7994             if (appData.debugMode) {
7995                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7996             }
7997           } else {
7998             if (appData.debugMode) {
7999                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8000                         cps->which, gameMode);
8001             }
8002
8003             SendToProgram("undo\n", cps);
8004           }
8005           return;
8006         }
8007
8008         switch (gameMode) {
8009           case BeginningOfGame:
8010             /* Extra move from before last reset; ignore */
8011             if (appData.debugMode) {
8012                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8013             }
8014             return;
8015
8016           case EndOfGame:
8017           case IcsIdle:
8018           default:
8019             /* Extra move after we tried to stop.  The mode test is
8020                not a reliable way of detecting this problem, but it's
8021                the best we can do on engines that don't support ping.
8022             */
8023             if (appData.debugMode) {
8024                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8025                         cps->which, gameMode);
8026             }
8027             SendToProgram("undo\n", cps);
8028             return;
8029
8030           case MachinePlaysWhite:
8031           case IcsPlayingWhite:
8032             machineWhite = TRUE;
8033             break;
8034
8035           case MachinePlaysBlack:
8036           case IcsPlayingBlack:
8037             machineWhite = FALSE;
8038             break;
8039
8040           case TwoMachinesPlay:
8041             machineWhite = (cps->twoMachinesColor[0] == 'w');
8042             break;
8043         }
8044         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8045             if (appData.debugMode) {
8046                 fprintf(debugFP,
8047                         "Ignoring move out of turn by %s, gameMode %d"
8048                         ", forwardMost %d\n",
8049                         cps->which, gameMode, forwardMostMove);
8050             }
8051             return;
8052         }
8053
8054         if(cps->alphaRank) AlphaRank(machineMove, 4);
8055         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8056                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8057             /* Machine move could not be parsed; ignore it. */
8058           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8059                     machineMove, _(cps->which));
8060             DisplayError(buf1, 0);
8061             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8062                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8063             if (gameMode == TwoMachinesPlay) {
8064               GameEnds(machineWhite ? BlackWins : WhiteWins,
8065                        buf1, GE_XBOARD);
8066             }
8067             return;
8068         }
8069
8070         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8071         /* So we have to redo legality test with true e.p. status here,  */
8072         /* to make sure an illegal e.p. capture does not slip through,   */
8073         /* to cause a forfeit on a justified illegal-move complaint      */
8074         /* of the opponent.                                              */
8075         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8076            ChessMove moveType;
8077            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8078                              fromY, fromX, toY, toX, promoChar);
8079             if(moveType == IllegalMove) {
8080               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8081                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8082                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8083                            buf1, GE_XBOARD);
8084                 return;
8085            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8086            /* [HGM] Kludge to handle engines that send FRC-style castling
8087               when they shouldn't (like TSCP-Gothic) */
8088            switch(moveType) {
8089              case WhiteASideCastleFR:
8090              case BlackASideCastleFR:
8091                toX+=2;
8092                currentMoveString[2]++;
8093                break;
8094              case WhiteHSideCastleFR:
8095              case BlackHSideCastleFR:
8096                toX--;
8097                currentMoveString[2]--;
8098                break;
8099              default: ; // nothing to do, but suppresses warning of pedantic compilers
8100            }
8101         }
8102         hintRequested = FALSE;
8103         lastHint[0] = NULLCHAR;
8104         bookRequested = FALSE;
8105         /* Program may be pondering now */
8106         cps->maybeThinking = TRUE;
8107         if (cps->sendTime == 2) cps->sendTime = 1;
8108         if (cps->offeredDraw) cps->offeredDraw--;
8109
8110         /* [AS] Save move info*/
8111         pvInfoList[ forwardMostMove ].score = programStats.score;
8112         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8113         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8114
8115         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8116
8117         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8118         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8119             int count = 0;
8120
8121             while( count < adjudicateLossPlies ) {
8122                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8123
8124                 if( count & 1 ) {
8125                     score = -score; /* Flip score for winning side */
8126                 }
8127
8128                 if( score > adjudicateLossThreshold ) {
8129                     break;
8130                 }
8131
8132                 count++;
8133             }
8134
8135             if( count >= adjudicateLossPlies ) {
8136                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8137
8138                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8139                     "Xboard adjudication",
8140                     GE_XBOARD );
8141
8142                 return;
8143             }
8144         }
8145
8146         if(Adjudicate(cps)) {
8147             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8148             return; // [HGM] adjudicate: for all automatic game ends
8149         }
8150
8151 #if ZIPPY
8152         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8153             first.initDone) {
8154           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8155                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8156                 SendToICS("draw ");
8157                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8158           }
8159           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8160           ics_user_moved = 1;
8161           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8162                 char buf[3*MSG_SIZ];
8163
8164                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8165                         programStats.score / 100.,
8166                         programStats.depth,
8167                         programStats.time / 100.,
8168                         (unsigned int)programStats.nodes,
8169                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8170                         programStats.movelist);
8171                 SendToICS(buf);
8172 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8173           }
8174         }
8175 #endif
8176
8177         /* [AS] Clear stats for next move */
8178         ClearProgramStats();
8179         thinkOutput[0] = NULLCHAR;
8180         hiddenThinkOutputState = 0;
8181
8182         bookHit = NULL;
8183         if (gameMode == TwoMachinesPlay) {
8184             /* [HGM] relaying draw offers moved to after reception of move */
8185             /* and interpreting offer as claim if it brings draw condition */
8186             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8187                 SendToProgram("draw\n", cps->other);
8188             }
8189             if (cps->other->sendTime) {
8190                 SendTimeRemaining(cps->other,
8191                                   cps->other->twoMachinesColor[0] == 'w');
8192             }
8193             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8194             if (firstMove && !bookHit) {
8195                 firstMove = FALSE;
8196                 if (cps->other->useColors) {
8197                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8198                 }
8199                 SendToProgram("go\n", cps->other);
8200             }
8201             cps->other->maybeThinking = TRUE;
8202         }
8203
8204         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8205
8206         if (!pausing && appData.ringBellAfterMoves) {
8207             RingBell();
8208         }
8209
8210         /*
8211          * Reenable menu items that were disabled while
8212          * machine was thinking
8213          */
8214         if (gameMode != TwoMachinesPlay)
8215             SetUserThinkingEnables();
8216
8217         // [HGM] book: after book hit opponent has received move and is now in force mode
8218         // force the book reply into it, and then fake that it outputted this move by jumping
8219         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8220         if(bookHit) {
8221                 static char bookMove[MSG_SIZ]; // a bit generous?
8222
8223                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8224                 strcat(bookMove, bookHit);
8225                 message = bookMove;
8226                 cps = cps->other;
8227                 programStats.nodes = programStats.depth = programStats.time =
8228                 programStats.score = programStats.got_only_move = 0;
8229                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8230
8231                 if(cps->lastPing != cps->lastPong) {
8232                     savedMessage = message; // args for deferred call
8233                     savedState = cps;
8234                     ScheduleDelayedEvent(DeferredBookMove, 10);
8235                     return;
8236                 }
8237                 goto FakeBookMove;
8238         }
8239
8240         return;
8241     }
8242
8243     /* Set special modes for chess engines.  Later something general
8244      *  could be added here; for now there is just one kludge feature,
8245      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8246      *  when "xboard" is given as an interactive command.
8247      */
8248     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8249         cps->useSigint = FALSE;
8250         cps->useSigterm = FALSE;
8251     }
8252     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8253       ParseFeatures(message+8, cps);
8254       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8255     }
8256
8257     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8258                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8259       int dummy, s=6; char buf[MSG_SIZ];
8260       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8261       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8262       if(startedFromSetupPosition) return;
8263       ParseFEN(boards[0], &dummy, message+s);
8264       DrawPosition(TRUE, boards[0]);
8265       startedFromSetupPosition = TRUE;
8266       return;
8267     }
8268     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8269      * want this, I was asked to put it in, and obliged.
8270      */
8271     if (!strncmp(message, "setboard ", 9)) {
8272         Board initial_position;
8273
8274         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8275
8276         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8277             DisplayError(_("Bad FEN received from engine"), 0);
8278             return ;
8279         } else {
8280            Reset(TRUE, FALSE);
8281            CopyBoard(boards[0], initial_position);
8282            initialRulePlies = FENrulePlies;
8283            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8284            else gameMode = MachinePlaysBlack;
8285            DrawPosition(FALSE, boards[currentMove]);
8286         }
8287         return;
8288     }
8289
8290     /*
8291      * Look for communication commands
8292      */
8293     if (!strncmp(message, "telluser ", 9)) {
8294         if(message[9] == '\\' && message[10] == '\\')
8295             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8296         PlayTellSound();
8297         DisplayNote(message + 9);
8298         return;
8299     }
8300     if (!strncmp(message, "tellusererror ", 14)) {
8301         cps->userError = 1;
8302         if(message[14] == '\\' && message[15] == '\\')
8303             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8304         PlayTellSound();
8305         DisplayError(message + 14, 0);
8306         return;
8307     }
8308     if (!strncmp(message, "tellopponent ", 13)) {
8309       if (appData.icsActive) {
8310         if (loggedOn) {
8311           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8312           SendToICS(buf1);
8313         }
8314       } else {
8315         DisplayNote(message + 13);
8316       }
8317       return;
8318     }
8319     if (!strncmp(message, "tellothers ", 11)) {
8320       if (appData.icsActive) {
8321         if (loggedOn) {
8322           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8323           SendToICS(buf1);
8324         }
8325       }
8326       return;
8327     }
8328     if (!strncmp(message, "tellall ", 8)) {
8329       if (appData.icsActive) {
8330         if (loggedOn) {
8331           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8332           SendToICS(buf1);
8333         }
8334       } else {
8335         DisplayNote(message + 8);
8336       }
8337       return;
8338     }
8339     if (strncmp(message, "warning", 7) == 0) {
8340         /* Undocumented feature, use tellusererror in new code */
8341         DisplayError(message, 0);
8342         return;
8343     }
8344     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8345         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8346         strcat(realname, " query");
8347         AskQuestion(realname, buf2, buf1, cps->pr);
8348         return;
8349     }
8350     /* Commands from the engine directly to ICS.  We don't allow these to be
8351      *  sent until we are logged on. Crafty kibitzes have been known to
8352      *  interfere with the login process.
8353      */
8354     if (loggedOn) {
8355         if (!strncmp(message, "tellics ", 8)) {
8356             SendToICS(message + 8);
8357             SendToICS("\n");
8358             return;
8359         }
8360         if (!strncmp(message, "tellicsnoalias ", 15)) {
8361             SendToICS(ics_prefix);
8362             SendToICS(message + 15);
8363             SendToICS("\n");
8364             return;
8365         }
8366         /* The following are for backward compatibility only */
8367         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8368             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8369             SendToICS(ics_prefix);
8370             SendToICS(message);
8371             SendToICS("\n");
8372             return;
8373         }
8374     }
8375     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8376         return;
8377     }
8378     /*
8379      * If the move is illegal, cancel it and redraw the board.
8380      * Also deal with other error cases.  Matching is rather loose
8381      * here to accommodate engines written before the spec.
8382      */
8383     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8384         strncmp(message, "Error", 5) == 0) {
8385         if (StrStr(message, "name") ||
8386             StrStr(message, "rating") || StrStr(message, "?") ||
8387             StrStr(message, "result") || StrStr(message, "board") ||
8388             StrStr(message, "bk") || StrStr(message, "computer") ||
8389             StrStr(message, "variant") || StrStr(message, "hint") ||
8390             StrStr(message, "random") || StrStr(message, "depth") ||
8391             StrStr(message, "accepted")) {
8392             return;
8393         }
8394         if (StrStr(message, "protover")) {
8395           /* Program is responding to input, so it's apparently done
8396              initializing, and this error message indicates it is
8397              protocol version 1.  So we don't need to wait any longer
8398              for it to initialize and send feature commands. */
8399           FeatureDone(cps, 1);
8400           cps->protocolVersion = 1;
8401           return;
8402         }
8403         cps->maybeThinking = FALSE;
8404
8405         if (StrStr(message, "draw")) {
8406             /* Program doesn't have "draw" command */
8407             cps->sendDrawOffers = 0;
8408             return;
8409         }
8410         if (cps->sendTime != 1 &&
8411             (StrStr(message, "time") || StrStr(message, "otim"))) {
8412           /* Program apparently doesn't have "time" or "otim" command */
8413           cps->sendTime = 0;
8414           return;
8415         }
8416         if (StrStr(message, "analyze")) {
8417             cps->analysisSupport = FALSE;
8418             cps->analyzing = FALSE;
8419 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8420             EditGameEvent(); // [HGM] try to preserve loaded game
8421             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8422             DisplayError(buf2, 0);
8423             return;
8424         }
8425         if (StrStr(message, "(no matching move)st")) {
8426           /* Special kludge for GNU Chess 4 only */
8427           cps->stKludge = TRUE;
8428           SendTimeControl(cps, movesPerSession, timeControl,
8429                           timeIncrement, appData.searchDepth,
8430                           searchTime);
8431           return;
8432         }
8433         if (StrStr(message, "(no matching move)sd")) {
8434           /* Special kludge for GNU Chess 4 only */
8435           cps->sdKludge = TRUE;
8436           SendTimeControl(cps, movesPerSession, timeControl,
8437                           timeIncrement, appData.searchDepth,
8438                           searchTime);
8439           return;
8440         }
8441         if (!StrStr(message, "llegal")) {
8442             return;
8443         }
8444         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8445             gameMode == IcsIdle) return;
8446         if (forwardMostMove <= backwardMostMove) return;
8447         if (pausing) PauseEvent();
8448       if(appData.forceIllegal) {
8449             // [HGM] illegal: machine refused move; force position after move into it
8450           SendToProgram("force\n", cps);
8451           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8452                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8453                 // when black is to move, while there might be nothing on a2 or black
8454                 // might already have the move. So send the board as if white has the move.
8455                 // But first we must change the stm of the engine, as it refused the last move
8456                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8457                 if(WhiteOnMove(forwardMostMove)) {
8458                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8459                     SendBoard(cps, forwardMostMove); // kludgeless board
8460                 } else {
8461                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8462                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8463                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8464                 }
8465           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8466             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8467                  gameMode == TwoMachinesPlay)
8468               SendToProgram("go\n", cps);
8469             return;
8470       } else
8471         if (gameMode == PlayFromGameFile) {
8472             /* Stop reading this game file */
8473             gameMode = EditGame;
8474             ModeHighlight();
8475         }
8476         /* [HGM] illegal-move claim should forfeit game when Xboard */
8477         /* only passes fully legal moves                            */
8478         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8479             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8480                                 "False illegal-move claim", GE_XBOARD );
8481             return; // do not take back move we tested as valid
8482         }
8483         currentMove = forwardMostMove-1;
8484         DisplayMove(currentMove-1); /* before DisplayMoveError */
8485         SwitchClocks(forwardMostMove-1); // [HGM] race
8486         DisplayBothClocks();
8487         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8488                 parseList[currentMove], _(cps->which));
8489         DisplayMoveError(buf1);
8490         DrawPosition(FALSE, boards[currentMove]);
8491
8492         SetUserThinkingEnables();
8493         return;
8494     }
8495     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8496         /* Program has a broken "time" command that
8497            outputs a string not ending in newline.
8498            Don't use it. */
8499         cps->sendTime = 0;
8500     }
8501
8502     /*
8503      * If chess program startup fails, exit with an error message.
8504      * Attempts to recover here are futile. [HGM] Well, we try anyway
8505      */
8506     if ((StrStr(message, "unknown host") != NULL)
8507         || (StrStr(message, "No remote directory") != NULL)
8508         || (StrStr(message, "not found") != NULL)
8509         || (StrStr(message, "No such file") != NULL)
8510         || (StrStr(message, "can't alloc") != NULL)
8511         || (StrStr(message, "Permission denied") != NULL)) {
8512
8513         cps->maybeThinking = FALSE;
8514         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8515                 _(cps->which), cps->program, cps->host, message);
8516         RemoveInputSource(cps->isr);
8517         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8518             cps->isr = NULL;
8519             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8520             cps->pr = NoProc; 
8521             if(cps == &first) {
8522                 appData.noChessProgram = TRUE;
8523                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8524                 gameMode = BeginningOfGame; ModeHighlight();
8525                 SetNCPMode();
8526             }
8527             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8528             DisplayMessage("", ""); // erase waiting message
8529             DisplayError(buf1, 0);
8530         }
8531         return;
8532     }
8533
8534     /*
8535      * Look for hint output
8536      */
8537     if (sscanf(message, "Hint: %s", buf1) == 1) {
8538         if (cps == &first && hintRequested) {
8539             hintRequested = FALSE;
8540             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8541                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8542                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8543                                     PosFlags(forwardMostMove),
8544                                     fromY, fromX, toY, toX, promoChar, buf1);
8545                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8546                 DisplayInformation(buf2);
8547             } else {
8548                 /* Hint move could not be parsed!? */
8549               snprintf(buf2, sizeof(buf2),
8550                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8551                         buf1, _(cps->which));
8552                 DisplayError(buf2, 0);
8553             }
8554         } else {
8555           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8556         }
8557         return;
8558     }
8559
8560     /*
8561      * Ignore other messages if game is not in progress
8562      */
8563     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8564         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8565
8566     /*
8567      * look for win, lose, draw, or draw offer
8568      */
8569     if (strncmp(message, "1-0", 3) == 0) {
8570         char *p, *q, *r = "";
8571         p = strchr(message, '{');
8572         if (p) {
8573             q = strchr(p, '}');
8574             if (q) {
8575                 *q = NULLCHAR;
8576                 r = p + 1;
8577             }
8578         }
8579         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8580         return;
8581     } else if (strncmp(message, "0-1", 3) == 0) {
8582         char *p, *q, *r = "";
8583         p = strchr(message, '{');
8584         if (p) {
8585             q = strchr(p, '}');
8586             if (q) {
8587                 *q = NULLCHAR;
8588                 r = p + 1;
8589             }
8590         }
8591         /* Kludge for Arasan 4.1 bug */
8592         if (strcmp(r, "Black resigns") == 0) {
8593             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8594             return;
8595         }
8596         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8597         return;
8598     } else if (strncmp(message, "1/2", 3) == 0) {
8599         char *p, *q, *r = "";
8600         p = strchr(message, '{');
8601         if (p) {
8602             q = strchr(p, '}');
8603             if (q) {
8604                 *q = NULLCHAR;
8605                 r = p + 1;
8606             }
8607         }
8608
8609         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8610         return;
8611
8612     } else if (strncmp(message, "White resign", 12) == 0) {
8613         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8614         return;
8615     } else if (strncmp(message, "Black resign", 12) == 0) {
8616         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8617         return;
8618     } else if (strncmp(message, "White matches", 13) == 0 ||
8619                strncmp(message, "Black matches", 13) == 0   ) {
8620         /* [HGM] ignore GNUShogi noises */
8621         return;
8622     } else if (strncmp(message, "White", 5) == 0 &&
8623                message[5] != '(' &&
8624                StrStr(message, "Black") == NULL) {
8625         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8626         return;
8627     } else if (strncmp(message, "Black", 5) == 0 &&
8628                message[5] != '(') {
8629         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8630         return;
8631     } else if (strcmp(message, "resign") == 0 ||
8632                strcmp(message, "computer resigns") == 0) {
8633         switch (gameMode) {
8634           case MachinePlaysBlack:
8635           case IcsPlayingBlack:
8636             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8637             break;
8638           case MachinePlaysWhite:
8639           case IcsPlayingWhite:
8640             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8641             break;
8642           case TwoMachinesPlay:
8643             if (cps->twoMachinesColor[0] == 'w')
8644               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8645             else
8646               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8647             break;
8648           default:
8649             /* can't happen */
8650             break;
8651         }
8652         return;
8653     } else if (strncmp(message, "opponent mates", 14) == 0) {
8654         switch (gameMode) {
8655           case MachinePlaysBlack:
8656           case IcsPlayingBlack:
8657             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8658             break;
8659           case MachinePlaysWhite:
8660           case IcsPlayingWhite:
8661             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8662             break;
8663           case TwoMachinesPlay:
8664             if (cps->twoMachinesColor[0] == 'w')
8665               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8666             else
8667               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8668             break;
8669           default:
8670             /* can't happen */
8671             break;
8672         }
8673         return;
8674     } else if (strncmp(message, "computer mates", 14) == 0) {
8675         switch (gameMode) {
8676           case MachinePlaysBlack:
8677           case IcsPlayingBlack:
8678             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8679             break;
8680           case MachinePlaysWhite:
8681           case IcsPlayingWhite:
8682             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8683             break;
8684           case TwoMachinesPlay:
8685             if (cps->twoMachinesColor[0] == 'w')
8686               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8687             else
8688               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8689             break;
8690           default:
8691             /* can't happen */
8692             break;
8693         }
8694         return;
8695     } else if (strncmp(message, "checkmate", 9) == 0) {
8696         if (WhiteOnMove(forwardMostMove)) {
8697             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8698         } else {
8699             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8700         }
8701         return;
8702     } else if (strstr(message, "Draw") != NULL ||
8703                strstr(message, "game is a draw") != NULL) {
8704         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8705         return;
8706     } else if (strstr(message, "offer") != NULL &&
8707                strstr(message, "draw") != NULL) {
8708 #if ZIPPY
8709         if (appData.zippyPlay && first.initDone) {
8710             /* Relay offer to ICS */
8711             SendToICS(ics_prefix);
8712             SendToICS("draw\n");
8713         }
8714 #endif
8715         cps->offeredDraw = 2; /* valid until this engine moves twice */
8716         if (gameMode == TwoMachinesPlay) {
8717             if (cps->other->offeredDraw) {
8718                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8719             /* [HGM] in two-machine mode we delay relaying draw offer      */
8720             /* until after we also have move, to see if it is really claim */
8721             }
8722         } else if (gameMode == MachinePlaysWhite ||
8723                    gameMode == MachinePlaysBlack) {
8724           if (userOfferedDraw) {
8725             DisplayInformation(_("Machine accepts your draw offer"));
8726             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8727           } else {
8728             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8729           }
8730         }
8731     }
8732
8733
8734     /*
8735      * Look for thinking output
8736      */
8737     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8738           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8739                                 ) {
8740         int plylev, mvleft, mvtot, curscore, time;
8741         char mvname[MOVE_LEN];
8742         u64 nodes; // [DM]
8743         char plyext;
8744         int ignore = FALSE;
8745         int prefixHint = FALSE;
8746         mvname[0] = NULLCHAR;
8747
8748         switch (gameMode) {
8749           case MachinePlaysBlack:
8750           case IcsPlayingBlack:
8751             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8752             break;
8753           case MachinePlaysWhite:
8754           case IcsPlayingWhite:
8755             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8756             break;
8757           case AnalyzeMode:
8758           case AnalyzeFile:
8759             break;
8760           case IcsObserving: /* [DM] icsEngineAnalyze */
8761             if (!appData.icsEngineAnalyze) ignore = TRUE;
8762             break;
8763           case TwoMachinesPlay:
8764             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8765                 ignore = TRUE;
8766             }
8767             break;
8768           default:
8769             ignore = TRUE;
8770             break;
8771         }
8772
8773         if (!ignore) {
8774             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8775             buf1[0] = NULLCHAR;
8776             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8777                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8778
8779                 if (plyext != ' ' && plyext != '\t') {
8780                     time *= 100;
8781                 }
8782
8783                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8784                 if( cps->scoreIsAbsolute &&
8785                     ( gameMode == MachinePlaysBlack ||
8786                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8787                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8788                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8789                      !WhiteOnMove(currentMove)
8790                     ) )
8791                 {
8792                     curscore = -curscore;
8793                 }
8794
8795                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8796
8797                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8798                         char buf[MSG_SIZ];
8799                         FILE *f;
8800                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8801                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8802                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8803                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8804                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8805                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8806                                 fclose(f);
8807                         } else DisplayError(_("failed writing PV"), 0);
8808                 }
8809
8810                 tempStats.depth = plylev;
8811                 tempStats.nodes = nodes;
8812                 tempStats.time = time;
8813                 tempStats.score = curscore;
8814                 tempStats.got_only_move = 0;
8815
8816                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8817                         int ticklen;
8818
8819                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8820                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8821                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8822                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8823                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8824                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8825                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8826                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8827                 }
8828
8829                 /* Buffer overflow protection */
8830                 if (pv[0] != NULLCHAR) {
8831                     if (strlen(pv) >= sizeof(tempStats.movelist)
8832                         && appData.debugMode) {
8833                         fprintf(debugFP,
8834                                 "PV is too long; using the first %u bytes.\n",
8835                                 (unsigned) sizeof(tempStats.movelist) - 1);
8836                     }
8837
8838                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8839                 } else {
8840                     sprintf(tempStats.movelist, " no PV\n");
8841                 }
8842
8843                 if (tempStats.seen_stat) {
8844                     tempStats.ok_to_send = 1;
8845                 }
8846
8847                 if (strchr(tempStats.movelist, '(') != NULL) {
8848                     tempStats.line_is_book = 1;
8849                     tempStats.nr_moves = 0;
8850                     tempStats.moves_left = 0;
8851                 } else {
8852                     tempStats.line_is_book = 0;
8853                 }
8854
8855                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8856                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8857
8858                 SendProgramStatsToFrontend( cps, &tempStats );
8859
8860                 /*
8861                     [AS] Protect the thinkOutput buffer from overflow... this
8862                     is only useful if buf1 hasn't overflowed first!
8863                 */
8864                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8865                          plylev,
8866                          (gameMode == TwoMachinesPlay ?
8867                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8868                          ((double) curscore) / 100.0,
8869                          prefixHint ? lastHint : "",
8870                          prefixHint ? " " : "" );
8871
8872                 if( buf1[0] != NULLCHAR ) {
8873                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8874
8875                     if( strlen(pv) > max_len ) {
8876                         if( appData.debugMode) {
8877                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8878                         }
8879                         pv[max_len+1] = '\0';
8880                     }
8881
8882                     strcat( thinkOutput, pv);
8883                 }
8884
8885                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8886                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8887                     DisplayMove(currentMove - 1);
8888                 }
8889                 return;
8890
8891             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8892                 /* crafty (9.25+) says "(only move) <move>"
8893                  * if there is only 1 legal move
8894                  */
8895                 sscanf(p, "(only move) %s", buf1);
8896                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8897                 sprintf(programStats.movelist, "%s (only move)", buf1);
8898                 programStats.depth = 1;
8899                 programStats.nr_moves = 1;
8900                 programStats.moves_left = 1;
8901                 programStats.nodes = 1;
8902                 programStats.time = 1;
8903                 programStats.got_only_move = 1;
8904
8905                 /* Not really, but we also use this member to
8906                    mean "line isn't going to change" (Crafty
8907                    isn't searching, so stats won't change) */
8908                 programStats.line_is_book = 1;
8909
8910                 SendProgramStatsToFrontend( cps, &programStats );
8911
8912                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8913                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8914                     DisplayMove(currentMove - 1);
8915                 }
8916                 return;
8917             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8918                               &time, &nodes, &plylev, &mvleft,
8919                               &mvtot, mvname) >= 5) {
8920                 /* The stat01: line is from Crafty (9.29+) in response
8921                    to the "." command */
8922                 programStats.seen_stat = 1;
8923                 cps->maybeThinking = TRUE;
8924
8925                 if (programStats.got_only_move || !appData.periodicUpdates)
8926                   return;
8927
8928                 programStats.depth = plylev;
8929                 programStats.time = time;
8930                 programStats.nodes = nodes;
8931                 programStats.moves_left = mvleft;
8932                 programStats.nr_moves = mvtot;
8933                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8934                 programStats.ok_to_send = 1;
8935                 programStats.movelist[0] = '\0';
8936
8937                 SendProgramStatsToFrontend( cps, &programStats );
8938
8939                 return;
8940
8941             } else if (strncmp(message,"++",2) == 0) {
8942                 /* Crafty 9.29+ outputs this */
8943                 programStats.got_fail = 2;
8944                 return;
8945
8946             } else if (strncmp(message,"--",2) == 0) {
8947                 /* Crafty 9.29+ outputs this */
8948                 programStats.got_fail = 1;
8949                 return;
8950
8951             } else if (thinkOutput[0] != NULLCHAR &&
8952                        strncmp(message, "    ", 4) == 0) {
8953                 unsigned message_len;
8954
8955                 p = message;
8956                 while (*p && *p == ' ') p++;
8957
8958                 message_len = strlen( p );
8959
8960                 /* [AS] Avoid buffer overflow */
8961                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8962                     strcat(thinkOutput, " ");
8963                     strcat(thinkOutput, p);
8964                 }
8965
8966                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8967                     strcat(programStats.movelist, " ");
8968                     strcat(programStats.movelist, p);
8969                 }
8970
8971                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8972                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8973                     DisplayMove(currentMove - 1);
8974                 }
8975                 return;
8976             }
8977         }
8978         else {
8979             buf1[0] = NULLCHAR;
8980
8981             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8982                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8983             {
8984                 ChessProgramStats cpstats;
8985
8986                 if (plyext != ' ' && plyext != '\t') {
8987                     time *= 100;
8988                 }
8989
8990                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8991                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8992                     curscore = -curscore;
8993                 }
8994
8995                 cpstats.depth = plylev;
8996                 cpstats.nodes = nodes;
8997                 cpstats.time = time;
8998                 cpstats.score = curscore;
8999                 cpstats.got_only_move = 0;
9000                 cpstats.movelist[0] = '\0';
9001
9002                 if (buf1[0] != NULLCHAR) {
9003                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9004                 }
9005
9006                 cpstats.ok_to_send = 0;
9007                 cpstats.line_is_book = 0;
9008                 cpstats.nr_moves = 0;
9009                 cpstats.moves_left = 0;
9010
9011                 SendProgramStatsToFrontend( cps, &cpstats );
9012             }
9013         }
9014     }
9015 }
9016
9017
9018 /* Parse a game score from the character string "game", and
9019    record it as the history of the current game.  The game
9020    score is NOT assumed to start from the standard position.
9021    The display is not updated in any way.
9022    */
9023 void
9024 ParseGameHistory (char *game)
9025 {
9026     ChessMove moveType;
9027     int fromX, fromY, toX, toY, boardIndex;
9028     char promoChar;
9029     char *p, *q;
9030     char buf[MSG_SIZ];
9031
9032     if (appData.debugMode)
9033       fprintf(debugFP, "Parsing game history: %s\n", game);
9034
9035     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9036     gameInfo.site = StrSave(appData.icsHost);
9037     gameInfo.date = PGNDate();
9038     gameInfo.round = StrSave("-");
9039
9040     /* Parse out names of players */
9041     while (*game == ' ') game++;
9042     p = buf;
9043     while (*game != ' ') *p++ = *game++;
9044     *p = NULLCHAR;
9045     gameInfo.white = StrSave(buf);
9046     while (*game == ' ') game++;
9047     p = buf;
9048     while (*game != ' ' && *game != '\n') *p++ = *game++;
9049     *p = NULLCHAR;
9050     gameInfo.black = StrSave(buf);
9051
9052     /* Parse moves */
9053     boardIndex = blackPlaysFirst ? 1 : 0;
9054     yynewstr(game);
9055     for (;;) {
9056         yyboardindex = boardIndex;
9057         moveType = (ChessMove) Myylex();
9058         switch (moveType) {
9059           case IllegalMove:             /* maybe suicide chess, etc. */
9060   if (appData.debugMode) {
9061     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9062     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9063     setbuf(debugFP, NULL);
9064   }
9065           case WhitePromotion:
9066           case BlackPromotion:
9067           case WhiteNonPromotion:
9068           case BlackNonPromotion:
9069           case NormalMove:
9070           case WhiteCapturesEnPassant:
9071           case BlackCapturesEnPassant:
9072           case WhiteKingSideCastle:
9073           case WhiteQueenSideCastle:
9074           case BlackKingSideCastle:
9075           case BlackQueenSideCastle:
9076           case WhiteKingSideCastleWild:
9077           case WhiteQueenSideCastleWild:
9078           case BlackKingSideCastleWild:
9079           case BlackQueenSideCastleWild:
9080           /* PUSH Fabien */
9081           case WhiteHSideCastleFR:
9082           case WhiteASideCastleFR:
9083           case BlackHSideCastleFR:
9084           case BlackASideCastleFR:
9085           /* POP Fabien */
9086             fromX = currentMoveString[0] - AAA;
9087             fromY = currentMoveString[1] - ONE;
9088             toX = currentMoveString[2] - AAA;
9089             toY = currentMoveString[3] - ONE;
9090             promoChar = currentMoveString[4];
9091             break;
9092           case WhiteDrop:
9093           case BlackDrop:
9094             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9095             fromX = moveType == WhiteDrop ?
9096               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9097             (int) CharToPiece(ToLower(currentMoveString[0]));
9098             fromY = DROP_RANK;
9099             toX = currentMoveString[2] - AAA;
9100             toY = currentMoveString[3] - ONE;
9101             promoChar = NULLCHAR;
9102             break;
9103           case AmbiguousMove:
9104             /* bug? */
9105             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9106   if (appData.debugMode) {
9107     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9108     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9109     setbuf(debugFP, NULL);
9110   }
9111             DisplayError(buf, 0);
9112             return;
9113           case ImpossibleMove:
9114             /* bug? */
9115             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9116   if (appData.debugMode) {
9117     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9118     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9119     setbuf(debugFP, NULL);
9120   }
9121             DisplayError(buf, 0);
9122             return;
9123           case EndOfFile:
9124             if (boardIndex < backwardMostMove) {
9125                 /* Oops, gap.  How did that happen? */
9126                 DisplayError(_("Gap in move list"), 0);
9127                 return;
9128             }
9129             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9130             if (boardIndex > forwardMostMove) {
9131                 forwardMostMove = boardIndex;
9132             }
9133             return;
9134           case ElapsedTime:
9135             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9136                 strcat(parseList[boardIndex-1], " ");
9137                 strcat(parseList[boardIndex-1], yy_text);
9138             }
9139             continue;
9140           case Comment:
9141           case PGNTag:
9142           case NAG:
9143           default:
9144             /* ignore */
9145             continue;
9146           case WhiteWins:
9147           case BlackWins:
9148           case GameIsDrawn:
9149           case GameUnfinished:
9150             if (gameMode == IcsExamining) {
9151                 if (boardIndex < backwardMostMove) {
9152                     /* Oops, gap.  How did that happen? */
9153                     return;
9154                 }
9155                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9156                 return;
9157             }
9158             gameInfo.result = moveType;
9159             p = strchr(yy_text, '{');
9160             if (p == NULL) p = strchr(yy_text, '(');
9161             if (p == NULL) {
9162                 p = yy_text;
9163                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9164             } else {
9165                 q = strchr(p, *p == '{' ? '}' : ')');
9166                 if (q != NULL) *q = NULLCHAR;
9167                 p++;
9168             }
9169             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9170             gameInfo.resultDetails = StrSave(p);
9171             continue;
9172         }
9173         if (boardIndex >= forwardMostMove &&
9174             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9175             backwardMostMove = blackPlaysFirst ? 1 : 0;
9176             return;
9177         }
9178         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9179                                  fromY, fromX, toY, toX, promoChar,
9180                                  parseList[boardIndex]);
9181         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9182         /* currentMoveString is set as a side-effect of yylex */
9183         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9184         strcat(moveList[boardIndex], "\n");
9185         boardIndex++;
9186         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9187         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9188           case MT_NONE:
9189           case MT_STALEMATE:
9190           default:
9191             break;
9192           case MT_CHECK:
9193             if(gameInfo.variant != VariantShogi)
9194                 strcat(parseList[boardIndex - 1], "+");
9195             break;
9196           case MT_CHECKMATE:
9197           case MT_STAINMATE:
9198             strcat(parseList[boardIndex - 1], "#");
9199             break;
9200         }
9201     }
9202 }
9203
9204
9205 /* Apply a move to the given board  */
9206 void
9207 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9208 {
9209   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9210   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9211
9212     /* [HGM] compute & store e.p. status and castling rights for new position */
9213     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9214
9215       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9216       oldEP = (signed char)board[EP_STATUS];
9217       board[EP_STATUS] = EP_NONE;
9218
9219   if (fromY == DROP_RANK) {
9220         /* must be first */
9221         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9222             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9223             return;
9224         }
9225         piece = board[toY][toX] = (ChessSquare) fromX;
9226   } else {
9227       int i;
9228
9229       if( board[toY][toX] != EmptySquare )
9230            board[EP_STATUS] = EP_CAPTURE;
9231
9232       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9233            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9234                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9235       } else
9236       if( board[fromY][fromX] == WhitePawn ) {
9237            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9238                board[EP_STATUS] = EP_PAWN_MOVE;
9239            if( toY-fromY==2) {
9240                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9241                         gameInfo.variant != VariantBerolina || toX < fromX)
9242                       board[EP_STATUS] = toX | berolina;
9243                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9244                         gameInfo.variant != VariantBerolina || toX > fromX)
9245                       board[EP_STATUS] = toX;
9246            }
9247       } else
9248       if( board[fromY][fromX] == BlackPawn ) {
9249            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9250                board[EP_STATUS] = EP_PAWN_MOVE;
9251            if( toY-fromY== -2) {
9252                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9253                         gameInfo.variant != VariantBerolina || toX < fromX)
9254                       board[EP_STATUS] = toX | berolina;
9255                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9256                         gameInfo.variant != VariantBerolina || toX > fromX)
9257                       board[EP_STATUS] = toX;
9258            }
9259        }
9260
9261        for(i=0; i<nrCastlingRights; i++) {
9262            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9263               board[CASTLING][i] == toX   && castlingRank[i] == toY
9264              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9265        }
9266
9267      if (fromX == toX && fromY == toY) return;
9268
9269      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9270      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9271      if(gameInfo.variant == VariantKnightmate)
9272          king += (int) WhiteUnicorn - (int) WhiteKing;
9273
9274     /* Code added by Tord: */
9275     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9276     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9277         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9278       board[fromY][fromX] = EmptySquare;
9279       board[toY][toX] = EmptySquare;
9280       if((toX > fromX) != (piece == WhiteRook)) {
9281         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9282       } else {
9283         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9284       }
9285     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9286                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9287       board[fromY][fromX] = EmptySquare;
9288       board[toY][toX] = EmptySquare;
9289       if((toX > fromX) != (piece == BlackRook)) {
9290         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9291       } else {
9292         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9293       }
9294     /* End of code added by Tord */
9295
9296     } else if (board[fromY][fromX] == king
9297         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9298         && toY == fromY && toX > fromX+1) {
9299         board[fromY][fromX] = EmptySquare;
9300         board[toY][toX] = king;
9301         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9302         board[fromY][BOARD_RGHT-1] = EmptySquare;
9303     } else if (board[fromY][fromX] == king
9304         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9305                && toY == fromY && toX < fromX-1) {
9306         board[fromY][fromX] = EmptySquare;
9307         board[toY][toX] = king;
9308         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9309         board[fromY][BOARD_LEFT] = EmptySquare;
9310     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9311                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9312                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9313                ) {
9314         /* white pawn promotion */
9315         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9316         if(gameInfo.variant==VariantBughouse ||
9317            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9318             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9319         board[fromY][fromX] = EmptySquare;
9320     } else if ((fromY >= BOARD_HEIGHT>>1)
9321                && (toX != fromX)
9322                && gameInfo.variant != VariantXiangqi
9323                && gameInfo.variant != VariantBerolina
9324                && (board[fromY][fromX] == WhitePawn)
9325                && (board[toY][toX] == EmptySquare)) {
9326         board[fromY][fromX] = EmptySquare;
9327         board[toY][toX] = WhitePawn;
9328         captured = board[toY - 1][toX];
9329         board[toY - 1][toX] = EmptySquare;
9330     } else if ((fromY == BOARD_HEIGHT-4)
9331                && (toX == fromX)
9332                && gameInfo.variant == VariantBerolina
9333                && (board[fromY][fromX] == WhitePawn)
9334                && (board[toY][toX] == EmptySquare)) {
9335         board[fromY][fromX] = EmptySquare;
9336         board[toY][toX] = WhitePawn;
9337         if(oldEP & EP_BEROLIN_A) {
9338                 captured = board[fromY][fromX-1];
9339                 board[fromY][fromX-1] = EmptySquare;
9340         }else{  captured = board[fromY][fromX+1];
9341                 board[fromY][fromX+1] = EmptySquare;
9342         }
9343     } else if (board[fromY][fromX] == king
9344         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9345                && toY == fromY && toX > fromX+1) {
9346         board[fromY][fromX] = EmptySquare;
9347         board[toY][toX] = king;
9348         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9349         board[fromY][BOARD_RGHT-1] = EmptySquare;
9350     } else if (board[fromY][fromX] == king
9351         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9352                && toY == fromY && toX < fromX-1) {
9353         board[fromY][fromX] = EmptySquare;
9354         board[toY][toX] = king;
9355         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9356         board[fromY][BOARD_LEFT] = EmptySquare;
9357     } else if (fromY == 7 && fromX == 3
9358                && board[fromY][fromX] == BlackKing
9359                && toY == 7 && toX == 5) {
9360         board[fromY][fromX] = EmptySquare;
9361         board[toY][toX] = BlackKing;
9362         board[fromY][7] = EmptySquare;
9363         board[toY][4] = BlackRook;
9364     } else if (fromY == 7 && fromX == 3
9365                && board[fromY][fromX] == BlackKing
9366                && toY == 7 && toX == 1) {
9367         board[fromY][fromX] = EmptySquare;
9368         board[toY][toX] = BlackKing;
9369         board[fromY][0] = EmptySquare;
9370         board[toY][2] = BlackRook;
9371     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9372                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9373                && toY < promoRank && promoChar
9374                ) {
9375         /* black pawn promotion */
9376         board[toY][toX] = CharToPiece(ToLower(promoChar));
9377         if(gameInfo.variant==VariantBughouse ||
9378            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9379             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9380         board[fromY][fromX] = EmptySquare;
9381     } else if ((fromY < BOARD_HEIGHT>>1)
9382                && (toX != fromX)
9383                && gameInfo.variant != VariantXiangqi
9384                && gameInfo.variant != VariantBerolina
9385                && (board[fromY][fromX] == BlackPawn)
9386                && (board[toY][toX] == EmptySquare)) {
9387         board[fromY][fromX] = EmptySquare;
9388         board[toY][toX] = BlackPawn;
9389         captured = board[toY + 1][toX];
9390         board[toY + 1][toX] = EmptySquare;
9391     } else if ((fromY == 3)
9392                && (toX == fromX)
9393                && gameInfo.variant == VariantBerolina
9394                && (board[fromY][fromX] == BlackPawn)
9395                && (board[toY][toX] == EmptySquare)) {
9396         board[fromY][fromX] = EmptySquare;
9397         board[toY][toX] = BlackPawn;
9398         if(oldEP & EP_BEROLIN_A) {
9399                 captured = board[fromY][fromX-1];
9400                 board[fromY][fromX-1] = EmptySquare;
9401         }else{  captured = board[fromY][fromX+1];
9402                 board[fromY][fromX+1] = EmptySquare;
9403         }
9404     } else {
9405         board[toY][toX] = board[fromY][fromX];
9406         board[fromY][fromX] = EmptySquare;
9407     }
9408   }
9409
9410     if (gameInfo.holdingsWidth != 0) {
9411
9412       /* !!A lot more code needs to be written to support holdings  */
9413       /* [HGM] OK, so I have written it. Holdings are stored in the */
9414       /* penultimate board files, so they are automaticlly stored   */
9415       /* in the game history.                                       */
9416       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9417                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9418         /* Delete from holdings, by decreasing count */
9419         /* and erasing image if necessary            */
9420         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9421         if(p < (int) BlackPawn) { /* white drop */
9422              p -= (int)WhitePawn;
9423                  p = PieceToNumber((ChessSquare)p);
9424              if(p >= gameInfo.holdingsSize) p = 0;
9425              if(--board[p][BOARD_WIDTH-2] <= 0)
9426                   board[p][BOARD_WIDTH-1] = EmptySquare;
9427              if((int)board[p][BOARD_WIDTH-2] < 0)
9428                         board[p][BOARD_WIDTH-2] = 0;
9429         } else {                  /* black drop */
9430              p -= (int)BlackPawn;
9431                  p = PieceToNumber((ChessSquare)p);
9432              if(p >= gameInfo.holdingsSize) p = 0;
9433              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9434                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9435              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9436                         board[BOARD_HEIGHT-1-p][1] = 0;
9437         }
9438       }
9439       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9440           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9441         /* [HGM] holdings: Add to holdings, if holdings exist */
9442         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9443                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9444                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9445         }
9446         p = (int) captured;
9447         if (p >= (int) BlackPawn) {
9448           p -= (int)BlackPawn;
9449           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9450                   /* in Shogi restore piece to its original  first */
9451                   captured = (ChessSquare) (DEMOTED captured);
9452                   p = DEMOTED p;
9453           }
9454           p = PieceToNumber((ChessSquare)p);
9455           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9456           board[p][BOARD_WIDTH-2]++;
9457           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9458         } else {
9459           p -= (int)WhitePawn;
9460           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9461                   captured = (ChessSquare) (DEMOTED captured);
9462                   p = DEMOTED p;
9463           }
9464           p = PieceToNumber((ChessSquare)p);
9465           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9466           board[BOARD_HEIGHT-1-p][1]++;
9467           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9468         }
9469       }
9470     } else if (gameInfo.variant == VariantAtomic) {
9471       if (captured != EmptySquare) {
9472         int y, x;
9473         for (y = toY-1; y <= toY+1; y++) {
9474           for (x = toX-1; x <= toX+1; x++) {
9475             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9476                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9477               board[y][x] = EmptySquare;
9478             }
9479           }
9480         }
9481         board[toY][toX] = EmptySquare;
9482       }
9483     }
9484     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9485         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9486     } else
9487     if(promoChar == '+') {
9488         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9489         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9490     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9491         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9492         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9493            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9494         board[toY][toX] = newPiece;
9495     }
9496     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9497                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9498         // [HGM] superchess: take promotion piece out of holdings
9499         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9500         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9501             if(!--board[k][BOARD_WIDTH-2])
9502                 board[k][BOARD_WIDTH-1] = EmptySquare;
9503         } else {
9504             if(!--board[BOARD_HEIGHT-1-k][1])
9505                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9506         }
9507     }
9508
9509 }
9510
9511 /* Updates forwardMostMove */
9512 void
9513 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9514 {
9515 //    forwardMostMove++; // [HGM] bare: moved downstream
9516
9517     (void) CoordsToAlgebraic(boards[forwardMostMove],
9518                              PosFlags(forwardMostMove),
9519                              fromY, fromX, toY, toX, promoChar,
9520                              parseList[forwardMostMove]);
9521
9522     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9523         int timeLeft; static int lastLoadFlag=0; int king, piece;
9524         piece = boards[forwardMostMove][fromY][fromX];
9525         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9526         if(gameInfo.variant == VariantKnightmate)
9527             king += (int) WhiteUnicorn - (int) WhiteKing;
9528         if(forwardMostMove == 0) {
9529             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9530                 fprintf(serverMoves, "%s;", UserName());
9531             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9532                 fprintf(serverMoves, "%s;", second.tidy);
9533             fprintf(serverMoves, "%s;", first.tidy);
9534             if(gameMode == MachinePlaysWhite)
9535                 fprintf(serverMoves, "%s;", UserName());
9536             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9537                 fprintf(serverMoves, "%s;", second.tidy);
9538         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9539         lastLoadFlag = loadFlag;
9540         // print base move
9541         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9542         // print castling suffix
9543         if( toY == fromY && piece == king ) {
9544             if(toX-fromX > 1)
9545                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9546             if(fromX-toX >1)
9547                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9548         }
9549         // e.p. suffix
9550         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9551              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9552              boards[forwardMostMove][toY][toX] == EmptySquare
9553              && fromX != toX && fromY != toY)
9554                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9555         // promotion suffix
9556         if(promoChar != NULLCHAR)
9557                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9558         if(!loadFlag) {
9559                 char buf[MOVE_LEN*2], *p; int len;
9560             fprintf(serverMoves, "/%d/%d",
9561                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9562             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9563             else                      timeLeft = blackTimeRemaining/1000;
9564             fprintf(serverMoves, "/%d", timeLeft);
9565                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9566                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9567                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9568             fprintf(serverMoves, "/%s", buf);
9569         }
9570         fflush(serverMoves);
9571     }
9572
9573     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9574         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9575       return;
9576     }
9577     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9578     if (commentList[forwardMostMove+1] != NULL) {
9579         free(commentList[forwardMostMove+1]);
9580         commentList[forwardMostMove+1] = NULL;
9581     }
9582     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9583     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9584     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9585     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9586     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9587     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9588     adjustedClock = FALSE;
9589     gameInfo.result = GameUnfinished;
9590     if (gameInfo.resultDetails != NULL) {
9591         free(gameInfo.resultDetails);
9592         gameInfo.resultDetails = NULL;
9593     }
9594     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9595                               moveList[forwardMostMove - 1]);
9596     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9597       case MT_NONE:
9598       case MT_STALEMATE:
9599       default:
9600         break;
9601       case MT_CHECK:
9602         if(gameInfo.variant != VariantShogi)
9603             strcat(parseList[forwardMostMove - 1], "+");
9604         break;
9605       case MT_CHECKMATE:
9606       case MT_STAINMATE:
9607         strcat(parseList[forwardMostMove - 1], "#");
9608         break;
9609     }
9610
9611 }
9612
9613 /* Updates currentMove if not pausing */
9614 void
9615 ShowMove (int fromX, int fromY, int toX, int toY)
9616 {
9617     int instant = (gameMode == PlayFromGameFile) ?
9618         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9619     if(appData.noGUI) return;
9620     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9621         if (!instant) {
9622             if (forwardMostMove == currentMove + 1) {
9623                 AnimateMove(boards[forwardMostMove - 1],
9624                             fromX, fromY, toX, toY);
9625             }
9626             if (appData.highlightLastMove) {
9627                 SetHighlights(fromX, fromY, toX, toY);
9628             }
9629         }
9630         currentMove = forwardMostMove;
9631     }
9632
9633     if (instant) return;
9634
9635     DisplayMove(currentMove - 1);
9636     DrawPosition(FALSE, boards[currentMove]);
9637     DisplayBothClocks();
9638     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9639 }
9640
9641 void
9642 SendEgtPath (ChessProgramState *cps)
9643 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9644         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9645
9646         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9647
9648         while(*p) {
9649             char c, *q = name+1, *r, *s;
9650
9651             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9652             while(*p && *p != ',') *q++ = *p++;
9653             *q++ = ':'; *q = 0;
9654             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9655                 strcmp(name, ",nalimov:") == 0 ) {
9656                 // take nalimov path from the menu-changeable option first, if it is defined
9657               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9658                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9659             } else
9660             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9661                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9662                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9663                 s = r = StrStr(s, ":") + 1; // beginning of path info
9664                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9665                 c = *r; *r = 0;             // temporarily null-terminate path info
9666                     *--q = 0;               // strip of trailig ':' from name
9667                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9668                 *r = c;
9669                 SendToProgram(buf,cps);     // send egtbpath command for this format
9670             }
9671             if(*p == ',') p++; // read away comma to position for next format name
9672         }
9673 }
9674
9675 void
9676 InitChessProgram (ChessProgramState *cps, int setup)
9677 /* setup needed to setup FRC opening position */
9678 {
9679     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9680     if (appData.noChessProgram) return;
9681     hintRequested = FALSE;
9682     bookRequested = FALSE;
9683
9684     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9685     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9686     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9687     if(cps->memSize) { /* [HGM] memory */
9688       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9689         SendToProgram(buf, cps);
9690     }
9691     SendEgtPath(cps); /* [HGM] EGT */
9692     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9693       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9694         SendToProgram(buf, cps);
9695     }
9696
9697     SendToProgram(cps->initString, cps);
9698     if (gameInfo.variant != VariantNormal &&
9699         gameInfo.variant != VariantLoadable
9700         /* [HGM] also send variant if board size non-standard */
9701         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9702                                             ) {
9703       char *v = VariantName(gameInfo.variant);
9704       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9705         /* [HGM] in protocol 1 we have to assume all variants valid */
9706         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9707         DisplayFatalError(buf, 0, 1);
9708         return;
9709       }
9710
9711       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9712       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9713       if( gameInfo.variant == VariantXiangqi )
9714            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9715       if( gameInfo.variant == VariantShogi )
9716            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9717       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9718            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9719       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9720           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9721            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9722       if( gameInfo.variant == VariantCourier )
9723            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9724       if( gameInfo.variant == VariantSuper )
9725            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9726       if( gameInfo.variant == VariantGreat )
9727            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9728       if( gameInfo.variant == VariantSChess )
9729            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9730       if( gameInfo.variant == VariantGrand )
9731            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9732
9733       if(overruled) {
9734         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9735                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9736            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9737            if(StrStr(cps->variants, b) == NULL) {
9738                // specific sized variant not known, check if general sizing allowed
9739                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9740                    if(StrStr(cps->variants, "boardsize") == NULL) {
9741                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9742                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9743                        DisplayFatalError(buf, 0, 1);
9744                        return;
9745                    }
9746                    /* [HGM] here we really should compare with the maximum supported board size */
9747                }
9748            }
9749       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9750       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9751       SendToProgram(buf, cps);
9752     }
9753     currentlyInitializedVariant = gameInfo.variant;
9754
9755     /* [HGM] send opening position in FRC to first engine */
9756     if(setup) {
9757           SendToProgram("force\n", cps);
9758           SendBoard(cps, 0);
9759           /* engine is now in force mode! Set flag to wake it up after first move. */
9760           setboardSpoiledMachineBlack = 1;
9761     }
9762
9763     if (cps->sendICS) {
9764       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9765       SendToProgram(buf, cps);
9766     }
9767     cps->maybeThinking = FALSE;
9768     cps->offeredDraw = 0;
9769     if (!appData.icsActive) {
9770         SendTimeControl(cps, movesPerSession, timeControl,
9771                         timeIncrement, appData.searchDepth,
9772                         searchTime);
9773     }
9774     if (appData.showThinking
9775         // [HGM] thinking: four options require thinking output to be sent
9776         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9777                                 ) {
9778         SendToProgram("post\n", cps);
9779     }
9780     SendToProgram("hard\n", cps);
9781     if (!appData.ponderNextMove) {
9782         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9783            it without being sure what state we are in first.  "hard"
9784            is not a toggle, so that one is OK.
9785          */
9786         SendToProgram("easy\n", cps);
9787     }
9788     if (cps->usePing) {
9789       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9790       SendToProgram(buf, cps);
9791     }
9792     cps->initDone = TRUE;
9793     ClearEngineOutputPane(cps == &second);
9794 }
9795
9796
9797 void
9798 StartChessProgram (ChessProgramState *cps)
9799 {
9800     char buf[MSG_SIZ];
9801     int err;
9802
9803     if (appData.noChessProgram) return;
9804     cps->initDone = FALSE;
9805
9806     if (strcmp(cps->host, "localhost") == 0) {
9807         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9808     } else if (*appData.remoteShell == NULLCHAR) {
9809         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9810     } else {
9811         if (*appData.remoteUser == NULLCHAR) {
9812           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9813                     cps->program);
9814         } else {
9815           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9816                     cps->host, appData.remoteUser, cps->program);
9817         }
9818         err = StartChildProcess(buf, "", &cps->pr);
9819     }
9820
9821     if (err != 0) {
9822       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9823         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9824         if(cps != &first) return;
9825         appData.noChessProgram = TRUE;
9826         ThawUI();
9827         SetNCPMode();
9828 //      DisplayFatalError(buf, err, 1);
9829 //      cps->pr = NoProc;
9830 //      cps->isr = NULL;
9831         return;
9832     }
9833
9834     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9835     if (cps->protocolVersion > 1) {
9836       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9837       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9838       cps->comboCnt = 0;  //                and values of combo boxes
9839       SendToProgram(buf, cps);
9840     } else {
9841       SendToProgram("xboard\n", cps);
9842     }
9843 }
9844
9845 void
9846 TwoMachinesEventIfReady P((void))
9847 {
9848   static int curMess = 0;
9849   if (first.lastPing != first.lastPong) {
9850     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9851     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9852     return;
9853   }
9854   if (second.lastPing != second.lastPong) {
9855     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9856     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9857     return;
9858   }
9859   DisplayMessage("", ""); curMess = 0;
9860   ThawUI();
9861   TwoMachinesEvent();
9862 }
9863
9864 char *
9865 MakeName (char *template)
9866 {
9867     time_t clock;
9868     struct tm *tm;
9869     static char buf[MSG_SIZ];
9870     char *p = buf;
9871     int i;
9872
9873     clock = time((time_t *)NULL);
9874     tm = localtime(&clock);
9875
9876     while(*p++ = *template++) if(p[-1] == '%') {
9877         switch(*template++) {
9878           case 0:   *p = 0; return buf;
9879           case 'Y': i = tm->tm_year+1900; break;
9880           case 'y': i = tm->tm_year-100; break;
9881           case 'M': i = tm->tm_mon+1; break;
9882           case 'd': i = tm->tm_mday; break;
9883           case 'h': i = tm->tm_hour; break;
9884           case 'm': i = tm->tm_min; break;
9885           case 's': i = tm->tm_sec; break;
9886           default:  i = 0;
9887         }
9888         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9889     }
9890     return buf;
9891 }
9892
9893 int
9894 CountPlayers (char *p)
9895 {
9896     int n = 0;
9897     while(p = strchr(p, '\n')) p++, n++; // count participants
9898     return n;
9899 }
9900
9901 FILE *
9902 WriteTourneyFile (char *results, FILE *f)
9903 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9904     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9905     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9906         // create a file with tournament description
9907         fprintf(f, "-participants {%s}\n", appData.participants);
9908         fprintf(f, "-seedBase %d\n", appData.seedBase);
9909         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9910         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9911         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9912         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9913         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9914         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9915         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9916         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9917         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9918         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9919         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9920         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9921         if(searchTime > 0)
9922                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9923         else {
9924                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9925                 fprintf(f, "-tc %s\n", appData.timeControl);
9926                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9927         }
9928         fprintf(f, "-results \"%s\"\n", results);
9929     }
9930     return f;
9931 }
9932
9933 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9934
9935 void
9936 Substitute (char *participants, int expunge)
9937 {
9938     int i, changed, changes=0, nPlayers=0;
9939     char *p, *q, *r, buf[MSG_SIZ];
9940     if(participants == NULL) return;
9941     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9942     r = p = participants; q = appData.participants;
9943     while(*p && *p == *q) {
9944         if(*p == '\n') r = p+1, nPlayers++;
9945         p++; q++;
9946     }
9947     if(*p) { // difference
9948         while(*p && *p++ != '\n');
9949         while(*q && *q++ != '\n');
9950       changed = nPlayers;
9951         changes = 1 + (strcmp(p, q) != 0);
9952     }
9953     if(changes == 1) { // a single engine mnemonic was changed
9954         q = r; while(*q) nPlayers += (*q++ == '\n');
9955         p = buf; while(*r && (*p = *r++) != '\n') p++;
9956         *p = NULLCHAR;
9957         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9958         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9959         if(mnemonic[i]) { // The substitute is valid
9960             FILE *f;
9961             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9962                 flock(fileno(f), LOCK_EX);
9963                 ParseArgsFromFile(f);
9964                 fseek(f, 0, SEEK_SET);
9965                 FREE(appData.participants); appData.participants = participants;
9966                 if(expunge) { // erase results of replaced engine
9967                     int len = strlen(appData.results), w, b, dummy;
9968                     for(i=0; i<len; i++) {
9969                         Pairing(i, nPlayers, &w, &b, &dummy);
9970                         if((w == changed || b == changed) && appData.results[i] == '*') {
9971                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9972                             fclose(f);
9973                             return;
9974                         }
9975                     }
9976                     for(i=0; i<len; i++) {
9977                         Pairing(i, nPlayers, &w, &b, &dummy);
9978                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9979                     }
9980                 }
9981                 WriteTourneyFile(appData.results, f);
9982                 fclose(f); // release lock
9983                 return;
9984             }
9985         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9986     }
9987     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9988     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9989     free(participants);
9990     return;
9991 }
9992
9993 int
9994 CreateTourney (char *name)
9995 {
9996         FILE *f;
9997         if(matchMode && strcmp(name, appData.tourneyFile)) {
9998              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9999         }
10000         if(name[0] == NULLCHAR) {
10001             if(appData.participants[0])
10002                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10003             return 0;
10004         }
10005         f = fopen(name, "r");
10006         if(f) { // file exists
10007             ASSIGN(appData.tourneyFile, name);
10008             ParseArgsFromFile(f); // parse it
10009         } else {
10010             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10011             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10012                 DisplayError(_("Not enough participants"), 0);
10013                 return 0;
10014             }
10015             ASSIGN(appData.tourneyFile, name);
10016             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10017             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10018         }
10019         fclose(f);
10020         appData.noChessProgram = FALSE;
10021         appData.clockMode = TRUE;
10022         SetGNUMode();
10023         return 1;
10024 }
10025
10026 int
10027 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10028 {
10029     char buf[MSG_SIZ], *p, *q;
10030     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10031     skip = !all && group[0]; // if group requested, we start in skip mode
10032     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10033         p = names; q = buf; header = 0;
10034         while(*p && *p != '\n') *q++ = *p++;
10035         *q = 0;
10036         if(*p == '\n') p++;
10037         if(buf[0] == '#') {
10038             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10039             depth++; // we must be entering a new group
10040             if(all) continue; // suppress printing group headers when complete list requested
10041             header = 1;
10042             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10043         }
10044         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10045         if(engineList[i]) free(engineList[i]);
10046         engineList[i] = strdup(buf);
10047         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10048         if(engineMnemonic[i]) free(engineMnemonic[i]);
10049         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10050             strcat(buf, " (");
10051             sscanf(q + 8, "%s", buf + strlen(buf));
10052             strcat(buf, ")");
10053         }
10054         engineMnemonic[i] = strdup(buf);
10055         i++;
10056     }
10057     engineList[i] = engineMnemonic[i] = NULL;
10058     return i;
10059 }
10060
10061 // following implemented as macro to avoid type limitations
10062 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10063
10064 void
10065 SwapEngines (int n)
10066 {   // swap settings for first engine and other engine (so far only some selected options)
10067     int h;
10068     char *p;
10069     if(n == 0) return;
10070     SWAP(directory, p)
10071     SWAP(chessProgram, p)
10072     SWAP(isUCI, h)
10073     SWAP(hasOwnBookUCI, h)
10074     SWAP(protocolVersion, h)
10075     SWAP(reuse, h)
10076     SWAP(scoreIsAbsolute, h)
10077     SWAP(timeOdds, h)
10078     SWAP(logo, p)
10079     SWAP(pgnName, p)
10080     SWAP(pvSAN, h)
10081     SWAP(engOptions, p)
10082     SWAP(engInitString, p)
10083     SWAP(computerString, p)
10084     SWAP(features, p)
10085     SWAP(fenOverride, p)
10086     SWAP(NPS, h)
10087     SWAP(accumulateTC, h)
10088     SWAP(host, p)
10089 }
10090
10091 int
10092 SetPlayer (int player, char *p)
10093 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10094     int i;
10095     char buf[MSG_SIZ], *engineName;
10096     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10097     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10098     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10099     if(mnemonic[i]) {
10100         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10101         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10102         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10103         ParseArgsFromString(buf);
10104     }
10105     free(engineName);
10106     return i;
10107 }
10108
10109 char *recentEngines;
10110
10111 void
10112 RecentEngineEvent (int nr)
10113 {
10114     int n;
10115 //    SwapEngines(1); // bump first to second
10116 //    ReplaceEngine(&second, 1); // and load it there
10117     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10118     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10119     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10120         ReplaceEngine(&first, 0);
10121         FloatToFront(&appData.recentEngineList, command[n]);
10122     }
10123 }
10124
10125 int
10126 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10127 {   // determine players from game number
10128     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10129
10130     if(appData.tourneyType == 0) {
10131         roundsPerCycle = (nPlayers - 1) | 1;
10132         pairingsPerRound = nPlayers / 2;
10133     } else if(appData.tourneyType > 0) {
10134         roundsPerCycle = nPlayers - appData.tourneyType;
10135         pairingsPerRound = appData.tourneyType;
10136     }
10137     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10138     gamesPerCycle = gamesPerRound * roundsPerCycle;
10139     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10140     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10141     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10142     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10143     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10144     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10145
10146     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10147     if(appData.roundSync) *syncInterval = gamesPerRound;
10148
10149     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10150
10151     if(appData.tourneyType == 0) {
10152         if(curPairing == (nPlayers-1)/2 ) {
10153             *whitePlayer = curRound;
10154             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10155         } else {
10156             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10157             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10158             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10159             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10160         }
10161     } else if(appData.tourneyType > 1) {
10162         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10163         *whitePlayer = curRound + appData.tourneyType;
10164     } else if(appData.tourneyType > 0) {
10165         *whitePlayer = curPairing;
10166         *blackPlayer = curRound + appData.tourneyType;
10167     }
10168
10169     // take care of white/black alternation per round. 
10170     // For cycles and games this is already taken care of by default, derived from matchGame!
10171     return curRound & 1;
10172 }
10173
10174 int
10175 NextTourneyGame (int nr, int *swapColors)
10176 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10177     char *p, *q;
10178     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10179     FILE *tf;
10180     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10181     tf = fopen(appData.tourneyFile, "r");
10182     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10183     ParseArgsFromFile(tf); fclose(tf);
10184     InitTimeControls(); // TC might be altered from tourney file
10185
10186     nPlayers = CountPlayers(appData.participants); // count participants
10187     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10188     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10189
10190     if(syncInterval) {
10191         p = q = appData.results;
10192         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10193         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10194             DisplayMessage(_("Waiting for other game(s)"),"");
10195             waitingForGame = TRUE;
10196             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10197             return 0;
10198         }
10199         waitingForGame = FALSE;
10200     }
10201
10202     if(appData.tourneyType < 0) {
10203         if(nr>=0 && !pairingReceived) {
10204             char buf[1<<16];
10205             if(pairing.pr == NoProc) {
10206                 if(!appData.pairingEngine[0]) {
10207                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10208                     return 0;
10209                 }
10210                 StartChessProgram(&pairing); // starts the pairing engine
10211             }
10212             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10213             SendToProgram(buf, &pairing);
10214             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10215             SendToProgram(buf, &pairing);
10216             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10217         }
10218         pairingReceived = 0;                              // ... so we continue here 
10219         *swapColors = 0;
10220         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10221         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10222         matchGame = 1; roundNr = nr / syncInterval + 1;
10223     }
10224
10225     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10226
10227     // redefine engines, engine dir, etc.
10228     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10229     if(first.pr == NoProc) {
10230       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10231       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10232     }
10233     if(second.pr == NoProc) {
10234       SwapEngines(1);
10235       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10236       SwapEngines(1);         // and make that valid for second engine by swapping
10237       InitEngine(&second, 1);
10238     }
10239     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10240     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10241     return 1;
10242 }
10243
10244 void
10245 NextMatchGame ()
10246 {   // performs game initialization that does not invoke engines, and then tries to start the game
10247     int res, firstWhite, swapColors = 0;
10248     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10249     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
10250         char buf[MSG_SIZ];
10251         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10252         if(strcmp(buf, currentDebugFile)) { // name has changed
10253             FILE *f = fopen(buf, "w");
10254             if(f) { // if opening the new file failed, just keep using the old one
10255                 ASSIGN(currentDebugFile, buf);
10256                 fclose(debugFP);
10257                 debugFP = f;
10258             }
10259             if(appData.serverFileName) {
10260                 if(serverFP) fclose(serverFP);
10261                 serverFP = fopen(appData.serverFileName, "w");
10262                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10263                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10264             }
10265         }
10266     }
10267     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10268     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10269     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10270     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10271     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10272     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10273     Reset(FALSE, first.pr != NoProc);
10274     res = LoadGameOrPosition(matchGame); // setup game
10275     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10276     if(!res) return; // abort when bad game/pos file
10277     TwoMachinesEvent();
10278 }
10279
10280 void
10281 UserAdjudicationEvent (int result)
10282 {
10283     ChessMove gameResult = GameIsDrawn;
10284
10285     if( result > 0 ) {
10286         gameResult = WhiteWins;
10287     }
10288     else if( result < 0 ) {
10289         gameResult = BlackWins;
10290     }
10291
10292     if( gameMode == TwoMachinesPlay ) {
10293         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10294     }
10295 }
10296
10297
10298 // [HGM] save: calculate checksum of game to make games easily identifiable
10299 int
10300 StringCheckSum (char *s)
10301 {
10302         int i = 0;
10303         if(s==NULL) return 0;
10304         while(*s) i = i*259 + *s++;
10305         return i;
10306 }
10307
10308 int
10309 GameCheckSum ()
10310 {
10311         int i, sum=0;
10312         for(i=backwardMostMove; i<forwardMostMove; i++) {
10313                 sum += pvInfoList[i].depth;
10314                 sum += StringCheckSum(parseList[i]);
10315                 sum += StringCheckSum(commentList[i]);
10316                 sum *= 261;
10317         }
10318         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10319         return sum + StringCheckSum(commentList[i]);
10320 } // end of save patch
10321
10322 void
10323 GameEnds (ChessMove result, char *resultDetails, int whosays)
10324 {
10325     GameMode nextGameMode;
10326     int isIcsGame;
10327     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10328
10329     if(endingGame) return; /* [HGM] crash: forbid recursion */
10330     endingGame = 1;
10331     if(twoBoards) { // [HGM] dual: switch back to one board
10332         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10333         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10334     }
10335     if (appData.debugMode) {
10336       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10337               result, resultDetails ? resultDetails : "(null)", whosays);
10338     }
10339
10340     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10341
10342     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10343         /* If we are playing on ICS, the server decides when the
10344            game is over, but the engine can offer to draw, claim
10345            a draw, or resign.
10346          */
10347 #if ZIPPY
10348         if (appData.zippyPlay && first.initDone) {
10349             if (result == GameIsDrawn) {
10350                 /* In case draw still needs to be claimed */
10351                 SendToICS(ics_prefix);
10352                 SendToICS("draw\n");
10353             } else if (StrCaseStr(resultDetails, "resign")) {
10354                 SendToICS(ics_prefix);
10355                 SendToICS("resign\n");
10356             }
10357         }
10358 #endif
10359         endingGame = 0; /* [HGM] crash */
10360         return;
10361     }
10362
10363     /* If we're loading the game from a file, stop */
10364     if (whosays == GE_FILE) {
10365       (void) StopLoadGameTimer();
10366       gameFileFP = NULL;
10367     }
10368
10369     /* Cancel draw offers */
10370     first.offeredDraw = second.offeredDraw = 0;
10371
10372     /* If this is an ICS game, only ICS can really say it's done;
10373        if not, anyone can. */
10374     isIcsGame = (gameMode == IcsPlayingWhite ||
10375                  gameMode == IcsPlayingBlack ||
10376                  gameMode == IcsObserving    ||
10377                  gameMode == IcsExamining);
10378
10379     if (!isIcsGame || whosays == GE_ICS) {
10380         /* OK -- not an ICS game, or ICS said it was done */
10381         StopClocks();
10382         if (!isIcsGame && !appData.noChessProgram)
10383           SetUserThinkingEnables();
10384
10385         /* [HGM] if a machine claims the game end we verify this claim */
10386         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10387             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10388                 char claimer;
10389                 ChessMove trueResult = (ChessMove) -1;
10390
10391                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10392                                             first.twoMachinesColor[0] :
10393                                             second.twoMachinesColor[0] ;
10394
10395                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10396                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10397                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10398                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10399                 } else
10400                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10401                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10402                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10403                 } else
10404                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10405                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10406                 }
10407
10408                 // now verify win claims, but not in drop games, as we don't understand those yet
10409                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10410                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10411                     (result == WhiteWins && claimer == 'w' ||
10412                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10413                       if (appData.debugMode) {
10414                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10415                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10416                       }
10417                       if(result != trueResult) {
10418                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10419                               result = claimer == 'w' ? BlackWins : WhiteWins;
10420                               resultDetails = buf;
10421                       }
10422                 } else
10423                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10424                     && (forwardMostMove <= backwardMostMove ||
10425                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10426                         (claimer=='b')==(forwardMostMove&1))
10427                                                                                   ) {
10428                       /* [HGM] verify: draws that were not flagged are false claims */
10429                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10430                       result = claimer == 'w' ? BlackWins : WhiteWins;
10431                       resultDetails = buf;
10432                 }
10433                 /* (Claiming a loss is accepted no questions asked!) */
10434             }
10435             /* [HGM] bare: don't allow bare King to win */
10436             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10437                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10438                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10439                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10440                && result != GameIsDrawn)
10441             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10442                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10443                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10444                         if(p >= 0 && p <= (int)WhiteKing) k++;
10445                 }
10446                 if (appData.debugMode) {
10447                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10448                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10449                 }
10450                 if(k <= 1) {
10451                         result = GameIsDrawn;
10452                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10453                         resultDetails = buf;
10454                 }
10455             }
10456         }
10457
10458
10459         if(serverMoves != NULL && !loadFlag) { char c = '=';
10460             if(result==WhiteWins) c = '+';
10461             if(result==BlackWins) c = '-';
10462             if(resultDetails != NULL)
10463                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10464         }
10465         if (resultDetails != NULL) {
10466             gameInfo.result = result;
10467             gameInfo.resultDetails = StrSave(resultDetails);
10468
10469             /* display last move only if game was not loaded from file */
10470             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10471                 DisplayMove(currentMove - 1);
10472
10473             if (forwardMostMove != 0) {
10474                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10475                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10476                                                                 ) {
10477                     if (*appData.saveGameFile != NULLCHAR) {
10478                         SaveGameToFile(appData.saveGameFile, TRUE);
10479                     } else if (appData.autoSaveGames) {
10480                         AutoSaveGame();
10481                     }
10482                     if (*appData.savePositionFile != NULLCHAR) {
10483                         SavePositionToFile(appData.savePositionFile);
10484                     }
10485                 }
10486             }
10487
10488             /* Tell program how game ended in case it is learning */
10489             /* [HGM] Moved this to after saving the PGN, just in case */
10490             /* engine died and we got here through time loss. In that */
10491             /* case we will get a fatal error writing the pipe, which */
10492             /* would otherwise lose us the PGN.                       */
10493             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10494             /* output during GameEnds should never be fatal anymore   */
10495             if (gameMode == MachinePlaysWhite ||
10496                 gameMode == MachinePlaysBlack ||
10497                 gameMode == TwoMachinesPlay ||
10498                 gameMode == IcsPlayingWhite ||
10499                 gameMode == IcsPlayingBlack ||
10500                 gameMode == BeginningOfGame) {
10501                 char buf[MSG_SIZ];
10502                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10503                         resultDetails);
10504                 if (first.pr != NoProc) {
10505                     SendToProgram(buf, &first);
10506                 }
10507                 if (second.pr != NoProc &&
10508                     gameMode == TwoMachinesPlay) {
10509                     SendToProgram(buf, &second);
10510                 }
10511             }
10512         }
10513
10514         if (appData.icsActive) {
10515             if (appData.quietPlay &&
10516                 (gameMode == IcsPlayingWhite ||
10517                  gameMode == IcsPlayingBlack)) {
10518                 SendToICS(ics_prefix);
10519                 SendToICS("set shout 1\n");
10520             }
10521             nextGameMode = IcsIdle;
10522             ics_user_moved = FALSE;
10523             /* clean up premove.  It's ugly when the game has ended and the
10524              * premove highlights are still on the board.
10525              */
10526             if (gotPremove) {
10527               gotPremove = FALSE;
10528               ClearPremoveHighlights();
10529               DrawPosition(FALSE, boards[currentMove]);
10530             }
10531             if (whosays == GE_ICS) {
10532                 switch (result) {
10533                 case WhiteWins:
10534                     if (gameMode == IcsPlayingWhite)
10535                         PlayIcsWinSound();
10536                     else if(gameMode == IcsPlayingBlack)
10537                         PlayIcsLossSound();
10538                     break;
10539                 case BlackWins:
10540                     if (gameMode == IcsPlayingBlack)
10541                         PlayIcsWinSound();
10542                     else if(gameMode == IcsPlayingWhite)
10543                         PlayIcsLossSound();
10544                     break;
10545                 case GameIsDrawn:
10546                     PlayIcsDrawSound();
10547                     break;
10548                 default:
10549                     PlayIcsUnfinishedSound();
10550                 }
10551             }
10552         } else if (gameMode == EditGame ||
10553                    gameMode == PlayFromGameFile ||
10554                    gameMode == AnalyzeMode ||
10555                    gameMode == AnalyzeFile) {
10556             nextGameMode = gameMode;
10557         } else {
10558             nextGameMode = EndOfGame;
10559         }
10560         pausing = FALSE;
10561         ModeHighlight();
10562     } else {
10563         nextGameMode = gameMode;
10564     }
10565
10566     if (appData.noChessProgram) {
10567         gameMode = nextGameMode;
10568         ModeHighlight();
10569         endingGame = 0; /* [HGM] crash */
10570         return;
10571     }
10572
10573     if (first.reuse) {
10574         /* Put first chess program into idle state */
10575         if (first.pr != NoProc &&
10576             (gameMode == MachinePlaysWhite ||
10577              gameMode == MachinePlaysBlack ||
10578              gameMode == TwoMachinesPlay ||
10579              gameMode == IcsPlayingWhite ||
10580              gameMode == IcsPlayingBlack ||
10581              gameMode == BeginningOfGame)) {
10582             SendToProgram("force\n", &first);
10583             if (first.usePing) {
10584               char buf[MSG_SIZ];
10585               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10586               SendToProgram(buf, &first);
10587             }
10588         }
10589     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10590         /* Kill off first chess program */
10591         if (first.isr != NULL)
10592           RemoveInputSource(first.isr);
10593         first.isr = NULL;
10594
10595         if (first.pr != NoProc) {
10596             ExitAnalyzeMode();
10597             DoSleep( appData.delayBeforeQuit );
10598             SendToProgram("quit\n", &first);
10599             DoSleep( appData.delayAfterQuit );
10600             DestroyChildProcess(first.pr, first.useSigterm);
10601         }
10602         first.pr = NoProc;
10603     }
10604     if (second.reuse) {
10605         /* Put second chess program into idle state */
10606         if (second.pr != NoProc &&
10607             gameMode == TwoMachinesPlay) {
10608             SendToProgram("force\n", &second);
10609             if (second.usePing) {
10610               char buf[MSG_SIZ];
10611               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10612               SendToProgram(buf, &second);
10613             }
10614         }
10615     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10616         /* Kill off second chess program */
10617         if (second.isr != NULL)
10618           RemoveInputSource(second.isr);
10619         second.isr = NULL;
10620
10621         if (second.pr != NoProc) {
10622             DoSleep( appData.delayBeforeQuit );
10623             SendToProgram("quit\n", &second);
10624             DoSleep( appData.delayAfterQuit );
10625             DestroyChildProcess(second.pr, second.useSigterm);
10626         }
10627         second.pr = NoProc;
10628     }
10629
10630     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10631         char resChar = '=';
10632         switch (result) {
10633         case WhiteWins:
10634           resChar = '+';
10635           if (first.twoMachinesColor[0] == 'w') {
10636             first.matchWins++;
10637           } else {
10638             second.matchWins++;
10639           }
10640           break;
10641         case BlackWins:
10642           resChar = '-';
10643           if (first.twoMachinesColor[0] == 'b') {
10644             first.matchWins++;
10645           } else {
10646             second.matchWins++;
10647           }
10648           break;
10649         case GameUnfinished:
10650           resChar = ' ';
10651         default:
10652           break;
10653         }
10654
10655         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10656         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10657             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10658             ReserveGame(nextGame, resChar); // sets nextGame
10659             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10660             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10661         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10662
10663         if (nextGame <= appData.matchGames && !abortMatch) {
10664             gameMode = nextGameMode;
10665             matchGame = nextGame; // this will be overruled in tourney mode!
10666             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10667             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10668             endingGame = 0; /* [HGM] crash */
10669             return;
10670         } else {
10671             gameMode = nextGameMode;
10672             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10673                      first.tidy, second.tidy,
10674                      first.matchWins, second.matchWins,
10675                      appData.matchGames - (first.matchWins + second.matchWins));
10676             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10677             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10678             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10679             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10680                 first.twoMachinesColor = "black\n";
10681                 second.twoMachinesColor = "white\n";
10682             } else {
10683                 first.twoMachinesColor = "white\n";
10684                 second.twoMachinesColor = "black\n";
10685             }
10686         }
10687     }
10688     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10689         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10690       ExitAnalyzeMode();
10691     gameMode = nextGameMode;
10692     ModeHighlight();
10693     endingGame = 0;  /* [HGM] crash */
10694     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10695         if(matchMode == TRUE) { // match through command line: exit with or without popup
10696             if(ranking) {
10697                 ToNrEvent(forwardMostMove);
10698                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10699                 else ExitEvent(0);
10700             } else DisplayFatalError(buf, 0, 0);
10701         } else { // match through menu; just stop, with or without popup
10702             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10703             ModeHighlight();
10704             if(ranking){
10705                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10706             } else DisplayNote(buf);
10707       }
10708       if(ranking) free(ranking);
10709     }
10710 }
10711
10712 /* Assumes program was just initialized (initString sent).
10713    Leaves program in force mode. */
10714 void
10715 FeedMovesToProgram (ChessProgramState *cps, int upto)
10716 {
10717     int i;
10718
10719     if (appData.debugMode)
10720       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10721               startedFromSetupPosition ? "position and " : "",
10722               backwardMostMove, upto, cps->which);
10723     if(currentlyInitializedVariant != gameInfo.variant) {
10724       char buf[MSG_SIZ];
10725         // [HGM] variantswitch: make engine aware of new variant
10726         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10727                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10728         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10729         SendToProgram(buf, cps);
10730         currentlyInitializedVariant = gameInfo.variant;
10731     }
10732     SendToProgram("force\n", cps);
10733     if (startedFromSetupPosition) {
10734         SendBoard(cps, backwardMostMove);
10735     if (appData.debugMode) {
10736         fprintf(debugFP, "feedMoves\n");
10737     }
10738     }
10739     for (i = backwardMostMove; i < upto; i++) {
10740         SendMoveToProgram(i, cps);
10741     }
10742 }
10743
10744
10745 int
10746 ResurrectChessProgram ()
10747 {
10748      /* The chess program may have exited.
10749         If so, restart it and feed it all the moves made so far. */
10750     static int doInit = 0;
10751
10752     if (appData.noChessProgram) return 1;
10753
10754     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10755         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10756         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10757         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10758     } else {
10759         if (first.pr != NoProc) return 1;
10760         StartChessProgram(&first);
10761     }
10762     InitChessProgram(&first, FALSE);
10763     FeedMovesToProgram(&first, currentMove);
10764
10765     if (!first.sendTime) {
10766         /* can't tell gnuchess what its clock should read,
10767            so we bow to its notion. */
10768         ResetClocks();
10769         timeRemaining[0][currentMove] = whiteTimeRemaining;
10770         timeRemaining[1][currentMove] = blackTimeRemaining;
10771     }
10772
10773     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10774                 appData.icsEngineAnalyze) && first.analysisSupport) {
10775       SendToProgram("analyze\n", &first);
10776       first.analyzing = TRUE;
10777     }
10778     return 1;
10779 }
10780
10781 /*
10782  * Button procedures
10783  */
10784 void
10785 Reset (int redraw, int init)
10786 {
10787     int i;
10788
10789     if (appData.debugMode) {
10790         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10791                 redraw, init, gameMode);
10792     }
10793     CleanupTail(); // [HGM] vari: delete any stored variations
10794     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10795     pausing = pauseExamInvalid = FALSE;
10796     startedFromSetupPosition = blackPlaysFirst = FALSE;
10797     firstMove = TRUE;
10798     whiteFlag = blackFlag = FALSE;
10799     userOfferedDraw = FALSE;
10800     hintRequested = bookRequested = FALSE;
10801     first.maybeThinking = FALSE;
10802     second.maybeThinking = FALSE;
10803     first.bookSuspend = FALSE; // [HGM] book
10804     second.bookSuspend = FALSE;
10805     thinkOutput[0] = NULLCHAR;
10806     lastHint[0] = NULLCHAR;
10807     ClearGameInfo(&gameInfo);
10808     gameInfo.variant = StringToVariant(appData.variant);
10809     ics_user_moved = ics_clock_paused = FALSE;
10810     ics_getting_history = H_FALSE;
10811     ics_gamenum = -1;
10812     white_holding[0] = black_holding[0] = NULLCHAR;
10813     ClearProgramStats();
10814     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10815
10816     ResetFrontEnd();
10817     ClearHighlights();
10818     flipView = appData.flipView;
10819     ClearPremoveHighlights();
10820     gotPremove = FALSE;
10821     alarmSounded = FALSE;
10822
10823     GameEnds(EndOfFile, NULL, GE_PLAYER);
10824     if(appData.serverMovesName != NULL) {
10825         /* [HGM] prepare to make moves file for broadcasting */
10826         clock_t t = clock();
10827         if(serverMoves != NULL) fclose(serverMoves);
10828         serverMoves = fopen(appData.serverMovesName, "r");
10829         if(serverMoves != NULL) {
10830             fclose(serverMoves);
10831             /* delay 15 sec before overwriting, so all clients can see end */
10832             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10833         }
10834         serverMoves = fopen(appData.serverMovesName, "w");
10835     }
10836
10837     ExitAnalyzeMode();
10838     gameMode = BeginningOfGame;
10839     ModeHighlight();
10840     if(appData.icsActive) gameInfo.variant = VariantNormal;
10841     currentMove = forwardMostMove = backwardMostMove = 0;
10842     MarkTargetSquares(1);
10843     InitPosition(redraw);
10844     for (i = 0; i < MAX_MOVES; i++) {
10845         if (commentList[i] != NULL) {
10846             free(commentList[i]);
10847             commentList[i] = NULL;
10848         }
10849     }
10850     ResetClocks();
10851     timeRemaining[0][0] = whiteTimeRemaining;
10852     timeRemaining[1][0] = blackTimeRemaining;
10853
10854     if (first.pr == NoProc) {
10855         StartChessProgram(&first);
10856     }
10857     if (init) {
10858             InitChessProgram(&first, startedFromSetupPosition);
10859     }
10860     DisplayTitle("");
10861     DisplayMessage("", "");
10862     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10863     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10864     ClearMap();        // [HGM] exclude: invalidate map
10865 }
10866
10867 void
10868 AutoPlayGameLoop ()
10869 {
10870     for (;;) {
10871         if (!AutoPlayOneMove())
10872           return;
10873         if (matchMode || appData.timeDelay == 0)
10874           continue;
10875         if (appData.timeDelay < 0)
10876           return;
10877         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10878         break;
10879     }
10880 }
10881
10882
10883 int
10884 AutoPlayOneMove ()
10885 {
10886     int fromX, fromY, toX, toY;
10887
10888     if (appData.debugMode) {
10889       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10890     }
10891
10892     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10893       return FALSE;
10894
10895     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10896       pvInfoList[currentMove].depth = programStats.depth;
10897       pvInfoList[currentMove].score = programStats.score;
10898       pvInfoList[currentMove].time  = 0;
10899       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10900     }
10901
10902     if (currentMove >= forwardMostMove) {
10903       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10904 //      gameMode = EndOfGame;
10905 //      ModeHighlight();
10906
10907       /* [AS] Clear current move marker at the end of a game */
10908       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10909
10910       return FALSE;
10911     }
10912
10913     toX = moveList[currentMove][2] - AAA;
10914     toY = moveList[currentMove][3] - ONE;
10915
10916     if (moveList[currentMove][1] == '@') {
10917         if (appData.highlightLastMove) {
10918             SetHighlights(-1, -1, toX, toY);
10919         }
10920     } else {
10921         fromX = moveList[currentMove][0] - AAA;
10922         fromY = moveList[currentMove][1] - ONE;
10923
10924         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10925
10926         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10927
10928         if (appData.highlightLastMove) {
10929             SetHighlights(fromX, fromY, toX, toY);
10930         }
10931     }
10932     DisplayMove(currentMove);
10933     SendMoveToProgram(currentMove++, &first);
10934     DisplayBothClocks();
10935     DrawPosition(FALSE, boards[currentMove]);
10936     // [HGM] PV info: always display, routine tests if empty
10937     DisplayComment(currentMove - 1, commentList[currentMove]);
10938     return TRUE;
10939 }
10940
10941
10942 int
10943 LoadGameOneMove (ChessMove readAhead)
10944 {
10945     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10946     char promoChar = NULLCHAR;
10947     ChessMove moveType;
10948     char move[MSG_SIZ];
10949     char *p, *q;
10950
10951     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10952         gameMode != AnalyzeMode && gameMode != Training) {
10953         gameFileFP = NULL;
10954         return FALSE;
10955     }
10956
10957     yyboardindex = forwardMostMove;
10958     if (readAhead != EndOfFile) {
10959       moveType = readAhead;
10960     } else {
10961       if (gameFileFP == NULL)
10962           return FALSE;
10963       moveType = (ChessMove) Myylex();
10964     }
10965
10966     done = FALSE;
10967     switch (moveType) {
10968       case Comment:
10969         if (appData.debugMode)
10970           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10971         p = yy_text;
10972
10973         /* append the comment but don't display it */
10974         AppendComment(currentMove, p, FALSE);
10975         return TRUE;
10976
10977       case WhiteCapturesEnPassant:
10978       case BlackCapturesEnPassant:
10979       case WhitePromotion:
10980       case BlackPromotion:
10981       case WhiteNonPromotion:
10982       case BlackNonPromotion:
10983       case NormalMove:
10984       case WhiteKingSideCastle:
10985       case WhiteQueenSideCastle:
10986       case BlackKingSideCastle:
10987       case BlackQueenSideCastle:
10988       case WhiteKingSideCastleWild:
10989       case WhiteQueenSideCastleWild:
10990       case BlackKingSideCastleWild:
10991       case BlackQueenSideCastleWild:
10992       /* PUSH Fabien */
10993       case WhiteHSideCastleFR:
10994       case WhiteASideCastleFR:
10995       case BlackHSideCastleFR:
10996       case BlackASideCastleFR:
10997       /* POP Fabien */
10998         if (appData.debugMode)
10999           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11000         fromX = currentMoveString[0] - AAA;
11001         fromY = currentMoveString[1] - ONE;
11002         toX = currentMoveString[2] - AAA;
11003         toY = currentMoveString[3] - ONE;
11004         promoChar = currentMoveString[4];
11005         break;
11006
11007       case WhiteDrop:
11008       case BlackDrop:
11009         if (appData.debugMode)
11010           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11011         fromX = moveType == WhiteDrop ?
11012           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11013         (int) CharToPiece(ToLower(currentMoveString[0]));
11014         fromY = DROP_RANK;
11015         toX = currentMoveString[2] - AAA;
11016         toY = currentMoveString[3] - ONE;
11017         break;
11018
11019       case WhiteWins:
11020       case BlackWins:
11021       case GameIsDrawn:
11022       case GameUnfinished:
11023         if (appData.debugMode)
11024           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11025         p = strchr(yy_text, '{');
11026         if (p == NULL) p = strchr(yy_text, '(');
11027         if (p == NULL) {
11028             p = yy_text;
11029             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11030         } else {
11031             q = strchr(p, *p == '{' ? '}' : ')');
11032             if (q != NULL) *q = NULLCHAR;
11033             p++;
11034         }
11035         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11036         GameEnds(moveType, p, GE_FILE);
11037         done = TRUE;
11038         if (cmailMsgLoaded) {
11039             ClearHighlights();
11040             flipView = WhiteOnMove(currentMove);
11041             if (moveType == GameUnfinished) flipView = !flipView;
11042             if (appData.debugMode)
11043               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11044         }
11045         break;
11046
11047       case EndOfFile:
11048         if (appData.debugMode)
11049           fprintf(debugFP, "Parser hit end of file\n");
11050         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11051           case MT_NONE:
11052           case MT_CHECK:
11053             break;
11054           case MT_CHECKMATE:
11055           case MT_STAINMATE:
11056             if (WhiteOnMove(currentMove)) {
11057                 GameEnds(BlackWins, "Black mates", GE_FILE);
11058             } else {
11059                 GameEnds(WhiteWins, "White mates", GE_FILE);
11060             }
11061             break;
11062           case MT_STALEMATE:
11063             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11064             break;
11065         }
11066         done = TRUE;
11067         break;
11068
11069       case MoveNumberOne:
11070         if (lastLoadGameStart == GNUChessGame) {
11071             /* GNUChessGames have numbers, but they aren't move numbers */
11072             if (appData.debugMode)
11073               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11074                       yy_text, (int) moveType);
11075             return LoadGameOneMove(EndOfFile); /* tail recursion */
11076         }
11077         /* else fall thru */
11078
11079       case XBoardGame:
11080       case GNUChessGame:
11081       case PGNTag:
11082         /* Reached start of next game in file */
11083         if (appData.debugMode)
11084           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11085         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11086           case MT_NONE:
11087           case MT_CHECK:
11088             break;
11089           case MT_CHECKMATE:
11090           case MT_STAINMATE:
11091             if (WhiteOnMove(currentMove)) {
11092                 GameEnds(BlackWins, "Black mates", GE_FILE);
11093             } else {
11094                 GameEnds(WhiteWins, "White mates", GE_FILE);
11095             }
11096             break;
11097           case MT_STALEMATE:
11098             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11099             break;
11100         }
11101         done = TRUE;
11102         break;
11103
11104       case PositionDiagram:     /* should not happen; ignore */
11105       case ElapsedTime:         /* ignore */
11106       case NAG:                 /* ignore */
11107         if (appData.debugMode)
11108           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11109                   yy_text, (int) moveType);
11110         return LoadGameOneMove(EndOfFile); /* tail recursion */
11111
11112       case IllegalMove:
11113         if (appData.testLegality) {
11114             if (appData.debugMode)
11115               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11116             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11117                     (forwardMostMove / 2) + 1,
11118                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11119             DisplayError(move, 0);
11120             done = TRUE;
11121         } else {
11122             if (appData.debugMode)
11123               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11124                       yy_text, currentMoveString);
11125             fromX = currentMoveString[0] - AAA;
11126             fromY = currentMoveString[1] - ONE;
11127             toX = currentMoveString[2] - AAA;
11128             toY = currentMoveString[3] - ONE;
11129             promoChar = currentMoveString[4];
11130         }
11131         break;
11132
11133       case AmbiguousMove:
11134         if (appData.debugMode)
11135           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11136         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11137                 (forwardMostMove / 2) + 1,
11138                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11139         DisplayError(move, 0);
11140         done = TRUE;
11141         break;
11142
11143       default:
11144       case ImpossibleMove:
11145         if (appData.debugMode)
11146           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11147         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11148                 (forwardMostMove / 2) + 1,
11149                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11150         DisplayError(move, 0);
11151         done = TRUE;
11152         break;
11153     }
11154
11155     if (done) {
11156         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11157             DrawPosition(FALSE, boards[currentMove]);
11158             DisplayBothClocks();
11159             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11160               DisplayComment(currentMove - 1, commentList[currentMove]);
11161         }
11162         (void) StopLoadGameTimer();
11163         gameFileFP = NULL;
11164         cmailOldMove = forwardMostMove;
11165         return FALSE;
11166     } else {
11167         /* currentMoveString is set as a side-effect of yylex */
11168
11169         thinkOutput[0] = NULLCHAR;
11170         MakeMove(fromX, fromY, toX, toY, promoChar);
11171         currentMove = forwardMostMove;
11172         return TRUE;
11173     }
11174 }
11175
11176 /* Load the nth game from the given file */
11177 int
11178 LoadGameFromFile (char *filename, int n, char *title, int useList)
11179 {
11180     FILE *f;
11181     char buf[MSG_SIZ];
11182
11183     if (strcmp(filename, "-") == 0) {
11184         f = stdin;
11185         title = "stdin";
11186     } else {
11187         f = fopen(filename, "rb");
11188         if (f == NULL) {
11189           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11190             DisplayError(buf, errno);
11191             return FALSE;
11192         }
11193     }
11194     if (fseek(f, 0, 0) == -1) {
11195         /* f is not seekable; probably a pipe */
11196         useList = FALSE;
11197     }
11198     if (useList && n == 0) {
11199         int error = GameListBuild(f);
11200         if (error) {
11201             DisplayError(_("Cannot build game list"), error);
11202         } else if (!ListEmpty(&gameList) &&
11203                    ((ListGame *) gameList.tailPred)->number > 1) {
11204             GameListPopUp(f, title);
11205             return TRUE;
11206         }
11207         GameListDestroy();
11208         n = 1;
11209     }
11210     if (n == 0) n = 1;
11211     return LoadGame(f, n, title, FALSE);
11212 }
11213
11214
11215 void
11216 MakeRegisteredMove ()
11217 {
11218     int fromX, fromY, toX, toY;
11219     char promoChar;
11220     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11221         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11222           case CMAIL_MOVE:
11223           case CMAIL_DRAW:
11224             if (appData.debugMode)
11225               fprintf(debugFP, "Restoring %s for game %d\n",
11226                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11227
11228             thinkOutput[0] = NULLCHAR;
11229             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11230             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11231             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11232             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11233             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11234             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11235             MakeMove(fromX, fromY, toX, toY, promoChar);
11236             ShowMove(fromX, fromY, toX, toY);
11237
11238             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11239               case MT_NONE:
11240               case MT_CHECK:
11241                 break;
11242
11243               case MT_CHECKMATE:
11244               case MT_STAINMATE:
11245                 if (WhiteOnMove(currentMove)) {
11246                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11247                 } else {
11248                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11249                 }
11250                 break;
11251
11252               case MT_STALEMATE:
11253                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11254                 break;
11255             }
11256
11257             break;
11258
11259           case CMAIL_RESIGN:
11260             if (WhiteOnMove(currentMove)) {
11261                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11262             } else {
11263                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11264             }
11265             break;
11266
11267           case CMAIL_ACCEPT:
11268             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11269             break;
11270
11271           default:
11272             break;
11273         }
11274     }
11275
11276     return;
11277 }
11278
11279 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11280 int
11281 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11282 {
11283     int retVal;
11284
11285     if (gameNumber > nCmailGames) {
11286         DisplayError(_("No more games in this message"), 0);
11287         return FALSE;
11288     }
11289     if (f == lastLoadGameFP) {
11290         int offset = gameNumber - lastLoadGameNumber;
11291         if (offset == 0) {
11292             cmailMsg[0] = NULLCHAR;
11293             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11294                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11295                 nCmailMovesRegistered--;
11296             }
11297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11298             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11299                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11300             }
11301         } else {
11302             if (! RegisterMove()) return FALSE;
11303         }
11304     }
11305
11306     retVal = LoadGame(f, gameNumber, title, useList);
11307
11308     /* Make move registered during previous look at this game, if any */
11309     MakeRegisteredMove();
11310
11311     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11312         commentList[currentMove]
11313           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11314         DisplayComment(currentMove - 1, commentList[currentMove]);
11315     }
11316
11317     return retVal;
11318 }
11319
11320 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11321 int
11322 ReloadGame (int offset)
11323 {
11324     int gameNumber = lastLoadGameNumber + offset;
11325     if (lastLoadGameFP == NULL) {
11326         DisplayError(_("No game has been loaded yet"), 0);
11327         return FALSE;
11328     }
11329     if (gameNumber <= 0) {
11330         DisplayError(_("Can't back up any further"), 0);
11331         return FALSE;
11332     }
11333     if (cmailMsgLoaded) {
11334         return CmailLoadGame(lastLoadGameFP, gameNumber,
11335                              lastLoadGameTitle, lastLoadGameUseList);
11336     } else {
11337         return LoadGame(lastLoadGameFP, gameNumber,
11338                         lastLoadGameTitle, lastLoadGameUseList);
11339     }
11340 }
11341
11342 int keys[EmptySquare+1];
11343
11344 int
11345 PositionMatches (Board b1, Board b2)
11346 {
11347     int r, f, sum=0;
11348     switch(appData.searchMode) {
11349         case 1: return CompareWithRights(b1, b2);
11350         case 2:
11351             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11352                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11353             }
11354             return TRUE;
11355         case 3:
11356             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11358                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11359             }
11360             return sum==0;
11361         case 4:
11362             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11363                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11364             }
11365             return sum==0;
11366     }
11367     return TRUE;
11368 }
11369
11370 #define Q_PROMO  4
11371 #define Q_EP     3
11372 #define Q_BCASTL 2
11373 #define Q_WCASTL 1
11374
11375 int pieceList[256], quickBoard[256];
11376 ChessSquare pieceType[256] = { EmptySquare };
11377 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11378 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11379 int soughtTotal, turn;
11380 Boolean epOK, flipSearch;
11381
11382 typedef struct {
11383     unsigned char piece, to;
11384 } Move;
11385
11386 #define DSIZE (250000)
11387
11388 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11389 Move *moveDatabase = initialSpace;
11390 unsigned int movePtr, dataSize = DSIZE;
11391
11392 int
11393 MakePieceList (Board board, int *counts)
11394 {
11395     int r, f, n=Q_PROMO, total=0;
11396     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11397     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11398         int sq = f + (r<<4);
11399         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11400             quickBoard[sq] = ++n;
11401             pieceList[n] = sq;
11402             pieceType[n] = board[r][f];
11403             counts[board[r][f]]++;
11404             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11405             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11406             total++;
11407         }
11408     }
11409     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11410     return total;
11411 }
11412
11413 void
11414 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11415 {
11416     int sq = fromX + (fromY<<4);
11417     int piece = quickBoard[sq];
11418     quickBoard[sq] = 0;
11419     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11420     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11421         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11422         moveDatabase[movePtr++].piece = Q_WCASTL;
11423         quickBoard[sq] = piece;
11424         piece = quickBoard[from]; quickBoard[from] = 0;
11425         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11426     } else
11427     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11428         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11429         moveDatabase[movePtr++].piece = Q_BCASTL;
11430         quickBoard[sq] = piece;
11431         piece = quickBoard[from]; quickBoard[from] = 0;
11432         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11433     } else
11434     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11435         quickBoard[(fromY<<4)+toX] = 0;
11436         moveDatabase[movePtr].piece = Q_EP;
11437         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11438         moveDatabase[movePtr].to = sq;
11439     } else
11440     if(promoPiece != pieceType[piece]) {
11441         moveDatabase[movePtr++].piece = Q_PROMO;
11442         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11443     }
11444     moveDatabase[movePtr].piece = piece;
11445     quickBoard[sq] = piece;
11446     movePtr++;
11447 }
11448
11449 int
11450 PackGame (Board board)
11451 {
11452     Move *newSpace = NULL;
11453     moveDatabase[movePtr].piece = 0; // terminate previous game
11454     if(movePtr > dataSize) {
11455         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11456         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11457         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11458         if(newSpace) {
11459             int i;
11460             Move *p = moveDatabase, *q = newSpace;
11461             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11462             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11463             moveDatabase = newSpace;
11464         } else { // calloc failed, we must be out of memory. Too bad...
11465             dataSize = 0; // prevent calloc events for all subsequent games
11466             return 0;     // and signal this one isn't cached
11467         }
11468     }
11469     movePtr++;
11470     MakePieceList(board, counts);
11471     return movePtr;
11472 }
11473
11474 int
11475 QuickCompare (Board board, int *minCounts, int *maxCounts)
11476 {   // compare according to search mode
11477     int r, f;
11478     switch(appData.searchMode)
11479     {
11480       case 1: // exact position match
11481         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11482         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11483             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11484         }
11485         break;
11486       case 2: // can have extra material on empty squares
11487         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11488             if(board[r][f] == EmptySquare) continue;
11489             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11490         }
11491         break;
11492       case 3: // material with exact Pawn structure
11493         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11494             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11495             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11496         } // fall through to material comparison
11497       case 4: // exact material
11498         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11499         break;
11500       case 6: // material range with given imbalance
11501         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11502         // fall through to range comparison
11503       case 5: // material range
11504         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11505     }
11506     return TRUE;
11507 }
11508
11509 int
11510 QuickScan (Board board, Move *move)
11511 {   // reconstruct game,and compare all positions in it
11512     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11513     do {
11514         int piece = move->piece;
11515         int to = move->to, from = pieceList[piece];
11516         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11517           if(!piece) return -1;
11518           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11519             piece = (++move)->piece;
11520             from = pieceList[piece];
11521             counts[pieceType[piece]]--;
11522             pieceType[piece] = (ChessSquare) move->to;
11523             counts[move->to]++;
11524           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11525             counts[pieceType[quickBoard[to]]]--;
11526             quickBoard[to] = 0; total--;
11527             move++;
11528             continue;
11529           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11530             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11531             from  = pieceList[piece]; // so this must be King
11532             quickBoard[from] = 0;
11533             quickBoard[to] = piece;
11534             pieceList[piece] = to;
11535             move++;
11536             continue;
11537           }
11538         }
11539         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11540         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11541         quickBoard[from] = 0;
11542         quickBoard[to] = piece;
11543         pieceList[piece] = to;
11544         cnt++; turn ^= 3;
11545         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11546            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11547            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11548                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11549           ) {
11550             static int lastCounts[EmptySquare+1];
11551             int i;
11552             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11553             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11554         } else stretch = 0;
11555         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11556         move++;
11557     } while(1);
11558 }
11559
11560 void
11561 InitSearch ()
11562 {
11563     int r, f;
11564     flipSearch = FALSE;
11565     CopyBoard(soughtBoard, boards[currentMove]);
11566     soughtTotal = MakePieceList(soughtBoard, maxSought);
11567     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11568     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11569     CopyBoard(reverseBoard, boards[currentMove]);
11570     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11571         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11572         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11573         reverseBoard[r][f] = piece;
11574     }
11575     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11576     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11577     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11578                  || (boards[currentMove][CASTLING][2] == NoRights || 
11579                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11580                  && (boards[currentMove][CASTLING][5] == NoRights || 
11581                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11582       ) {
11583         flipSearch = TRUE;
11584         CopyBoard(flipBoard, soughtBoard);
11585         CopyBoard(rotateBoard, reverseBoard);
11586         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11587             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11588             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11589         }
11590     }
11591     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11592     if(appData.searchMode >= 5) {
11593         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11594         MakePieceList(soughtBoard, minSought);
11595         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11596     }
11597     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11598         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11599 }
11600
11601 GameInfo dummyInfo;
11602
11603 int
11604 GameContainsPosition (FILE *f, ListGame *lg)
11605 {
11606     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11607     int fromX, fromY, toX, toY;
11608     char promoChar;
11609     static int initDone=FALSE;
11610
11611     // weed out games based on numerical tag comparison
11612     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11613     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11614     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11615     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11616     if(!initDone) {
11617         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11618         initDone = TRUE;
11619     }
11620     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11621     else CopyBoard(boards[scratch], initialPosition); // default start position
11622     if(lg->moves) {
11623         turn = btm + 1;
11624         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11625         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11626     }
11627     if(btm) plyNr++;
11628     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11629     fseek(f, lg->offset, 0);
11630     yynewfile(f);
11631     while(1) {
11632         yyboardindex = scratch;
11633         quickFlag = plyNr+1;
11634         next = Myylex();
11635         quickFlag = 0;
11636         switch(next) {
11637             case PGNTag:
11638                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11639             default:
11640                 continue;
11641
11642             case XBoardGame:
11643             case GNUChessGame:
11644                 if(plyNr) return -1; // after we have seen moves, this is for new game
11645               continue;
11646
11647             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11648             case ImpossibleMove:
11649             case WhiteWins: // game ends here with these four
11650             case BlackWins:
11651             case GameIsDrawn:
11652             case GameUnfinished:
11653                 return -1;
11654
11655             case IllegalMove:
11656                 if(appData.testLegality) return -1;
11657             case WhiteCapturesEnPassant:
11658             case BlackCapturesEnPassant:
11659             case WhitePromotion:
11660             case BlackPromotion:
11661             case WhiteNonPromotion:
11662             case BlackNonPromotion:
11663             case NormalMove:
11664             case WhiteKingSideCastle:
11665             case WhiteQueenSideCastle:
11666             case BlackKingSideCastle:
11667             case BlackQueenSideCastle:
11668             case WhiteKingSideCastleWild:
11669             case WhiteQueenSideCastleWild:
11670             case BlackKingSideCastleWild:
11671             case BlackQueenSideCastleWild:
11672             case WhiteHSideCastleFR:
11673             case WhiteASideCastleFR:
11674             case BlackHSideCastleFR:
11675             case BlackASideCastleFR:
11676                 fromX = currentMoveString[0] - AAA;
11677                 fromY = currentMoveString[1] - ONE;
11678                 toX = currentMoveString[2] - AAA;
11679                 toY = currentMoveString[3] - ONE;
11680                 promoChar = currentMoveString[4];
11681                 break;
11682             case WhiteDrop:
11683             case BlackDrop:
11684                 fromX = next == WhiteDrop ?
11685                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11686                   (int) CharToPiece(ToLower(currentMoveString[0]));
11687                 fromY = DROP_RANK;
11688                 toX = currentMoveString[2] - AAA;
11689                 toY = currentMoveString[3] - ONE;
11690                 promoChar = 0;
11691                 break;
11692         }
11693         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11694         plyNr++;
11695         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11696         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11697         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11698         if(appData.findMirror) {
11699             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11700             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11701         }
11702     }
11703 }
11704
11705 /* Load the nth game from open file f */
11706 int
11707 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11708 {
11709     ChessMove cm;
11710     char buf[MSG_SIZ];
11711     int gn = gameNumber;
11712     ListGame *lg = NULL;
11713     int numPGNTags = 0;
11714     int err, pos = -1;
11715     GameMode oldGameMode;
11716     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11717
11718     if (appData.debugMode)
11719         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11720
11721     if (gameMode == Training )
11722         SetTrainingModeOff();
11723
11724     oldGameMode = gameMode;
11725     if (gameMode != BeginningOfGame) {
11726       Reset(FALSE, TRUE);
11727     }
11728
11729     gameFileFP = f;
11730     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11731         fclose(lastLoadGameFP);
11732     }
11733
11734     if (useList) {
11735         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11736
11737         if (lg) {
11738             fseek(f, lg->offset, 0);
11739             GameListHighlight(gameNumber);
11740             pos = lg->position;
11741             gn = 1;
11742         }
11743         else {
11744             DisplayError(_("Game number out of range"), 0);
11745             return FALSE;
11746         }
11747     } else {
11748         GameListDestroy();
11749         if (fseek(f, 0, 0) == -1) {
11750             if (f == lastLoadGameFP ?
11751                 gameNumber == lastLoadGameNumber + 1 :
11752                 gameNumber == 1) {
11753                 gn = 1;
11754             } else {
11755                 DisplayError(_("Can't seek on game file"), 0);
11756                 return FALSE;
11757             }
11758         }
11759     }
11760     lastLoadGameFP = f;
11761     lastLoadGameNumber = gameNumber;
11762     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11763     lastLoadGameUseList = useList;
11764
11765     yynewfile(f);
11766
11767     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11768       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11769                 lg->gameInfo.black);
11770             DisplayTitle(buf);
11771     } else if (*title != NULLCHAR) {
11772         if (gameNumber > 1) {
11773           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11774             DisplayTitle(buf);
11775         } else {
11776             DisplayTitle(title);
11777         }
11778     }
11779
11780     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11781         gameMode = PlayFromGameFile;
11782         ModeHighlight();
11783     }
11784
11785     currentMove = forwardMostMove = backwardMostMove = 0;
11786     CopyBoard(boards[0], initialPosition);
11787     StopClocks();
11788
11789     /*
11790      * Skip the first gn-1 games in the file.
11791      * Also skip over anything that precedes an identifiable
11792      * start of game marker, to avoid being confused by
11793      * garbage at the start of the file.  Currently
11794      * recognized start of game markers are the move number "1",
11795      * the pattern "gnuchess .* game", the pattern
11796      * "^[#;%] [^ ]* game file", and a PGN tag block.
11797      * A game that starts with one of the latter two patterns
11798      * will also have a move number 1, possibly
11799      * following a position diagram.
11800      * 5-4-02: Let's try being more lenient and allowing a game to
11801      * start with an unnumbered move.  Does that break anything?
11802      */
11803     cm = lastLoadGameStart = EndOfFile;
11804     while (gn > 0) {
11805         yyboardindex = forwardMostMove;
11806         cm = (ChessMove) Myylex();
11807         switch (cm) {
11808           case EndOfFile:
11809             if (cmailMsgLoaded) {
11810                 nCmailGames = CMAIL_MAX_GAMES - gn;
11811             } else {
11812                 Reset(TRUE, TRUE);
11813                 DisplayError(_("Game not found in file"), 0);
11814             }
11815             return FALSE;
11816
11817           case GNUChessGame:
11818           case XBoardGame:
11819             gn--;
11820             lastLoadGameStart = cm;
11821             break;
11822
11823           case MoveNumberOne:
11824             switch (lastLoadGameStart) {
11825               case GNUChessGame:
11826               case XBoardGame:
11827               case PGNTag:
11828                 break;
11829               case MoveNumberOne:
11830               case EndOfFile:
11831                 gn--;           /* count this game */
11832                 lastLoadGameStart = cm;
11833                 break;
11834               default:
11835                 /* impossible */
11836                 break;
11837             }
11838             break;
11839
11840           case PGNTag:
11841             switch (lastLoadGameStart) {
11842               case GNUChessGame:
11843               case PGNTag:
11844               case MoveNumberOne:
11845               case EndOfFile:
11846                 gn--;           /* count this game */
11847                 lastLoadGameStart = cm;
11848                 break;
11849               case XBoardGame:
11850                 lastLoadGameStart = cm; /* game counted already */
11851                 break;
11852               default:
11853                 /* impossible */
11854                 break;
11855             }
11856             if (gn > 0) {
11857                 do {
11858                     yyboardindex = forwardMostMove;
11859                     cm = (ChessMove) Myylex();
11860                 } while (cm == PGNTag || cm == Comment);
11861             }
11862             break;
11863
11864           case WhiteWins:
11865           case BlackWins:
11866           case GameIsDrawn:
11867             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11868                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11869                     != CMAIL_OLD_RESULT) {
11870                     nCmailResults ++ ;
11871                     cmailResult[  CMAIL_MAX_GAMES
11872                                 - gn - 1] = CMAIL_OLD_RESULT;
11873                 }
11874             }
11875             break;
11876
11877           case NormalMove:
11878             /* Only a NormalMove can be at the start of a game
11879              * without a position diagram. */
11880             if (lastLoadGameStart == EndOfFile ) {
11881               gn--;
11882               lastLoadGameStart = MoveNumberOne;
11883             }
11884             break;
11885
11886           default:
11887             break;
11888         }
11889     }
11890
11891     if (appData.debugMode)
11892       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11893
11894     if (cm == XBoardGame) {
11895         /* Skip any header junk before position diagram and/or move 1 */
11896         for (;;) {
11897             yyboardindex = forwardMostMove;
11898             cm = (ChessMove) Myylex();
11899
11900             if (cm == EndOfFile ||
11901                 cm == GNUChessGame || cm == XBoardGame) {
11902                 /* Empty game; pretend end-of-file and handle later */
11903                 cm = EndOfFile;
11904                 break;
11905             }
11906
11907             if (cm == MoveNumberOne || cm == PositionDiagram ||
11908                 cm == PGNTag || cm == Comment)
11909               break;
11910         }
11911     } else if (cm == GNUChessGame) {
11912         if (gameInfo.event != NULL) {
11913             free(gameInfo.event);
11914         }
11915         gameInfo.event = StrSave(yy_text);
11916     }
11917
11918     startedFromSetupPosition = FALSE;
11919     while (cm == PGNTag) {
11920         if (appData.debugMode)
11921           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11922         err = ParsePGNTag(yy_text, &gameInfo);
11923         if (!err) numPGNTags++;
11924
11925         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11926         if(gameInfo.variant != oldVariant) {
11927             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11928             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11929             InitPosition(TRUE);
11930             oldVariant = gameInfo.variant;
11931             if (appData.debugMode)
11932               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11933         }
11934
11935
11936         if (gameInfo.fen != NULL) {
11937           Board initial_position;
11938           startedFromSetupPosition = TRUE;
11939           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11940             Reset(TRUE, TRUE);
11941             DisplayError(_("Bad FEN position in file"), 0);
11942             return FALSE;
11943           }
11944           CopyBoard(boards[0], initial_position);
11945           if (blackPlaysFirst) {
11946             currentMove = forwardMostMove = backwardMostMove = 1;
11947             CopyBoard(boards[1], initial_position);
11948             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11949             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11950             timeRemaining[0][1] = whiteTimeRemaining;
11951             timeRemaining[1][1] = blackTimeRemaining;
11952             if (commentList[0] != NULL) {
11953               commentList[1] = commentList[0];
11954               commentList[0] = NULL;
11955             }
11956           } else {
11957             currentMove = forwardMostMove = backwardMostMove = 0;
11958           }
11959           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11960           {   int i;
11961               initialRulePlies = FENrulePlies;
11962               for( i=0; i< nrCastlingRights; i++ )
11963                   initialRights[i] = initial_position[CASTLING][i];
11964           }
11965           yyboardindex = forwardMostMove;
11966           free(gameInfo.fen);
11967           gameInfo.fen = NULL;
11968         }
11969
11970         yyboardindex = forwardMostMove;
11971         cm = (ChessMove) Myylex();
11972
11973         /* Handle comments interspersed among the tags */
11974         while (cm == Comment) {
11975             char *p;
11976             if (appData.debugMode)
11977               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11978             p = yy_text;
11979             AppendComment(currentMove, p, FALSE);
11980             yyboardindex = forwardMostMove;
11981             cm = (ChessMove) Myylex();
11982         }
11983     }
11984
11985     /* don't rely on existence of Event tag since if game was
11986      * pasted from clipboard the Event tag may not exist
11987      */
11988     if (numPGNTags > 0){
11989         char *tags;
11990         if (gameInfo.variant == VariantNormal) {
11991           VariantClass v = StringToVariant(gameInfo.event);
11992           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11993           if(v < VariantShogi) gameInfo.variant = v;
11994         }
11995         if (!matchMode) {
11996           if( appData.autoDisplayTags ) {
11997             tags = PGNTags(&gameInfo);
11998             TagsPopUp(tags, CmailMsg());
11999             free(tags);
12000           }
12001         }
12002     } else {
12003         /* Make something up, but don't display it now */
12004         SetGameInfo();
12005         TagsPopDown();
12006     }
12007
12008     if (cm == PositionDiagram) {
12009         int i, j;
12010         char *p;
12011         Board initial_position;
12012
12013         if (appData.debugMode)
12014           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12015
12016         if (!startedFromSetupPosition) {
12017             p = yy_text;
12018             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12019               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12020                 switch (*p) {
12021                   case '{':
12022                   case '[':
12023                   case '-':
12024                   case ' ':
12025                   case '\t':
12026                   case '\n':
12027                   case '\r':
12028                     break;
12029                   default:
12030                     initial_position[i][j++] = CharToPiece(*p);
12031                     break;
12032                 }
12033             while (*p == ' ' || *p == '\t' ||
12034                    *p == '\n' || *p == '\r') p++;
12035
12036             if (strncmp(p, "black", strlen("black"))==0)
12037               blackPlaysFirst = TRUE;
12038             else
12039               blackPlaysFirst = FALSE;
12040             startedFromSetupPosition = TRUE;
12041
12042             CopyBoard(boards[0], initial_position);
12043             if (blackPlaysFirst) {
12044                 currentMove = forwardMostMove = backwardMostMove = 1;
12045                 CopyBoard(boards[1], initial_position);
12046                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12047                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12048                 timeRemaining[0][1] = whiteTimeRemaining;
12049                 timeRemaining[1][1] = blackTimeRemaining;
12050                 if (commentList[0] != NULL) {
12051                     commentList[1] = commentList[0];
12052                     commentList[0] = NULL;
12053                 }
12054             } else {
12055                 currentMove = forwardMostMove = backwardMostMove = 0;
12056             }
12057         }
12058         yyboardindex = forwardMostMove;
12059         cm = (ChessMove) Myylex();
12060     }
12061
12062     if (first.pr == NoProc) {
12063         StartChessProgram(&first);
12064     }
12065     InitChessProgram(&first, FALSE);
12066     SendToProgram("force\n", &first);
12067     if (startedFromSetupPosition) {
12068         SendBoard(&first, forwardMostMove);
12069     if (appData.debugMode) {
12070         fprintf(debugFP, "Load Game\n");
12071     }
12072         DisplayBothClocks();
12073     }
12074
12075     /* [HGM] server: flag to write setup moves in broadcast file as one */
12076     loadFlag = appData.suppressLoadMoves;
12077
12078     while (cm == Comment) {
12079         char *p;
12080         if (appData.debugMode)
12081           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12082         p = yy_text;
12083         AppendComment(currentMove, p, FALSE);
12084         yyboardindex = forwardMostMove;
12085         cm = (ChessMove) Myylex();
12086     }
12087
12088     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12089         cm == WhiteWins || cm == BlackWins ||
12090         cm == GameIsDrawn || cm == GameUnfinished) {
12091         DisplayMessage("", _("No moves in game"));
12092         if (cmailMsgLoaded) {
12093             if (appData.debugMode)
12094               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12095             ClearHighlights();
12096             flipView = FALSE;
12097         }
12098         DrawPosition(FALSE, boards[currentMove]);
12099         DisplayBothClocks();
12100         gameMode = EditGame;
12101         ModeHighlight();
12102         gameFileFP = NULL;
12103         cmailOldMove = 0;
12104         return TRUE;
12105     }
12106
12107     // [HGM] PV info: routine tests if comment empty
12108     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12109         DisplayComment(currentMove - 1, commentList[currentMove]);
12110     }
12111     if (!matchMode && appData.timeDelay != 0)
12112       DrawPosition(FALSE, boards[currentMove]);
12113
12114     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12115       programStats.ok_to_send = 1;
12116     }
12117
12118     /* if the first token after the PGN tags is a move
12119      * and not move number 1, retrieve it from the parser
12120      */
12121     if (cm != MoveNumberOne)
12122         LoadGameOneMove(cm);
12123
12124     /* load the remaining moves from the file */
12125     while (LoadGameOneMove(EndOfFile)) {
12126       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12127       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12128     }
12129
12130     /* rewind to the start of the game */
12131     currentMove = backwardMostMove;
12132
12133     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12134
12135     if (oldGameMode == AnalyzeFile ||
12136         oldGameMode == AnalyzeMode) {
12137       AnalyzeFileEvent();
12138     }
12139
12140     if (!matchMode && pos >= 0) {
12141         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12142     } else
12143     if (matchMode || appData.timeDelay == 0) {
12144       ToEndEvent();
12145     } else if (appData.timeDelay > 0) {
12146       AutoPlayGameLoop();
12147     }
12148
12149     if (appData.debugMode)
12150         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12151
12152     loadFlag = 0; /* [HGM] true game starts */
12153     return TRUE;
12154 }
12155
12156 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12157 int
12158 ReloadPosition (int offset)
12159 {
12160     int positionNumber = lastLoadPositionNumber + offset;
12161     if (lastLoadPositionFP == NULL) {
12162         DisplayError(_("No position has been loaded yet"), 0);
12163         return FALSE;
12164     }
12165     if (positionNumber <= 0) {
12166         DisplayError(_("Can't back up any further"), 0);
12167         return FALSE;
12168     }
12169     return LoadPosition(lastLoadPositionFP, positionNumber,
12170                         lastLoadPositionTitle);
12171 }
12172
12173 /* Load the nth position from the given file */
12174 int
12175 LoadPositionFromFile (char *filename, int n, char *title)
12176 {
12177     FILE *f;
12178     char buf[MSG_SIZ];
12179
12180     if (strcmp(filename, "-") == 0) {
12181         return LoadPosition(stdin, n, "stdin");
12182     } else {
12183         f = fopen(filename, "rb");
12184         if (f == NULL) {
12185             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12186             DisplayError(buf, errno);
12187             return FALSE;
12188         } else {
12189             return LoadPosition(f, n, title);
12190         }
12191     }
12192 }
12193
12194 /* Load the nth position from the given open file, and close it */
12195 int
12196 LoadPosition (FILE *f, int positionNumber, char *title)
12197 {
12198     char *p, line[MSG_SIZ];
12199     Board initial_position;
12200     int i, j, fenMode, pn;
12201
12202     if (gameMode == Training )
12203         SetTrainingModeOff();
12204
12205     if (gameMode != BeginningOfGame) {
12206         Reset(FALSE, TRUE);
12207     }
12208     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12209         fclose(lastLoadPositionFP);
12210     }
12211     if (positionNumber == 0) positionNumber = 1;
12212     lastLoadPositionFP = f;
12213     lastLoadPositionNumber = positionNumber;
12214     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12215     if (first.pr == NoProc && !appData.noChessProgram) {
12216       StartChessProgram(&first);
12217       InitChessProgram(&first, FALSE);
12218     }
12219     pn = positionNumber;
12220     if (positionNumber < 0) {
12221         /* Negative position number means to seek to that byte offset */
12222         if (fseek(f, -positionNumber, 0) == -1) {
12223             DisplayError(_("Can't seek on position file"), 0);
12224             return FALSE;
12225         };
12226         pn = 1;
12227     } else {
12228         if (fseek(f, 0, 0) == -1) {
12229             if (f == lastLoadPositionFP ?
12230                 positionNumber == lastLoadPositionNumber + 1 :
12231                 positionNumber == 1) {
12232                 pn = 1;
12233             } else {
12234                 DisplayError(_("Can't seek on position file"), 0);
12235                 return FALSE;
12236             }
12237         }
12238     }
12239     /* See if this file is FEN or old-style xboard */
12240     if (fgets(line, MSG_SIZ, f) == NULL) {
12241         DisplayError(_("Position not found in file"), 0);
12242         return FALSE;
12243     }
12244     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12245     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12246
12247     if (pn >= 2) {
12248         if (fenMode || line[0] == '#') pn--;
12249         while (pn > 0) {
12250             /* skip positions before number pn */
12251             if (fgets(line, MSG_SIZ, f) == NULL) {
12252                 Reset(TRUE, TRUE);
12253                 DisplayError(_("Position not found in file"), 0);
12254                 return FALSE;
12255             }
12256             if (fenMode || line[0] == '#') pn--;
12257         }
12258     }
12259
12260     if (fenMode) {
12261         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12262             DisplayError(_("Bad FEN position in file"), 0);
12263             return FALSE;
12264         }
12265     } else {
12266         (void) fgets(line, MSG_SIZ, f);
12267         (void) fgets(line, MSG_SIZ, f);
12268
12269         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12270             (void) fgets(line, MSG_SIZ, f);
12271             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12272                 if (*p == ' ')
12273                   continue;
12274                 initial_position[i][j++] = CharToPiece(*p);
12275             }
12276         }
12277
12278         blackPlaysFirst = FALSE;
12279         if (!feof(f)) {
12280             (void) fgets(line, MSG_SIZ, f);
12281             if (strncmp(line, "black", strlen("black"))==0)
12282               blackPlaysFirst = TRUE;
12283         }
12284     }
12285     startedFromSetupPosition = TRUE;
12286
12287     CopyBoard(boards[0], initial_position);
12288     if (blackPlaysFirst) {
12289         currentMove = forwardMostMove = backwardMostMove = 1;
12290         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12291         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12292         CopyBoard(boards[1], initial_position);
12293         DisplayMessage("", _("Black to play"));
12294     } else {
12295         currentMove = forwardMostMove = backwardMostMove = 0;
12296         DisplayMessage("", _("White to play"));
12297     }
12298     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12299     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12300         SendToProgram("force\n", &first);
12301         SendBoard(&first, forwardMostMove);
12302     }
12303     if (appData.debugMode) {
12304 int i, j;
12305   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12306   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12307         fprintf(debugFP, "Load Position\n");
12308     }
12309
12310     if (positionNumber > 1) {
12311       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12312         DisplayTitle(line);
12313     } else {
12314         DisplayTitle(title);
12315     }
12316     gameMode = EditGame;
12317     ModeHighlight();
12318     ResetClocks();
12319     timeRemaining[0][1] = whiteTimeRemaining;
12320     timeRemaining[1][1] = blackTimeRemaining;
12321     DrawPosition(FALSE, boards[currentMove]);
12322
12323     return TRUE;
12324 }
12325
12326
12327 void
12328 CopyPlayerNameIntoFileName (char **dest, char *src)
12329 {
12330     while (*src != NULLCHAR && *src != ',') {
12331         if (*src == ' ') {
12332             *(*dest)++ = '_';
12333             src++;
12334         } else {
12335             *(*dest)++ = *src++;
12336         }
12337     }
12338 }
12339
12340 char *
12341 DefaultFileName (char *ext)
12342 {
12343     static char def[MSG_SIZ];
12344     char *p;
12345
12346     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12347         p = def;
12348         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12349         *p++ = '-';
12350         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12351         *p++ = '.';
12352         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12353     } else {
12354         def[0] = NULLCHAR;
12355     }
12356     return def;
12357 }
12358
12359 /* Save the current game to the given file */
12360 int
12361 SaveGameToFile (char *filename, int append)
12362 {
12363     FILE *f;
12364     char buf[MSG_SIZ];
12365     int result, i, t,tot=0;
12366
12367     if (strcmp(filename, "-") == 0) {
12368         return SaveGame(stdout, 0, NULL);
12369     } else {
12370         for(i=0; i<10; i++) { // upto 10 tries
12371              f = fopen(filename, append ? "a" : "w");
12372              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12373              if(f || errno != 13) break;
12374              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12375              tot += t;
12376         }
12377         if (f == NULL) {
12378             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12379             DisplayError(buf, errno);
12380             return FALSE;
12381         } else {
12382             safeStrCpy(buf, lastMsg, MSG_SIZ);
12383             DisplayMessage(_("Waiting for access to save file"), "");
12384             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12385             DisplayMessage(_("Saving game"), "");
12386             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12387             result = SaveGame(f, 0, NULL);
12388             DisplayMessage(buf, "");
12389             return result;
12390         }
12391     }
12392 }
12393
12394 char *
12395 SavePart (char *str)
12396 {
12397     static char buf[MSG_SIZ];
12398     char *p;
12399
12400     p = strchr(str, ' ');
12401     if (p == NULL) return str;
12402     strncpy(buf, str, p - str);
12403     buf[p - str] = NULLCHAR;
12404     return buf;
12405 }
12406
12407 #define PGN_MAX_LINE 75
12408
12409 #define PGN_SIDE_WHITE  0
12410 #define PGN_SIDE_BLACK  1
12411
12412 static int
12413 FindFirstMoveOutOfBook (int side)
12414 {
12415     int result = -1;
12416
12417     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12418         int index = backwardMostMove;
12419         int has_book_hit = 0;
12420
12421         if( (index % 2) != side ) {
12422             index++;
12423         }
12424
12425         while( index < forwardMostMove ) {
12426             /* Check to see if engine is in book */
12427             int depth = pvInfoList[index].depth;
12428             int score = pvInfoList[index].score;
12429             int in_book = 0;
12430
12431             if( depth <= 2 ) {
12432                 in_book = 1;
12433             }
12434             else if( score == 0 && depth == 63 ) {
12435                 in_book = 1; /* Zappa */
12436             }
12437             else if( score == 2 && depth == 99 ) {
12438                 in_book = 1; /* Abrok */
12439             }
12440
12441             has_book_hit += in_book;
12442
12443             if( ! in_book ) {
12444                 result = index;
12445
12446                 break;
12447             }
12448
12449             index += 2;
12450         }
12451     }
12452
12453     return result;
12454 }
12455
12456 void
12457 GetOutOfBookInfo (char * buf)
12458 {
12459     int oob[2];
12460     int i;
12461     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12462
12463     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12464     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12465
12466     *buf = '\0';
12467
12468     if( oob[0] >= 0 || oob[1] >= 0 ) {
12469         for( i=0; i<2; i++ ) {
12470             int idx = oob[i];
12471
12472             if( idx >= 0 ) {
12473                 if( i > 0 && oob[0] >= 0 ) {
12474                     strcat( buf, "   " );
12475                 }
12476
12477                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12478                 sprintf( buf+strlen(buf), "%s%.2f",
12479                     pvInfoList[idx].score >= 0 ? "+" : "",
12480                     pvInfoList[idx].score / 100.0 );
12481             }
12482         }
12483     }
12484 }
12485
12486 /* Save game in PGN style and close the file */
12487 int
12488 SaveGamePGN (FILE *f)
12489 {
12490     int i, offset, linelen, newblock;
12491     time_t tm;
12492 //    char *movetext;
12493     char numtext[32];
12494     int movelen, numlen, blank;
12495     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12496
12497     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12498
12499     tm = time((time_t *) NULL);
12500
12501     PrintPGNTags(f, &gameInfo);
12502
12503     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12504
12505     if (backwardMostMove > 0 || startedFromSetupPosition) {
12506         char *fen = PositionToFEN(backwardMostMove, NULL);
12507         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12508         fprintf(f, "\n{--------------\n");
12509         PrintPosition(f, backwardMostMove);
12510         fprintf(f, "--------------}\n");
12511         free(fen);
12512     }
12513     else {
12514         /* [AS] Out of book annotation */
12515         if( appData.saveOutOfBookInfo ) {
12516             char buf[64];
12517
12518             GetOutOfBookInfo( buf );
12519
12520             if( buf[0] != '\0' ) {
12521                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12522             }
12523         }
12524
12525         fprintf(f, "\n");
12526     }
12527
12528     i = backwardMostMove;
12529     linelen = 0;
12530     newblock = TRUE;
12531
12532     while (i < forwardMostMove) {
12533         /* Print comments preceding this move */
12534         if (commentList[i] != NULL) {
12535             if (linelen > 0) fprintf(f, "\n");
12536             fprintf(f, "%s", commentList[i]);
12537             linelen = 0;
12538             newblock = TRUE;
12539         }
12540
12541         /* Format move number */
12542         if ((i % 2) == 0)
12543           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12544         else
12545           if (newblock)
12546             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12547           else
12548             numtext[0] = NULLCHAR;
12549
12550         numlen = strlen(numtext);
12551         newblock = FALSE;
12552
12553         /* Print move number */
12554         blank = linelen > 0 && numlen > 0;
12555         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12556             fprintf(f, "\n");
12557             linelen = 0;
12558             blank = 0;
12559         }
12560         if (blank) {
12561             fprintf(f, " ");
12562             linelen++;
12563         }
12564         fprintf(f, "%s", numtext);
12565         linelen += numlen;
12566
12567         /* Get move */
12568         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12569         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12570
12571         /* Print move */
12572         blank = linelen > 0 && movelen > 0;
12573         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12574             fprintf(f, "\n");
12575             linelen = 0;
12576             blank = 0;
12577         }
12578         if (blank) {
12579             fprintf(f, " ");
12580             linelen++;
12581         }
12582         fprintf(f, "%s", move_buffer);
12583         linelen += movelen;
12584
12585         /* [AS] Add PV info if present */
12586         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12587             /* [HGM] add time */
12588             char buf[MSG_SIZ]; int seconds;
12589
12590             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12591
12592             if( seconds <= 0)
12593               buf[0] = 0;
12594             else
12595               if( seconds < 30 )
12596                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12597               else
12598                 {
12599                   seconds = (seconds + 4)/10; // round to full seconds
12600                   if( seconds < 60 )
12601                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12602                   else
12603                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12604                 }
12605
12606             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12607                       pvInfoList[i].score >= 0 ? "+" : "",
12608                       pvInfoList[i].score / 100.0,
12609                       pvInfoList[i].depth,
12610                       buf );
12611
12612             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12613
12614             /* Print score/depth */
12615             blank = linelen > 0 && movelen > 0;
12616             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12617                 fprintf(f, "\n");
12618                 linelen = 0;
12619                 blank = 0;
12620             }
12621             if (blank) {
12622                 fprintf(f, " ");
12623                 linelen++;
12624             }
12625             fprintf(f, "%s", move_buffer);
12626             linelen += movelen;
12627         }
12628
12629         i++;
12630     }
12631
12632     /* Start a new line */
12633     if (linelen > 0) fprintf(f, "\n");
12634
12635     /* Print comments after last move */
12636     if (commentList[i] != NULL) {
12637         fprintf(f, "%s\n", commentList[i]);
12638     }
12639
12640     /* Print result */
12641     if (gameInfo.resultDetails != NULL &&
12642         gameInfo.resultDetails[0] != NULLCHAR) {
12643         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12644                 PGNResult(gameInfo.result));
12645     } else {
12646         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12647     }
12648
12649     fclose(f);
12650     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12651     return TRUE;
12652 }
12653
12654 /* Save game in old style and close the file */
12655 int
12656 SaveGameOldStyle (FILE *f)
12657 {
12658     int i, offset;
12659     time_t tm;
12660
12661     tm = time((time_t *) NULL);
12662
12663     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12664     PrintOpponents(f);
12665
12666     if (backwardMostMove > 0 || startedFromSetupPosition) {
12667         fprintf(f, "\n[--------------\n");
12668         PrintPosition(f, backwardMostMove);
12669         fprintf(f, "--------------]\n");
12670     } else {
12671         fprintf(f, "\n");
12672     }
12673
12674     i = backwardMostMove;
12675     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12676
12677     while (i < forwardMostMove) {
12678         if (commentList[i] != NULL) {
12679             fprintf(f, "[%s]\n", commentList[i]);
12680         }
12681
12682         if ((i % 2) == 1) {
12683             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12684             i++;
12685         } else {
12686             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12687             i++;
12688             if (commentList[i] != NULL) {
12689                 fprintf(f, "\n");
12690                 continue;
12691             }
12692             if (i >= forwardMostMove) {
12693                 fprintf(f, "\n");
12694                 break;
12695             }
12696             fprintf(f, "%s\n", parseList[i]);
12697             i++;
12698         }
12699     }
12700
12701     if (commentList[i] != NULL) {
12702         fprintf(f, "[%s]\n", commentList[i]);
12703     }
12704
12705     /* This isn't really the old style, but it's close enough */
12706     if (gameInfo.resultDetails != NULL &&
12707         gameInfo.resultDetails[0] != NULLCHAR) {
12708         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12709                 gameInfo.resultDetails);
12710     } else {
12711         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12712     }
12713
12714     fclose(f);
12715     return TRUE;
12716 }
12717
12718 /* Save the current game to open file f and close the file */
12719 int
12720 SaveGame (FILE *f, int dummy, char *dummy2)
12721 {
12722     if (gameMode == EditPosition) EditPositionDone(TRUE);
12723     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12724     if (appData.oldSaveStyle)
12725       return SaveGameOldStyle(f);
12726     else
12727       return SaveGamePGN(f);
12728 }
12729
12730 /* Save the current position to the given file */
12731 int
12732 SavePositionToFile (char *filename)
12733 {
12734     FILE *f;
12735     char buf[MSG_SIZ];
12736
12737     if (strcmp(filename, "-") == 0) {
12738         return SavePosition(stdout, 0, NULL);
12739     } else {
12740         f = fopen(filename, "a");
12741         if (f == NULL) {
12742             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12743             DisplayError(buf, errno);
12744             return FALSE;
12745         } else {
12746             safeStrCpy(buf, lastMsg, MSG_SIZ);
12747             DisplayMessage(_("Waiting for access to save file"), "");
12748             flock(fileno(f), LOCK_EX); // [HGM] lock
12749             DisplayMessage(_("Saving position"), "");
12750             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12751             SavePosition(f, 0, NULL);
12752             DisplayMessage(buf, "");
12753             return TRUE;
12754         }
12755     }
12756 }
12757
12758 /* Save the current position to the given open file and close the file */
12759 int
12760 SavePosition (FILE *f, int dummy, char *dummy2)
12761 {
12762     time_t tm;
12763     char *fen;
12764
12765     if (gameMode == EditPosition) EditPositionDone(TRUE);
12766     if (appData.oldSaveStyle) {
12767         tm = time((time_t *) NULL);
12768
12769         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12770         PrintOpponents(f);
12771         fprintf(f, "[--------------\n");
12772         PrintPosition(f, currentMove);
12773         fprintf(f, "--------------]\n");
12774     } else {
12775         fen = PositionToFEN(currentMove, NULL);
12776         fprintf(f, "%s\n", fen);
12777         free(fen);
12778     }
12779     fclose(f);
12780     return TRUE;
12781 }
12782
12783 void
12784 ReloadCmailMsgEvent (int unregister)
12785 {
12786 #if !WIN32
12787     static char *inFilename = NULL;
12788     static char *outFilename;
12789     int i;
12790     struct stat inbuf, outbuf;
12791     int status;
12792
12793     /* Any registered moves are unregistered if unregister is set, */
12794     /* i.e. invoked by the signal handler */
12795     if (unregister) {
12796         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12797             cmailMoveRegistered[i] = FALSE;
12798             if (cmailCommentList[i] != NULL) {
12799                 free(cmailCommentList[i]);
12800                 cmailCommentList[i] = NULL;
12801             }
12802         }
12803         nCmailMovesRegistered = 0;
12804     }
12805
12806     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12807         cmailResult[i] = CMAIL_NOT_RESULT;
12808     }
12809     nCmailResults = 0;
12810
12811     if (inFilename == NULL) {
12812         /* Because the filenames are static they only get malloced once  */
12813         /* and they never get freed                                      */
12814         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12815         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12816
12817         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12818         sprintf(outFilename, "%s.out", appData.cmailGameName);
12819     }
12820
12821     status = stat(outFilename, &outbuf);
12822     if (status < 0) {
12823         cmailMailedMove = FALSE;
12824     } else {
12825         status = stat(inFilename, &inbuf);
12826         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12827     }
12828
12829     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12830        counts the games, notes how each one terminated, etc.
12831
12832        It would be nice to remove this kludge and instead gather all
12833        the information while building the game list.  (And to keep it
12834        in the game list nodes instead of having a bunch of fixed-size
12835        parallel arrays.)  Note this will require getting each game's
12836        termination from the PGN tags, as the game list builder does
12837        not process the game moves.  --mann
12838        */
12839     cmailMsgLoaded = TRUE;
12840     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12841
12842     /* Load first game in the file or popup game menu */
12843     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12844
12845 #endif /* !WIN32 */
12846     return;
12847 }
12848
12849 int
12850 RegisterMove ()
12851 {
12852     FILE *f;
12853     char string[MSG_SIZ];
12854
12855     if (   cmailMailedMove
12856         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12857         return TRUE;            /* Allow free viewing  */
12858     }
12859
12860     /* Unregister move to ensure that we don't leave RegisterMove        */
12861     /* with the move registered when the conditions for registering no   */
12862     /* longer hold                                                       */
12863     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12864         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12865         nCmailMovesRegistered --;
12866
12867         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12868           {
12869               free(cmailCommentList[lastLoadGameNumber - 1]);
12870               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12871           }
12872     }
12873
12874     if (cmailOldMove == -1) {
12875         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12876         return FALSE;
12877     }
12878
12879     if (currentMove > cmailOldMove + 1) {
12880         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12881         return FALSE;
12882     }
12883
12884     if (currentMove < cmailOldMove) {
12885         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12886         return FALSE;
12887     }
12888
12889     if (forwardMostMove > currentMove) {
12890         /* Silently truncate extra moves */
12891         TruncateGame();
12892     }
12893
12894     if (   (currentMove == cmailOldMove + 1)
12895         || (   (currentMove == cmailOldMove)
12896             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12897                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12898         if (gameInfo.result != GameUnfinished) {
12899             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12900         }
12901
12902         if (commentList[currentMove] != NULL) {
12903             cmailCommentList[lastLoadGameNumber - 1]
12904               = StrSave(commentList[currentMove]);
12905         }
12906         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12907
12908         if (appData.debugMode)
12909           fprintf(debugFP, "Saving %s for game %d\n",
12910                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12911
12912         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12913
12914         f = fopen(string, "w");
12915         if (appData.oldSaveStyle) {
12916             SaveGameOldStyle(f); /* also closes the file */
12917
12918             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12919             f = fopen(string, "w");
12920             SavePosition(f, 0, NULL); /* also closes the file */
12921         } else {
12922             fprintf(f, "{--------------\n");
12923             PrintPosition(f, currentMove);
12924             fprintf(f, "--------------}\n\n");
12925
12926             SaveGame(f, 0, NULL); /* also closes the file*/
12927         }
12928
12929         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12930         nCmailMovesRegistered ++;
12931     } else if (nCmailGames == 1) {
12932         DisplayError(_("You have not made a move yet"), 0);
12933         return FALSE;
12934     }
12935
12936     return TRUE;
12937 }
12938
12939 void
12940 MailMoveEvent ()
12941 {
12942 #if !WIN32
12943     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12944     FILE *commandOutput;
12945     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12946     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12947     int nBuffers;
12948     int i;
12949     int archived;
12950     char *arcDir;
12951
12952     if (! cmailMsgLoaded) {
12953         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12954         return;
12955     }
12956
12957     if (nCmailGames == nCmailResults) {
12958         DisplayError(_("No unfinished games"), 0);
12959         return;
12960     }
12961
12962 #if CMAIL_PROHIBIT_REMAIL
12963     if (cmailMailedMove) {
12964       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);
12965         DisplayError(msg, 0);
12966         return;
12967     }
12968 #endif
12969
12970     if (! (cmailMailedMove || RegisterMove())) return;
12971
12972     if (   cmailMailedMove
12973         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12974       snprintf(string, MSG_SIZ, partCommandString,
12975                appData.debugMode ? " -v" : "", appData.cmailGameName);
12976         commandOutput = popen(string, "r");
12977
12978         if (commandOutput == NULL) {
12979             DisplayError(_("Failed to invoke cmail"), 0);
12980         } else {
12981             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12982                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12983             }
12984             if (nBuffers > 1) {
12985                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12986                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12987                 nBytes = MSG_SIZ - 1;
12988             } else {
12989                 (void) memcpy(msg, buffer, nBytes);
12990             }
12991             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12992
12993             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12994                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12995
12996                 archived = TRUE;
12997                 for (i = 0; i < nCmailGames; i ++) {
12998                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12999                         archived = FALSE;
13000                     }
13001                 }
13002                 if (   archived
13003                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13004                         != NULL)) {
13005                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13006                            arcDir,
13007                            appData.cmailGameName,
13008                            gameInfo.date);
13009                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13010                     cmailMsgLoaded = FALSE;
13011                 }
13012             }
13013
13014             DisplayInformation(msg);
13015             pclose(commandOutput);
13016         }
13017     } else {
13018         if ((*cmailMsg) != '\0') {
13019             DisplayInformation(cmailMsg);
13020         }
13021     }
13022
13023     return;
13024 #endif /* !WIN32 */
13025 }
13026
13027 char *
13028 CmailMsg ()
13029 {
13030 #if WIN32
13031     return NULL;
13032 #else
13033     int  prependComma = 0;
13034     char number[5];
13035     char string[MSG_SIZ];       /* Space for game-list */
13036     int  i;
13037
13038     if (!cmailMsgLoaded) return "";
13039
13040     if (cmailMailedMove) {
13041       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13042     } else {
13043         /* Create a list of games left */
13044       snprintf(string, MSG_SIZ, "[");
13045         for (i = 0; i < nCmailGames; i ++) {
13046             if (! (   cmailMoveRegistered[i]
13047                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13048                 if (prependComma) {
13049                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13050                 } else {
13051                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13052                     prependComma = 1;
13053                 }
13054
13055                 strcat(string, number);
13056             }
13057         }
13058         strcat(string, "]");
13059
13060         if (nCmailMovesRegistered + nCmailResults == 0) {
13061             switch (nCmailGames) {
13062               case 1:
13063                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13064                 break;
13065
13066               case 2:
13067                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13068                 break;
13069
13070               default:
13071                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13072                          nCmailGames);
13073                 break;
13074             }
13075         } else {
13076             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13077               case 1:
13078                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13079                          string);
13080                 break;
13081
13082               case 0:
13083                 if (nCmailResults == nCmailGames) {
13084                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13085                 } else {
13086                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13087                 }
13088                 break;
13089
13090               default:
13091                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13092                          string);
13093             }
13094         }
13095     }
13096     return cmailMsg;
13097 #endif /* WIN32 */
13098 }
13099
13100 void
13101 ResetGameEvent ()
13102 {
13103     if (gameMode == Training)
13104       SetTrainingModeOff();
13105
13106     Reset(TRUE, TRUE);
13107     cmailMsgLoaded = FALSE;
13108     if (appData.icsActive) {
13109       SendToICS(ics_prefix);
13110       SendToICS("refresh\n");
13111     }
13112 }
13113
13114 void
13115 ExitEvent (int status)
13116 {
13117     exiting++;
13118     if (exiting > 2) {
13119       /* Give up on clean exit */
13120       exit(status);
13121     }
13122     if (exiting > 1) {
13123       /* Keep trying for clean exit */
13124       return;
13125     }
13126
13127     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13128
13129     if (telnetISR != NULL) {
13130       RemoveInputSource(telnetISR);
13131     }
13132     if (icsPR != NoProc) {
13133       DestroyChildProcess(icsPR, TRUE);
13134     }
13135
13136     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13137     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13138
13139     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13140     /* make sure this other one finishes before killing it!                  */
13141     if(endingGame) { int count = 0;
13142         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13143         while(endingGame && count++ < 10) DoSleep(1);
13144         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13145     }
13146
13147     /* Kill off chess programs */
13148     if (first.pr != NoProc) {
13149         ExitAnalyzeMode();
13150
13151         DoSleep( appData.delayBeforeQuit );
13152         SendToProgram("quit\n", &first);
13153         DoSleep( appData.delayAfterQuit );
13154         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13155     }
13156     if (second.pr != NoProc) {
13157         DoSleep( appData.delayBeforeQuit );
13158         SendToProgram("quit\n", &second);
13159         DoSleep( appData.delayAfterQuit );
13160         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13161     }
13162     if (first.isr != NULL) {
13163         RemoveInputSource(first.isr);
13164     }
13165     if (second.isr != NULL) {
13166         RemoveInputSource(second.isr);
13167     }
13168
13169     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13170     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13171
13172     ShutDownFrontEnd();
13173     exit(status);
13174 }
13175
13176 void
13177 PauseEvent ()
13178 {
13179     if (appData.debugMode)
13180         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13181     if (pausing) {
13182         pausing = FALSE;
13183         ModeHighlight();
13184         if (gameMode == MachinePlaysWhite ||
13185             gameMode == MachinePlaysBlack) {
13186             StartClocks();
13187         } else {
13188             DisplayBothClocks();
13189         }
13190         if (gameMode == PlayFromGameFile) {
13191             if (appData.timeDelay >= 0)
13192                 AutoPlayGameLoop();
13193         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13194             Reset(FALSE, TRUE);
13195             SendToICS(ics_prefix);
13196             SendToICS("refresh\n");
13197         } else if (currentMove < forwardMostMove) {
13198             ForwardInner(forwardMostMove);
13199         }
13200         pauseExamInvalid = FALSE;
13201     } else {
13202         switch (gameMode) {
13203           default:
13204             return;
13205           case IcsExamining:
13206             pauseExamForwardMostMove = forwardMostMove;
13207             pauseExamInvalid = FALSE;
13208             /* fall through */
13209           case IcsObserving:
13210           case IcsPlayingWhite:
13211           case IcsPlayingBlack:
13212             pausing = TRUE;
13213             ModeHighlight();
13214             return;
13215           case PlayFromGameFile:
13216             (void) StopLoadGameTimer();
13217             pausing = TRUE;
13218             ModeHighlight();
13219             break;
13220           case BeginningOfGame:
13221             if (appData.icsActive) return;
13222             /* else fall through */
13223           case MachinePlaysWhite:
13224           case MachinePlaysBlack:
13225           case TwoMachinesPlay:
13226             if (forwardMostMove == 0)
13227               return;           /* don't pause if no one has moved */
13228             if ((gameMode == MachinePlaysWhite &&
13229                  !WhiteOnMove(forwardMostMove)) ||
13230                 (gameMode == MachinePlaysBlack &&
13231                  WhiteOnMove(forwardMostMove))) {
13232                 StopClocks();
13233             }
13234             pausing = TRUE;
13235             ModeHighlight();
13236             break;
13237         }
13238     }
13239 }
13240
13241 void
13242 EditCommentEvent ()
13243 {
13244     char title[MSG_SIZ];
13245
13246     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13247       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13248     } else {
13249       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13250                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13251                parseList[currentMove - 1]);
13252     }
13253
13254     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13255 }
13256
13257
13258 void
13259 EditTagsEvent ()
13260 {
13261     char *tags = PGNTags(&gameInfo);
13262     bookUp = FALSE;
13263     EditTagsPopUp(tags, NULL);
13264     free(tags);
13265 }
13266
13267 void
13268 AnalyzeModeEvent ()
13269 {
13270     if (appData.noChessProgram || gameMode == AnalyzeMode)
13271       return;
13272
13273     if (gameMode != AnalyzeFile) {
13274         if (!appData.icsEngineAnalyze) {
13275                EditGameEvent();
13276                if (gameMode != EditGame) return;
13277         }
13278         ResurrectChessProgram();
13279         SendToProgram("analyze\n", &first);
13280         first.analyzing = TRUE;
13281         /*first.maybeThinking = TRUE;*/
13282         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13283         EngineOutputPopUp();
13284     }
13285     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13286     pausing = FALSE;
13287     ModeHighlight();
13288     SetGameInfo();
13289
13290     StartAnalysisClock();
13291     GetTimeMark(&lastNodeCountTime);
13292     lastNodeCount = 0;
13293 }
13294
13295 void
13296 AnalyzeFileEvent ()
13297 {
13298     if (appData.noChessProgram || gameMode == AnalyzeFile)
13299       return;
13300
13301     if (gameMode != AnalyzeMode) {
13302         EditGameEvent();
13303         if (gameMode != EditGame) return;
13304         ResurrectChessProgram();
13305         SendToProgram("analyze\n", &first);
13306         first.analyzing = TRUE;
13307         /*first.maybeThinking = TRUE;*/
13308         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13309         EngineOutputPopUp();
13310     }
13311     gameMode = AnalyzeFile;
13312     pausing = FALSE;
13313     ModeHighlight();
13314     SetGameInfo();
13315
13316     StartAnalysisClock();
13317     GetTimeMark(&lastNodeCountTime);
13318     lastNodeCount = 0;
13319     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13320 }
13321
13322 void
13323 MachineWhiteEvent ()
13324 {
13325     char buf[MSG_SIZ];
13326     char *bookHit = NULL;
13327
13328     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13329       return;
13330
13331
13332     if (gameMode == PlayFromGameFile ||
13333         gameMode == TwoMachinesPlay  ||
13334         gameMode == Training         ||
13335         gameMode == AnalyzeMode      ||
13336         gameMode == EndOfGame)
13337         EditGameEvent();
13338
13339     if (gameMode == EditPosition)
13340         EditPositionDone(TRUE);
13341
13342     if (!WhiteOnMove(currentMove)) {
13343         DisplayError(_("It is not White's turn"), 0);
13344         return;
13345     }
13346
13347     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13348       ExitAnalyzeMode();
13349
13350     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13351         gameMode == AnalyzeFile)
13352         TruncateGame();
13353
13354     ResurrectChessProgram();    /* in case it isn't running */
13355     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13356         gameMode = MachinePlaysWhite;
13357         ResetClocks();
13358     } else
13359     gameMode = MachinePlaysWhite;
13360     pausing = FALSE;
13361     ModeHighlight();
13362     SetGameInfo();
13363     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13364     DisplayTitle(buf);
13365     if (first.sendName) {
13366       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13367       SendToProgram(buf, &first);
13368     }
13369     if (first.sendTime) {
13370       if (first.useColors) {
13371         SendToProgram("black\n", &first); /*gnu kludge*/
13372       }
13373       SendTimeRemaining(&first, TRUE);
13374     }
13375     if (first.useColors) {
13376       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13377     }
13378     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13379     SetMachineThinkingEnables();
13380     first.maybeThinking = TRUE;
13381     StartClocks();
13382     firstMove = FALSE;
13383
13384     if (appData.autoFlipView && !flipView) {
13385       flipView = !flipView;
13386       DrawPosition(FALSE, NULL);
13387       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13388     }
13389
13390     if(bookHit) { // [HGM] book: simulate book reply
13391         static char bookMove[MSG_SIZ]; // a bit generous?
13392
13393         programStats.nodes = programStats.depth = programStats.time =
13394         programStats.score = programStats.got_only_move = 0;
13395         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13396
13397         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13398         strcat(bookMove, bookHit);
13399         HandleMachineMove(bookMove, &first);
13400     }
13401 }
13402
13403 void
13404 MachineBlackEvent ()
13405 {
13406   char buf[MSG_SIZ];
13407   char *bookHit = NULL;
13408
13409     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13410         return;
13411
13412
13413     if (gameMode == PlayFromGameFile ||
13414         gameMode == TwoMachinesPlay  ||
13415         gameMode == Training         ||
13416         gameMode == AnalyzeMode      ||
13417         gameMode == EndOfGame)
13418         EditGameEvent();
13419
13420     if (gameMode == EditPosition)
13421         EditPositionDone(TRUE);
13422
13423     if (WhiteOnMove(currentMove)) {
13424         DisplayError(_("It is not Black's turn"), 0);
13425         return;
13426     }
13427
13428     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13429       ExitAnalyzeMode();
13430
13431     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13432         gameMode == AnalyzeFile)
13433         TruncateGame();
13434
13435     ResurrectChessProgram();    /* in case it isn't running */
13436     gameMode = MachinePlaysBlack;
13437     pausing = FALSE;
13438     ModeHighlight();
13439     SetGameInfo();
13440     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13441     DisplayTitle(buf);
13442     if (first.sendName) {
13443       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13444       SendToProgram(buf, &first);
13445     }
13446     if (first.sendTime) {
13447       if (first.useColors) {
13448         SendToProgram("white\n", &first); /*gnu kludge*/
13449       }
13450       SendTimeRemaining(&first, FALSE);
13451     }
13452     if (first.useColors) {
13453       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13454     }
13455     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13456     SetMachineThinkingEnables();
13457     first.maybeThinking = TRUE;
13458     StartClocks();
13459
13460     if (appData.autoFlipView && flipView) {
13461       flipView = !flipView;
13462       DrawPosition(FALSE, NULL);
13463       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13464     }
13465     if(bookHit) { // [HGM] book: simulate book reply
13466         static char bookMove[MSG_SIZ]; // a bit generous?
13467
13468         programStats.nodes = programStats.depth = programStats.time =
13469         programStats.score = programStats.got_only_move = 0;
13470         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13471
13472         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13473         strcat(bookMove, bookHit);
13474         HandleMachineMove(bookMove, &first);
13475     }
13476 }
13477
13478
13479 void
13480 DisplayTwoMachinesTitle ()
13481 {
13482     char buf[MSG_SIZ];
13483     if (appData.matchGames > 0) {
13484         if(appData.tourneyFile[0]) {
13485           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13486                    gameInfo.white, _("vs."), gameInfo.black,
13487                    nextGame+1, appData.matchGames+1,
13488                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13489         } else 
13490         if (first.twoMachinesColor[0] == 'w') {
13491           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13492                    gameInfo.white, _("vs."),  gameInfo.black,
13493                    first.matchWins, second.matchWins,
13494                    matchGame - 1 - (first.matchWins + second.matchWins));
13495         } else {
13496           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13497                    gameInfo.white, _("vs."), gameInfo.black,
13498                    second.matchWins, first.matchWins,
13499                    matchGame - 1 - (first.matchWins + second.matchWins));
13500         }
13501     } else {
13502       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13503     }
13504     DisplayTitle(buf);
13505 }
13506
13507 void
13508 SettingsMenuIfReady ()
13509 {
13510   if (second.lastPing != second.lastPong) {
13511     DisplayMessage("", _("Waiting for second chess program"));
13512     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13513     return;
13514   }
13515   ThawUI();
13516   DisplayMessage("", "");
13517   SettingsPopUp(&second);
13518 }
13519
13520 int
13521 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13522 {
13523     char buf[MSG_SIZ];
13524     if (cps->pr == NoProc) {
13525         StartChessProgram(cps);
13526         if (cps->protocolVersion == 1) {
13527           retry();
13528         } else {
13529           /* kludge: allow timeout for initial "feature" command */
13530           FreezeUI();
13531           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13532           DisplayMessage("", buf);
13533           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13534         }
13535         return 1;
13536     }
13537     return 0;
13538 }
13539
13540 void
13541 TwoMachinesEvent P((void))
13542 {
13543     int i;
13544     char buf[MSG_SIZ];
13545     ChessProgramState *onmove;
13546     char *bookHit = NULL;
13547     static int stalling = 0;
13548     TimeMark now;
13549     long wait;
13550
13551     if (appData.noChessProgram) return;
13552
13553     switch (gameMode) {
13554       case TwoMachinesPlay:
13555         return;
13556       case MachinePlaysWhite:
13557       case MachinePlaysBlack:
13558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13560             return;
13561         }
13562         /* fall through */
13563       case BeginningOfGame:
13564       case PlayFromGameFile:
13565       case EndOfGame:
13566         EditGameEvent();
13567         if (gameMode != EditGame) return;
13568         break;
13569       case EditPosition:
13570         EditPositionDone(TRUE);
13571         break;
13572       case AnalyzeMode:
13573       case AnalyzeFile:
13574         ExitAnalyzeMode();
13575         break;
13576       case EditGame:
13577       default:
13578         break;
13579     }
13580
13581 //    forwardMostMove = currentMove;
13582     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13583
13584     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13585
13586     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13587     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13588       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13589       return;
13590     }
13591     if(!stalling) {
13592       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13593       SendToProgram("force\n", &second);
13594       stalling = 1;
13595       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13596       return;
13597     }
13598     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13599     if(appData.matchPause>10000 || appData.matchPause<10)
13600                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13601     wait = SubtractTimeMarks(&now, &pauseStart);
13602     if(wait < appData.matchPause) {
13603         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13604         return;
13605     }
13606     // we are now committed to starting the game
13607     stalling = 0;
13608     DisplayMessage("", "");
13609     if (startedFromSetupPosition) {
13610         SendBoard(&second, backwardMostMove);
13611     if (appData.debugMode) {
13612         fprintf(debugFP, "Two Machines\n");
13613     }
13614     }
13615     for (i = backwardMostMove; i < forwardMostMove; i++) {
13616         SendMoveToProgram(i, &second);
13617     }
13618
13619     gameMode = TwoMachinesPlay;
13620     pausing = FALSE;
13621     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13622     SetGameInfo();
13623     DisplayTwoMachinesTitle();
13624     firstMove = TRUE;
13625     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13626         onmove = &first;
13627     } else {
13628         onmove = &second;
13629     }
13630     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13631     SendToProgram(first.computerString, &first);
13632     if (first.sendName) {
13633       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13634       SendToProgram(buf, &first);
13635     }
13636     SendToProgram(second.computerString, &second);
13637     if (second.sendName) {
13638       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13639       SendToProgram(buf, &second);
13640     }
13641
13642     ResetClocks();
13643     if (!first.sendTime || !second.sendTime) {
13644         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13645         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13646     }
13647     if (onmove->sendTime) {
13648       if (onmove->useColors) {
13649         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13650       }
13651       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13652     }
13653     if (onmove->useColors) {
13654       SendToProgram(onmove->twoMachinesColor, onmove);
13655     }
13656     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13657 //    SendToProgram("go\n", onmove);
13658     onmove->maybeThinking = TRUE;
13659     SetMachineThinkingEnables();
13660
13661     StartClocks();
13662
13663     if(bookHit) { // [HGM] book: simulate book reply
13664         static char bookMove[MSG_SIZ]; // a bit generous?
13665
13666         programStats.nodes = programStats.depth = programStats.time =
13667         programStats.score = programStats.got_only_move = 0;
13668         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13669
13670         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13671         strcat(bookMove, bookHit);
13672         savedMessage = bookMove; // args for deferred call
13673         savedState = onmove;
13674         ScheduleDelayedEvent(DeferredBookMove, 1);
13675     }
13676 }
13677
13678 void
13679 TrainingEvent ()
13680 {
13681     if (gameMode == Training) {
13682       SetTrainingModeOff();
13683       gameMode = PlayFromGameFile;
13684       DisplayMessage("", _("Training mode off"));
13685     } else {
13686       gameMode = Training;
13687       animateTraining = appData.animate;
13688
13689       /* make sure we are not already at the end of the game */
13690       if (currentMove < forwardMostMove) {
13691         SetTrainingModeOn();
13692         DisplayMessage("", _("Training mode on"));
13693       } else {
13694         gameMode = PlayFromGameFile;
13695         DisplayError(_("Already at end of game"), 0);
13696       }
13697     }
13698     ModeHighlight();
13699 }
13700
13701 void
13702 IcsClientEvent ()
13703 {
13704     if (!appData.icsActive) return;
13705     switch (gameMode) {
13706       case IcsPlayingWhite:
13707       case IcsPlayingBlack:
13708       case IcsObserving:
13709       case IcsIdle:
13710       case BeginningOfGame:
13711       case IcsExamining:
13712         return;
13713
13714       case EditGame:
13715         break;
13716
13717       case EditPosition:
13718         EditPositionDone(TRUE);
13719         break;
13720
13721       case AnalyzeMode:
13722       case AnalyzeFile:
13723         ExitAnalyzeMode();
13724         break;
13725
13726       default:
13727         EditGameEvent();
13728         break;
13729     }
13730
13731     gameMode = IcsIdle;
13732     ModeHighlight();
13733     return;
13734 }
13735
13736 void
13737 EditGameEvent ()
13738 {
13739     int i;
13740
13741     switch (gameMode) {
13742       case Training:
13743         SetTrainingModeOff();
13744         break;
13745       case MachinePlaysWhite:
13746       case MachinePlaysBlack:
13747       case BeginningOfGame:
13748         SendToProgram("force\n", &first);
13749         SetUserThinkingEnables();
13750         break;
13751       case PlayFromGameFile:
13752         (void) StopLoadGameTimer();
13753         if (gameFileFP != NULL) {
13754             gameFileFP = NULL;
13755         }
13756         break;
13757       case EditPosition:
13758         EditPositionDone(TRUE);
13759         break;
13760       case AnalyzeMode:
13761       case AnalyzeFile:
13762         ExitAnalyzeMode();
13763         SendToProgram("force\n", &first);
13764         break;
13765       case TwoMachinesPlay:
13766         GameEnds(EndOfFile, NULL, GE_PLAYER);
13767         ResurrectChessProgram();
13768         SetUserThinkingEnables();
13769         break;
13770       case EndOfGame:
13771         ResurrectChessProgram();
13772         break;
13773       case IcsPlayingBlack:
13774       case IcsPlayingWhite:
13775         DisplayError(_("Warning: You are still playing a game"), 0);
13776         break;
13777       case IcsObserving:
13778         DisplayError(_("Warning: You are still observing a game"), 0);
13779         break;
13780       case IcsExamining:
13781         DisplayError(_("Warning: You are still examining a game"), 0);
13782         break;
13783       case IcsIdle:
13784         break;
13785       case EditGame:
13786       default:
13787         return;
13788     }
13789
13790     pausing = FALSE;
13791     StopClocks();
13792     first.offeredDraw = second.offeredDraw = 0;
13793
13794     if (gameMode == PlayFromGameFile) {
13795         whiteTimeRemaining = timeRemaining[0][currentMove];
13796         blackTimeRemaining = timeRemaining[1][currentMove];
13797         DisplayTitle("");
13798     }
13799
13800     if (gameMode == MachinePlaysWhite ||
13801         gameMode == MachinePlaysBlack ||
13802         gameMode == TwoMachinesPlay ||
13803         gameMode == EndOfGame) {
13804         i = forwardMostMove;
13805         while (i > currentMove) {
13806             SendToProgram("undo\n", &first);
13807             i--;
13808         }
13809         if(!adjustedClock) {
13810         whiteTimeRemaining = timeRemaining[0][currentMove];
13811         blackTimeRemaining = timeRemaining[1][currentMove];
13812         DisplayBothClocks();
13813         }
13814         if (whiteFlag || blackFlag) {
13815             whiteFlag = blackFlag = 0;
13816         }
13817         DisplayTitle("");
13818     }
13819
13820     gameMode = EditGame;
13821     ModeHighlight();
13822     SetGameInfo();
13823 }
13824
13825
13826 void
13827 EditPositionEvent ()
13828 {
13829     if (gameMode == EditPosition) {
13830         EditGameEvent();
13831         return;
13832     }
13833
13834     EditGameEvent();
13835     if (gameMode != EditGame) return;
13836
13837     gameMode = EditPosition;
13838     ModeHighlight();
13839     SetGameInfo();
13840     if (currentMove > 0)
13841       CopyBoard(boards[0], boards[currentMove]);
13842
13843     blackPlaysFirst = !WhiteOnMove(currentMove);
13844     ResetClocks();
13845     currentMove = forwardMostMove = backwardMostMove = 0;
13846     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13847     DisplayMove(-1);
13848     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13849 }
13850
13851 void
13852 ExitAnalyzeMode ()
13853 {
13854     /* [DM] icsEngineAnalyze - possible call from other functions */
13855     if (appData.icsEngineAnalyze) {
13856         appData.icsEngineAnalyze = FALSE;
13857
13858         DisplayMessage("",_("Close ICS engine analyze..."));
13859     }
13860     if (first.analysisSupport && first.analyzing) {
13861       SendToProgram("exit\n", &first);
13862       first.analyzing = FALSE;
13863     }
13864     thinkOutput[0] = NULLCHAR;
13865 }
13866
13867 void
13868 EditPositionDone (Boolean fakeRights)
13869 {
13870     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13871
13872     startedFromSetupPosition = TRUE;
13873     InitChessProgram(&first, FALSE);
13874     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13875       boards[0][EP_STATUS] = EP_NONE;
13876       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13877     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13878         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13879         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13880       } else boards[0][CASTLING][2] = NoRights;
13881     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13882         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13883         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13884       } else boards[0][CASTLING][5] = NoRights;
13885     }
13886     SendToProgram("force\n", &first);
13887     if (blackPlaysFirst) {
13888         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13889         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13890         currentMove = forwardMostMove = backwardMostMove = 1;
13891         CopyBoard(boards[1], boards[0]);
13892     } else {
13893         currentMove = forwardMostMove = backwardMostMove = 0;
13894     }
13895     SendBoard(&first, forwardMostMove);
13896     if (appData.debugMode) {
13897         fprintf(debugFP, "EditPosDone\n");
13898     }
13899     DisplayTitle("");
13900     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13901     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13902     gameMode = EditGame;
13903     ModeHighlight();
13904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13905     ClearHighlights(); /* [AS] */
13906 }
13907
13908 /* Pause for `ms' milliseconds */
13909 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13910 void
13911 TimeDelay (long ms)
13912 {
13913     TimeMark m1, m2;
13914
13915     GetTimeMark(&m1);
13916     do {
13917         GetTimeMark(&m2);
13918     } while (SubtractTimeMarks(&m2, &m1) < ms);
13919 }
13920
13921 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13922 void
13923 SendMultiLineToICS (char *buf)
13924 {
13925     char temp[MSG_SIZ+1], *p;
13926     int len;
13927
13928     len = strlen(buf);
13929     if (len > MSG_SIZ)
13930       len = MSG_SIZ;
13931
13932     strncpy(temp, buf, len);
13933     temp[len] = 0;
13934
13935     p = temp;
13936     while (*p) {
13937         if (*p == '\n' || *p == '\r')
13938           *p = ' ';
13939         ++p;
13940     }
13941
13942     strcat(temp, "\n");
13943     SendToICS(temp);
13944     SendToPlayer(temp, strlen(temp));
13945 }
13946
13947 void
13948 SetWhiteToPlayEvent ()
13949 {
13950     if (gameMode == EditPosition) {
13951         blackPlaysFirst = FALSE;
13952         DisplayBothClocks();    /* works because currentMove is 0 */
13953     } else if (gameMode == IcsExamining) {
13954         SendToICS(ics_prefix);
13955         SendToICS("tomove white\n");
13956     }
13957 }
13958
13959 void
13960 SetBlackToPlayEvent ()
13961 {
13962     if (gameMode == EditPosition) {
13963         blackPlaysFirst = TRUE;
13964         currentMove = 1;        /* kludge */
13965         DisplayBothClocks();
13966         currentMove = 0;
13967     } else if (gameMode == IcsExamining) {
13968         SendToICS(ics_prefix);
13969         SendToICS("tomove black\n");
13970     }
13971 }
13972
13973 void
13974 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13975 {
13976     char buf[MSG_SIZ];
13977     ChessSquare piece = boards[0][y][x];
13978
13979     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13980
13981     switch (selection) {
13982       case ClearBoard:
13983         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13984             SendToICS(ics_prefix);
13985             SendToICS("bsetup clear\n");
13986         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13987             SendToICS(ics_prefix);
13988             SendToICS("clearboard\n");
13989         } else {
13990             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13991                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13992                 for (y = 0; y < BOARD_HEIGHT; y++) {
13993                     if (gameMode == IcsExamining) {
13994                         if (boards[currentMove][y][x] != EmptySquare) {
13995                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13996                                     AAA + x, ONE + y);
13997                             SendToICS(buf);
13998                         }
13999                     } else {
14000                         boards[0][y][x] = p;
14001                     }
14002                 }
14003             }
14004         }
14005         if (gameMode == EditPosition) {
14006             DrawPosition(FALSE, boards[0]);
14007         }
14008         break;
14009
14010       case WhitePlay:
14011         SetWhiteToPlayEvent();
14012         break;
14013
14014       case BlackPlay:
14015         SetBlackToPlayEvent();
14016         break;
14017
14018       case EmptySquare:
14019         if (gameMode == IcsExamining) {
14020             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14021             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14022             SendToICS(buf);
14023         } else {
14024             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14025                 if(x == BOARD_LEFT-2) {
14026                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14027                     boards[0][y][1] = 0;
14028                 } else
14029                 if(x == BOARD_RGHT+1) {
14030                     if(y >= gameInfo.holdingsSize) break;
14031                     boards[0][y][BOARD_WIDTH-2] = 0;
14032                 } else break;
14033             }
14034             boards[0][y][x] = EmptySquare;
14035             DrawPosition(FALSE, boards[0]);
14036         }
14037         break;
14038
14039       case PromotePiece:
14040         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14041            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14042             selection = (ChessSquare) (PROMOTED piece);
14043         } else if(piece == EmptySquare) selection = WhiteSilver;
14044         else selection = (ChessSquare)((int)piece - 1);
14045         goto defaultlabel;
14046
14047       case DemotePiece:
14048         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14049            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14050             selection = (ChessSquare) (DEMOTED piece);
14051         } else if(piece == EmptySquare) selection = BlackSilver;
14052         else selection = (ChessSquare)((int)piece + 1);
14053         goto defaultlabel;
14054
14055       case WhiteQueen:
14056       case BlackQueen:
14057         if(gameInfo.variant == VariantShatranj ||
14058            gameInfo.variant == VariantXiangqi  ||
14059            gameInfo.variant == VariantCourier  ||
14060            gameInfo.variant == VariantMakruk     )
14061             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14062         goto defaultlabel;
14063
14064       case WhiteKing:
14065       case BlackKing:
14066         if(gameInfo.variant == VariantXiangqi)
14067             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14068         if(gameInfo.variant == VariantKnightmate)
14069             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14070       default:
14071         defaultlabel:
14072         if (gameMode == IcsExamining) {
14073             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14074             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14075                      PieceToChar(selection), AAA + x, ONE + y);
14076             SendToICS(buf);
14077         } else {
14078             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14079                 int n;
14080                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14081                     n = PieceToNumber(selection - BlackPawn);
14082                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14083                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14084                     boards[0][BOARD_HEIGHT-1-n][1]++;
14085                 } else
14086                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14087                     n = PieceToNumber(selection);
14088                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14089                     boards[0][n][BOARD_WIDTH-1] = selection;
14090                     boards[0][n][BOARD_WIDTH-2]++;
14091                 }
14092             } else
14093             boards[0][y][x] = selection;
14094             DrawPosition(TRUE, boards[0]);
14095             ClearHighlights();
14096             fromX = fromY = -1;
14097         }
14098         break;
14099     }
14100 }
14101
14102
14103 void
14104 DropMenuEvent (ChessSquare selection, int x, int y)
14105 {
14106     ChessMove moveType;
14107
14108     switch (gameMode) {
14109       case IcsPlayingWhite:
14110       case MachinePlaysBlack:
14111         if (!WhiteOnMove(currentMove)) {
14112             DisplayMoveError(_("It is Black's turn"));
14113             return;
14114         }
14115         moveType = WhiteDrop;
14116         break;
14117       case IcsPlayingBlack:
14118       case MachinePlaysWhite:
14119         if (WhiteOnMove(currentMove)) {
14120             DisplayMoveError(_("It is White's turn"));
14121             return;
14122         }
14123         moveType = BlackDrop;
14124         break;
14125       case EditGame:
14126         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14127         break;
14128       default:
14129         return;
14130     }
14131
14132     if (moveType == BlackDrop && selection < BlackPawn) {
14133       selection = (ChessSquare) ((int) selection
14134                                  + (int) BlackPawn - (int) WhitePawn);
14135     }
14136     if (boards[currentMove][y][x] != EmptySquare) {
14137         DisplayMoveError(_("That square is occupied"));
14138         return;
14139     }
14140
14141     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14142 }
14143
14144 void
14145 AcceptEvent ()
14146 {
14147     /* Accept a pending offer of any kind from opponent */
14148
14149     if (appData.icsActive) {
14150         SendToICS(ics_prefix);
14151         SendToICS("accept\n");
14152     } else if (cmailMsgLoaded) {
14153         if (currentMove == cmailOldMove &&
14154             commentList[cmailOldMove] != NULL &&
14155             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14156                    "Black offers a draw" : "White offers a draw")) {
14157             TruncateGame();
14158             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14159             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14160         } else {
14161             DisplayError(_("There is no pending offer on this move"), 0);
14162             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14163         }
14164     } else {
14165         /* Not used for offers from chess program */
14166     }
14167 }
14168
14169 void
14170 DeclineEvent ()
14171 {
14172     /* Decline a pending offer of any kind from opponent */
14173
14174     if (appData.icsActive) {
14175         SendToICS(ics_prefix);
14176         SendToICS("decline\n");
14177     } else if (cmailMsgLoaded) {
14178         if (currentMove == cmailOldMove &&
14179             commentList[cmailOldMove] != NULL &&
14180             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14181                    "Black offers a draw" : "White offers a draw")) {
14182 #ifdef NOTDEF
14183             AppendComment(cmailOldMove, "Draw declined", TRUE);
14184             DisplayComment(cmailOldMove - 1, "Draw declined");
14185 #endif /*NOTDEF*/
14186         } else {
14187             DisplayError(_("There is no pending offer on this move"), 0);
14188         }
14189     } else {
14190         /* Not used for offers from chess program */
14191     }
14192 }
14193
14194 void
14195 RematchEvent ()
14196 {
14197     /* Issue ICS rematch command */
14198     if (appData.icsActive) {
14199         SendToICS(ics_prefix);
14200         SendToICS("rematch\n");
14201     }
14202 }
14203
14204 void
14205 CallFlagEvent ()
14206 {
14207     /* Call your opponent's flag (claim a win on time) */
14208     if (appData.icsActive) {
14209         SendToICS(ics_prefix);
14210         SendToICS("flag\n");
14211     } else {
14212         switch (gameMode) {
14213           default:
14214             return;
14215           case MachinePlaysWhite:
14216             if (whiteFlag) {
14217                 if (blackFlag)
14218                   GameEnds(GameIsDrawn, "Both players ran out of time",
14219                            GE_PLAYER);
14220                 else
14221                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14222             } else {
14223                 DisplayError(_("Your opponent is not out of time"), 0);
14224             }
14225             break;
14226           case MachinePlaysBlack:
14227             if (blackFlag) {
14228                 if (whiteFlag)
14229                   GameEnds(GameIsDrawn, "Both players ran out of time",
14230                            GE_PLAYER);
14231                 else
14232                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14233             } else {
14234                 DisplayError(_("Your opponent is not out of time"), 0);
14235             }
14236             break;
14237         }
14238     }
14239 }
14240
14241 void
14242 ClockClick (int which)
14243 {       // [HGM] code moved to back-end from winboard.c
14244         if(which) { // black clock
14245           if (gameMode == EditPosition || gameMode == IcsExamining) {
14246             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14247             SetBlackToPlayEvent();
14248           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14249           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14250           } else if (shiftKey) {
14251             AdjustClock(which, -1);
14252           } else if (gameMode == IcsPlayingWhite ||
14253                      gameMode == MachinePlaysBlack) {
14254             CallFlagEvent();
14255           }
14256         } else { // white clock
14257           if (gameMode == EditPosition || gameMode == IcsExamining) {
14258             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14259             SetWhiteToPlayEvent();
14260           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14261           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14262           } else if (shiftKey) {
14263             AdjustClock(which, -1);
14264           } else if (gameMode == IcsPlayingBlack ||
14265                    gameMode == MachinePlaysWhite) {
14266             CallFlagEvent();
14267           }
14268         }
14269 }
14270
14271 void
14272 DrawEvent ()
14273 {
14274     /* Offer draw or accept pending draw offer from opponent */
14275
14276     if (appData.icsActive) {
14277         /* Note: tournament rules require draw offers to be
14278            made after you make your move but before you punch
14279            your clock.  Currently ICS doesn't let you do that;
14280            instead, you immediately punch your clock after making
14281            a move, but you can offer a draw at any time. */
14282
14283         SendToICS(ics_prefix);
14284         SendToICS("draw\n");
14285         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14286     } else if (cmailMsgLoaded) {
14287         if (currentMove == cmailOldMove &&
14288             commentList[cmailOldMove] != NULL &&
14289             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14290                    "Black offers a draw" : "White offers a draw")) {
14291             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14292             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14293         } else if (currentMove == cmailOldMove + 1) {
14294             char *offer = WhiteOnMove(cmailOldMove) ?
14295               "White offers a draw" : "Black offers a draw";
14296             AppendComment(currentMove, offer, TRUE);
14297             DisplayComment(currentMove - 1, offer);
14298             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14299         } else {
14300             DisplayError(_("You must make your move before offering a draw"), 0);
14301             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14302         }
14303     } else if (first.offeredDraw) {
14304         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14305     } else {
14306         if (first.sendDrawOffers) {
14307             SendToProgram("draw\n", &first);
14308             userOfferedDraw = TRUE;
14309         }
14310     }
14311 }
14312
14313 void
14314 AdjournEvent ()
14315 {
14316     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14317
14318     if (appData.icsActive) {
14319         SendToICS(ics_prefix);
14320         SendToICS("adjourn\n");
14321     } else {
14322         /* Currently GNU Chess doesn't offer or accept Adjourns */
14323     }
14324 }
14325
14326
14327 void
14328 AbortEvent ()
14329 {
14330     /* Offer Abort or accept pending Abort offer from opponent */
14331
14332     if (appData.icsActive) {
14333         SendToICS(ics_prefix);
14334         SendToICS("abort\n");
14335     } else {
14336         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14337     }
14338 }
14339
14340 void
14341 ResignEvent ()
14342 {
14343     /* Resign.  You can do this even if it's not your turn. */
14344
14345     if (appData.icsActive) {
14346         SendToICS(ics_prefix);
14347         SendToICS("resign\n");
14348     } else {
14349         switch (gameMode) {
14350           case MachinePlaysWhite:
14351             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14352             break;
14353           case MachinePlaysBlack:
14354             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14355             break;
14356           case EditGame:
14357             if (cmailMsgLoaded) {
14358                 TruncateGame();
14359                 if (WhiteOnMove(cmailOldMove)) {
14360                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14361                 } else {
14362                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14363                 }
14364                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14365             }
14366             break;
14367           default:
14368             break;
14369         }
14370     }
14371 }
14372
14373
14374 void
14375 StopObservingEvent ()
14376 {
14377     /* Stop observing current games */
14378     SendToICS(ics_prefix);
14379     SendToICS("unobserve\n");
14380 }
14381
14382 void
14383 StopExaminingEvent ()
14384 {
14385     /* Stop observing current game */
14386     SendToICS(ics_prefix);
14387     SendToICS("unexamine\n");
14388 }
14389
14390 void
14391 ForwardInner (int target)
14392 {
14393     int limit; int oldSeekGraphUp = seekGraphUp;
14394
14395     if (appData.debugMode)
14396         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14397                 target, currentMove, forwardMostMove);
14398
14399     if (gameMode == EditPosition)
14400       return;
14401
14402     seekGraphUp = FALSE;
14403     MarkTargetSquares(1);
14404
14405     if (gameMode == PlayFromGameFile && !pausing)
14406       PauseEvent();
14407
14408     if (gameMode == IcsExamining && pausing)
14409       limit = pauseExamForwardMostMove;
14410     else
14411       limit = forwardMostMove;
14412
14413     if (target > limit) target = limit;
14414
14415     if (target > 0 && moveList[target - 1][0]) {
14416         int fromX, fromY, toX, toY;
14417         toX = moveList[target - 1][2] - AAA;
14418         toY = moveList[target - 1][3] - ONE;
14419         if (moveList[target - 1][1] == '@') {
14420             if (appData.highlightLastMove) {
14421                 SetHighlights(-1, -1, toX, toY);
14422             }
14423         } else {
14424             fromX = moveList[target - 1][0] - AAA;
14425             fromY = moveList[target - 1][1] - ONE;
14426             if (target == currentMove + 1) {
14427                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14428             }
14429             if (appData.highlightLastMove) {
14430                 SetHighlights(fromX, fromY, toX, toY);
14431             }
14432         }
14433     }
14434     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14435         gameMode == Training || gameMode == PlayFromGameFile ||
14436         gameMode == AnalyzeFile) {
14437         while (currentMove < target) {
14438             SendMoveToProgram(currentMove++, &first);
14439         }
14440     } else {
14441         currentMove = target;
14442     }
14443
14444     if (gameMode == EditGame || gameMode == EndOfGame) {
14445         whiteTimeRemaining = timeRemaining[0][currentMove];
14446         blackTimeRemaining = timeRemaining[1][currentMove];
14447     }
14448     DisplayBothClocks();
14449     DisplayMove(currentMove - 1);
14450     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14451     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14452     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14453         DisplayComment(currentMove - 1, commentList[currentMove]);
14454     }
14455     ClearMap(); // [HGM] exclude: invalidate map
14456 }
14457
14458
14459 void
14460 ForwardEvent ()
14461 {
14462     if (gameMode == IcsExamining && !pausing) {
14463         SendToICS(ics_prefix);
14464         SendToICS("forward\n");
14465     } else {
14466         ForwardInner(currentMove + 1);
14467     }
14468 }
14469
14470 void
14471 ToEndEvent ()
14472 {
14473     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14474         /* to optimze, we temporarily turn off analysis mode while we feed
14475          * the remaining moves to the engine. Otherwise we get analysis output
14476          * after each move.
14477          */
14478         if (first.analysisSupport) {
14479           SendToProgram("exit\nforce\n", &first);
14480           first.analyzing = FALSE;
14481         }
14482     }
14483
14484     if (gameMode == IcsExamining && !pausing) {
14485         SendToICS(ics_prefix);
14486         SendToICS("forward 999999\n");
14487     } else {
14488         ForwardInner(forwardMostMove);
14489     }
14490
14491     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14492         /* we have fed all the moves, so reactivate analysis mode */
14493         SendToProgram("analyze\n", &first);
14494         first.analyzing = TRUE;
14495         /*first.maybeThinking = TRUE;*/
14496         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14497     }
14498 }
14499
14500 void
14501 BackwardInner (int target)
14502 {
14503     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14504
14505     if (appData.debugMode)
14506         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14507                 target, currentMove, forwardMostMove);
14508
14509     if (gameMode == EditPosition) return;
14510     seekGraphUp = FALSE;
14511     MarkTargetSquares(1);
14512     if (currentMove <= backwardMostMove) {
14513         ClearHighlights();
14514         DrawPosition(full_redraw, boards[currentMove]);
14515         return;
14516     }
14517     if (gameMode == PlayFromGameFile && !pausing)
14518       PauseEvent();
14519
14520     if (moveList[target][0]) {
14521         int fromX, fromY, toX, toY;
14522         toX = moveList[target][2] - AAA;
14523         toY = moveList[target][3] - ONE;
14524         if (moveList[target][1] == '@') {
14525             if (appData.highlightLastMove) {
14526                 SetHighlights(-1, -1, toX, toY);
14527             }
14528         } else {
14529             fromX = moveList[target][0] - AAA;
14530             fromY = moveList[target][1] - ONE;
14531             if (target == currentMove - 1) {
14532                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14533             }
14534             if (appData.highlightLastMove) {
14535                 SetHighlights(fromX, fromY, toX, toY);
14536             }
14537         }
14538     }
14539     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14540         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14541         while (currentMove > target) {
14542             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14543                 // null move cannot be undone. Reload program with move history before it.
14544                 int i;
14545                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14546                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14547                 }
14548                 SendBoard(&first, i); 
14549                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14550                 break;
14551             }
14552             SendToProgram("undo\n", &first);
14553             currentMove--;
14554         }
14555     } else {
14556         currentMove = target;
14557     }
14558
14559     if (gameMode == EditGame || gameMode == EndOfGame) {
14560         whiteTimeRemaining = timeRemaining[0][currentMove];
14561         blackTimeRemaining = timeRemaining[1][currentMove];
14562     }
14563     DisplayBothClocks();
14564     DisplayMove(currentMove - 1);
14565     DrawPosition(full_redraw, boards[currentMove]);
14566     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14567     // [HGM] PV info: routine tests if comment empty
14568     DisplayComment(currentMove - 1, commentList[currentMove]);
14569     ClearMap(); // [HGM] exclude: invalidate map
14570 }
14571
14572 void
14573 BackwardEvent ()
14574 {
14575     if (gameMode == IcsExamining && !pausing) {
14576         SendToICS(ics_prefix);
14577         SendToICS("backward\n");
14578     } else {
14579         BackwardInner(currentMove - 1);
14580     }
14581 }
14582
14583 void
14584 ToStartEvent ()
14585 {
14586     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14587         /* to optimize, we temporarily turn off analysis mode while we undo
14588          * all the moves. Otherwise we get analysis output after each undo.
14589          */
14590         if (first.analysisSupport) {
14591           SendToProgram("exit\nforce\n", &first);
14592           first.analyzing = FALSE;
14593         }
14594     }
14595
14596     if (gameMode == IcsExamining && !pausing) {
14597         SendToICS(ics_prefix);
14598         SendToICS("backward 999999\n");
14599     } else {
14600         BackwardInner(backwardMostMove);
14601     }
14602
14603     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14604         /* we have fed all the moves, so reactivate analysis mode */
14605         SendToProgram("analyze\n", &first);
14606         first.analyzing = TRUE;
14607         /*first.maybeThinking = TRUE;*/
14608         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14609     }
14610 }
14611
14612 void
14613 ToNrEvent (int to)
14614 {
14615   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14616   if (to >= forwardMostMove) to = forwardMostMove;
14617   if (to <= backwardMostMove) to = backwardMostMove;
14618   if (to < currentMove) {
14619     BackwardInner(to);
14620   } else {
14621     ForwardInner(to);
14622   }
14623 }
14624
14625 void
14626 RevertEvent (Boolean annotate)
14627 {
14628     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14629         return;
14630     }
14631     if (gameMode != IcsExamining) {
14632         DisplayError(_("You are not examining a game"), 0);
14633         return;
14634     }
14635     if (pausing) {
14636         DisplayError(_("You can't revert while pausing"), 0);
14637         return;
14638     }
14639     SendToICS(ics_prefix);
14640     SendToICS("revert\n");
14641 }
14642
14643 void
14644 RetractMoveEvent ()
14645 {
14646     switch (gameMode) {
14647       case MachinePlaysWhite:
14648       case MachinePlaysBlack:
14649         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14650             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14651             return;
14652         }
14653         if (forwardMostMove < 2) return;
14654         currentMove = forwardMostMove = forwardMostMove - 2;
14655         whiteTimeRemaining = timeRemaining[0][currentMove];
14656         blackTimeRemaining = timeRemaining[1][currentMove];
14657         DisplayBothClocks();
14658         DisplayMove(currentMove - 1);
14659         ClearHighlights();/*!! could figure this out*/
14660         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14661         SendToProgram("remove\n", &first);
14662         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14663         break;
14664
14665       case BeginningOfGame:
14666       default:
14667         break;
14668
14669       case IcsPlayingWhite:
14670       case IcsPlayingBlack:
14671         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14672             SendToICS(ics_prefix);
14673             SendToICS("takeback 2\n");
14674         } else {
14675             SendToICS(ics_prefix);
14676             SendToICS("takeback 1\n");
14677         }
14678         break;
14679     }
14680 }
14681
14682 void
14683 MoveNowEvent ()
14684 {
14685     ChessProgramState *cps;
14686
14687     switch (gameMode) {
14688       case MachinePlaysWhite:
14689         if (!WhiteOnMove(forwardMostMove)) {
14690             DisplayError(_("It is your turn"), 0);
14691             return;
14692         }
14693         cps = &first;
14694         break;
14695       case MachinePlaysBlack:
14696         if (WhiteOnMove(forwardMostMove)) {
14697             DisplayError(_("It is your turn"), 0);
14698             return;
14699         }
14700         cps = &first;
14701         break;
14702       case TwoMachinesPlay:
14703         if (WhiteOnMove(forwardMostMove) ==
14704             (first.twoMachinesColor[0] == 'w')) {
14705             cps = &first;
14706         } else {
14707             cps = &second;
14708         }
14709         break;
14710       case BeginningOfGame:
14711       default:
14712         return;
14713     }
14714     SendToProgram("?\n", cps);
14715 }
14716
14717 void
14718 TruncateGameEvent ()
14719 {
14720     EditGameEvent();
14721     if (gameMode != EditGame) return;
14722     TruncateGame();
14723 }
14724
14725 void
14726 TruncateGame ()
14727 {
14728     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14729     if (forwardMostMove > currentMove) {
14730         if (gameInfo.resultDetails != NULL) {
14731             free(gameInfo.resultDetails);
14732             gameInfo.resultDetails = NULL;
14733             gameInfo.result = GameUnfinished;
14734         }
14735         forwardMostMove = currentMove;
14736         HistorySet(parseList, backwardMostMove, forwardMostMove,
14737                    currentMove-1);
14738     }
14739 }
14740
14741 void
14742 HintEvent ()
14743 {
14744     if (appData.noChessProgram) return;
14745     switch (gameMode) {
14746       case MachinePlaysWhite:
14747         if (WhiteOnMove(forwardMostMove)) {
14748             DisplayError(_("Wait until your turn"), 0);
14749             return;
14750         }
14751         break;
14752       case BeginningOfGame:
14753       case MachinePlaysBlack:
14754         if (!WhiteOnMove(forwardMostMove)) {
14755             DisplayError(_("Wait until your turn"), 0);
14756             return;
14757         }
14758         break;
14759       default:
14760         DisplayError(_("No hint available"), 0);
14761         return;
14762     }
14763     SendToProgram("hint\n", &first);
14764     hintRequested = TRUE;
14765 }
14766
14767 void
14768 BookEvent ()
14769 {
14770     if (appData.noChessProgram) return;
14771     switch (gameMode) {
14772       case MachinePlaysWhite:
14773         if (WhiteOnMove(forwardMostMove)) {
14774             DisplayError(_("Wait until your turn"), 0);
14775             return;
14776         }
14777         break;
14778       case BeginningOfGame:
14779       case MachinePlaysBlack:
14780         if (!WhiteOnMove(forwardMostMove)) {
14781             DisplayError(_("Wait until your turn"), 0);
14782             return;
14783         }
14784         break;
14785       case EditPosition:
14786         EditPositionDone(TRUE);
14787         break;
14788       case TwoMachinesPlay:
14789         return;
14790       default:
14791         break;
14792     }
14793     SendToProgram("bk\n", &first);
14794     bookOutput[0] = NULLCHAR;
14795     bookRequested = TRUE;
14796 }
14797
14798 void
14799 AboutGameEvent ()
14800 {
14801     char *tags = PGNTags(&gameInfo);
14802     TagsPopUp(tags, CmailMsg());
14803     free(tags);
14804 }
14805
14806 /* end button procedures */
14807
14808 void
14809 PrintPosition (FILE *fp, int move)
14810 {
14811     int i, j;
14812
14813     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14814         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14815             char c = PieceToChar(boards[move][i][j]);
14816             fputc(c == 'x' ? '.' : c, fp);
14817             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14818         }
14819     }
14820     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14821       fprintf(fp, "white to play\n");
14822     else
14823       fprintf(fp, "black to play\n");
14824 }
14825
14826 void
14827 PrintOpponents (FILE *fp)
14828 {
14829     if (gameInfo.white != NULL) {
14830         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14831     } else {
14832         fprintf(fp, "\n");
14833     }
14834 }
14835
14836 /* Find last component of program's own name, using some heuristics */
14837 void
14838 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14839 {
14840     char *p, *q, c;
14841     int local = (strcmp(host, "localhost") == 0);
14842     while (!local && (p = strchr(prog, ';')) != NULL) {
14843         p++;
14844         while (*p == ' ') p++;
14845         prog = p;
14846     }
14847     if (*prog == '"' || *prog == '\'') {
14848         q = strchr(prog + 1, *prog);
14849     } else {
14850         q = strchr(prog, ' ');
14851     }
14852     if (q == NULL) q = prog + strlen(prog);
14853     p = q;
14854     while (p >= prog && *p != '/' && *p != '\\') p--;
14855     p++;
14856     if(p == prog && *p == '"') p++;
14857     c = *q; *q = 0;
14858     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14859     memcpy(buf, p, q - p);
14860     buf[q - p] = NULLCHAR;
14861     if (!local) {
14862         strcat(buf, "@");
14863         strcat(buf, host);
14864     }
14865 }
14866
14867 char *
14868 TimeControlTagValue ()
14869 {
14870     char buf[MSG_SIZ];
14871     if (!appData.clockMode) {
14872       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14873     } else if (movesPerSession > 0) {
14874       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14875     } else if (timeIncrement == 0) {
14876       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14877     } else {
14878       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14879     }
14880     return StrSave(buf);
14881 }
14882
14883 void
14884 SetGameInfo ()
14885 {
14886     /* This routine is used only for certain modes */
14887     VariantClass v = gameInfo.variant;
14888     ChessMove r = GameUnfinished;
14889     char *p = NULL;
14890
14891     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14892         r = gameInfo.result;
14893         p = gameInfo.resultDetails;
14894         gameInfo.resultDetails = NULL;
14895     }
14896     ClearGameInfo(&gameInfo);
14897     gameInfo.variant = v;
14898
14899     switch (gameMode) {
14900       case MachinePlaysWhite:
14901         gameInfo.event = StrSave( appData.pgnEventHeader );
14902         gameInfo.site = StrSave(HostName());
14903         gameInfo.date = PGNDate();
14904         gameInfo.round = StrSave("-");
14905         gameInfo.white = StrSave(first.tidy);
14906         gameInfo.black = StrSave(UserName());
14907         gameInfo.timeControl = TimeControlTagValue();
14908         break;
14909
14910       case MachinePlaysBlack:
14911         gameInfo.event = StrSave( appData.pgnEventHeader );
14912         gameInfo.site = StrSave(HostName());
14913         gameInfo.date = PGNDate();
14914         gameInfo.round = StrSave("-");
14915         gameInfo.white = StrSave(UserName());
14916         gameInfo.black = StrSave(first.tidy);
14917         gameInfo.timeControl = TimeControlTagValue();
14918         break;
14919
14920       case TwoMachinesPlay:
14921         gameInfo.event = StrSave( appData.pgnEventHeader );
14922         gameInfo.site = StrSave(HostName());
14923         gameInfo.date = PGNDate();
14924         if (roundNr > 0) {
14925             char buf[MSG_SIZ];
14926             snprintf(buf, MSG_SIZ, "%d", roundNr);
14927             gameInfo.round = StrSave(buf);
14928         } else {
14929             gameInfo.round = StrSave("-");
14930         }
14931         if (first.twoMachinesColor[0] == 'w') {
14932             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14933             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14934         } else {
14935             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14936             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14937         }
14938         gameInfo.timeControl = TimeControlTagValue();
14939         break;
14940
14941       case EditGame:
14942         gameInfo.event = StrSave("Edited game");
14943         gameInfo.site = StrSave(HostName());
14944         gameInfo.date = PGNDate();
14945         gameInfo.round = StrSave("-");
14946         gameInfo.white = StrSave("-");
14947         gameInfo.black = StrSave("-");
14948         gameInfo.result = r;
14949         gameInfo.resultDetails = p;
14950         break;
14951
14952       case EditPosition:
14953         gameInfo.event = StrSave("Edited position");
14954         gameInfo.site = StrSave(HostName());
14955         gameInfo.date = PGNDate();
14956         gameInfo.round = StrSave("-");
14957         gameInfo.white = StrSave("-");
14958         gameInfo.black = StrSave("-");
14959         break;
14960
14961       case IcsPlayingWhite:
14962       case IcsPlayingBlack:
14963       case IcsObserving:
14964       case IcsExamining:
14965         break;
14966
14967       case PlayFromGameFile:
14968         gameInfo.event = StrSave("Game from non-PGN file");
14969         gameInfo.site = StrSave(HostName());
14970         gameInfo.date = PGNDate();
14971         gameInfo.round = StrSave("-");
14972         gameInfo.white = StrSave("?");
14973         gameInfo.black = StrSave("?");
14974         break;
14975
14976       default:
14977         break;
14978     }
14979 }
14980
14981 void
14982 ReplaceComment (int index, char *text)
14983 {
14984     int len;
14985     char *p;
14986     float score;
14987
14988     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14989        pvInfoList[index-1].depth == len &&
14990        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14991        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14992     while (*text == '\n') text++;
14993     len = strlen(text);
14994     while (len > 0 && text[len - 1] == '\n') len--;
14995
14996     if (commentList[index] != NULL)
14997       free(commentList[index]);
14998
14999     if (len == 0) {
15000         commentList[index] = NULL;
15001         return;
15002     }
15003   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15004       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15005       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15006     commentList[index] = (char *) malloc(len + 2);
15007     strncpy(commentList[index], text, len);
15008     commentList[index][len] = '\n';
15009     commentList[index][len + 1] = NULLCHAR;
15010   } else {
15011     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15012     char *p;
15013     commentList[index] = (char *) malloc(len + 7);
15014     safeStrCpy(commentList[index], "{\n", 3);
15015     safeStrCpy(commentList[index]+2, text, len+1);
15016     commentList[index][len+2] = NULLCHAR;
15017     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15018     strcat(commentList[index], "\n}\n");
15019   }
15020 }
15021
15022 void
15023 CrushCRs (char *text)
15024 {
15025   char *p = text;
15026   char *q = text;
15027   char ch;
15028
15029   do {
15030     ch = *p++;
15031     if (ch == '\r') continue;
15032     *q++ = ch;
15033   } while (ch != '\0');
15034 }
15035
15036 void
15037 AppendComment (int index, char *text, Boolean addBraces)
15038 /* addBraces  tells if we should add {} */
15039 {
15040     int oldlen, len;
15041     char *old;
15042
15043 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15044     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15045
15046     CrushCRs(text);
15047     while (*text == '\n') text++;
15048     len = strlen(text);
15049     while (len > 0 && text[len - 1] == '\n') len--;
15050     text[len] = NULLCHAR;
15051
15052     if (len == 0) return;
15053
15054     if (commentList[index] != NULL) {
15055       Boolean addClosingBrace = addBraces;
15056         old = commentList[index];
15057         oldlen = strlen(old);
15058         while(commentList[index][oldlen-1] ==  '\n')
15059           commentList[index][--oldlen] = NULLCHAR;
15060         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15061         safeStrCpy(commentList[index], old, oldlen + len + 6);
15062         free(old);
15063         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15064         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15065           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15066           while (*text == '\n') { text++; len--; }
15067           commentList[index][--oldlen] = NULLCHAR;
15068       }
15069         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15070         else          strcat(commentList[index], "\n");
15071         strcat(commentList[index], text);
15072         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15073         else          strcat(commentList[index], "\n");
15074     } else {
15075         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15076         if(addBraces)
15077           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15078         else commentList[index][0] = NULLCHAR;
15079         strcat(commentList[index], text);
15080         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15081         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15082     }
15083 }
15084
15085 static char *
15086 FindStr (char * text, char * sub_text)
15087 {
15088     char * result = strstr( text, sub_text );
15089
15090     if( result != NULL ) {
15091         result += strlen( sub_text );
15092     }
15093
15094     return result;
15095 }
15096
15097 /* [AS] Try to extract PV info from PGN comment */
15098 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15099 char *
15100 GetInfoFromComment (int index, char * text)
15101 {
15102     char * sep = text, *p;
15103
15104     if( text != NULL && index > 0 ) {
15105         int score = 0;
15106         int depth = 0;
15107         int time = -1, sec = 0, deci;
15108         char * s_eval = FindStr( text, "[%eval " );
15109         char * s_emt = FindStr( text, "[%emt " );
15110
15111         if( s_eval != NULL || s_emt != NULL ) {
15112             /* New style */
15113             char delim;
15114
15115             if( s_eval != NULL ) {
15116                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15117                     return text;
15118                 }
15119
15120                 if( delim != ']' ) {
15121                     return text;
15122                 }
15123             }
15124
15125             if( s_emt != NULL ) {
15126             }
15127                 return text;
15128         }
15129         else {
15130             /* We expect something like: [+|-]nnn.nn/dd */
15131             int score_lo = 0;
15132
15133             if(*text != '{') return text; // [HGM] braces: must be normal comment
15134
15135             sep = strchr( text, '/' );
15136             if( sep == NULL || sep < (text+4) ) {
15137                 return text;
15138             }
15139
15140             p = text;
15141             if(p[1] == '(') { // comment starts with PV
15142                p = strchr(p, ')'); // locate end of PV
15143                if(p == NULL || sep < p+5) return text;
15144                // at this point we have something like "{(.*) +0.23/6 ..."
15145                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15146                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15147                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15148             }
15149             time = -1; sec = -1; deci = -1;
15150             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15151                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15152                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15153                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15154                 return text;
15155             }
15156
15157             if( score_lo < 0 || score_lo >= 100 ) {
15158                 return text;
15159             }
15160
15161             if(sec >= 0) time = 600*time + 10*sec; else
15162             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15163
15164             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15165
15166             /* [HGM] PV time: now locate end of PV info */
15167             while( *++sep >= '0' && *sep <= '9'); // strip depth
15168             if(time >= 0)
15169             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15170             if(sec >= 0)
15171             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15172             if(deci >= 0)
15173             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15174             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15175         }
15176
15177         if( depth <= 0 ) {
15178             return text;
15179         }
15180
15181         if( time < 0 ) {
15182             time = -1;
15183         }
15184
15185         pvInfoList[index-1].depth = depth;
15186         pvInfoList[index-1].score = score;
15187         pvInfoList[index-1].time  = 10*time; // centi-sec
15188         if(*sep == '}') *sep = 0; else *--sep = '{';
15189         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15190     }
15191     return sep;
15192 }
15193
15194 void
15195 SendToProgram (char *message, ChessProgramState *cps)
15196 {
15197     int count, outCount, error;
15198     char buf[MSG_SIZ];
15199
15200     if (cps->pr == NoProc) return;
15201     Attention(cps);
15202
15203     if (appData.debugMode) {
15204         TimeMark now;
15205         GetTimeMark(&now);
15206         fprintf(debugFP, "%ld >%-6s: %s",
15207                 SubtractTimeMarks(&now, &programStartTime),
15208                 cps->which, message);
15209         if(serverFP)
15210             fprintf(serverFP, "%ld >%-6s: %s",
15211                 SubtractTimeMarks(&now, &programStartTime),
15212                 cps->which, message), fflush(serverFP);
15213     }
15214
15215     count = strlen(message);
15216     outCount = OutputToProcess(cps->pr, message, count, &error);
15217     if (outCount < count && !exiting
15218                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15219       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15220       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15221         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15222             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15223                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15224                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15225                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15226             } else {
15227                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15228                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15229                 gameInfo.result = res;
15230             }
15231             gameInfo.resultDetails = StrSave(buf);
15232         }
15233         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15234         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15235     }
15236 }
15237
15238 void
15239 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15240 {
15241     char *end_str;
15242     char buf[MSG_SIZ];
15243     ChessProgramState *cps = (ChessProgramState *)closure;
15244
15245     if (isr != cps->isr) return; /* Killed intentionally */
15246     if (count <= 0) {
15247         if (count == 0) {
15248             RemoveInputSource(cps->isr);
15249             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15250             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15251                     _(cps->which), cps->program);
15252         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15253                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15254                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15255                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15256                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15257                 } else {
15258                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15259                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15260                     gameInfo.result = res;
15261                 }
15262                 gameInfo.resultDetails = StrSave(buf);
15263             }
15264             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15265             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15266         } else {
15267             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15268                     _(cps->which), cps->program);
15269             RemoveInputSource(cps->isr);
15270
15271             /* [AS] Program is misbehaving badly... kill it */
15272             if( count == -2 ) {
15273                 DestroyChildProcess( cps->pr, 9 );
15274                 cps->pr = NoProc;
15275             }
15276
15277             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15278         }
15279         return;
15280     }
15281
15282     if ((end_str = strchr(message, '\r')) != NULL)
15283       *end_str = NULLCHAR;
15284     if ((end_str = strchr(message, '\n')) != NULL)
15285       *end_str = NULLCHAR;
15286
15287     if (appData.debugMode) {
15288         TimeMark now; int print = 1;
15289         char *quote = ""; char c; int i;
15290
15291         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15292                 char start = message[0];
15293                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15294                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15295                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15296                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15297                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15298                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15299                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15300                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15301                    sscanf(message, "hint: %c", &c)!=1 && 
15302                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15303                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15304                     print = (appData.engineComments >= 2);
15305                 }
15306                 message[0] = start; // restore original message
15307         }
15308         if(print) {
15309                 GetTimeMark(&now);
15310                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15311                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15312                         quote,
15313                         message);
15314                 if(serverFP)
15315                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15316                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15317                         quote,
15318                         message), fflush(serverFP);
15319         }
15320     }
15321
15322     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15323     if (appData.icsEngineAnalyze) {
15324         if (strstr(message, "whisper") != NULL ||
15325              strstr(message, "kibitz") != NULL ||
15326             strstr(message, "tellics") != NULL) return;
15327     }
15328
15329     HandleMachineMove(message, cps);
15330 }
15331
15332
15333 void
15334 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15335 {
15336     char buf[MSG_SIZ];
15337     int seconds;
15338
15339     if( timeControl_2 > 0 ) {
15340         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15341             tc = timeControl_2;
15342         }
15343     }
15344     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15345     inc /= cps->timeOdds;
15346     st  /= cps->timeOdds;
15347
15348     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15349
15350     if (st > 0) {
15351       /* Set exact time per move, normally using st command */
15352       if (cps->stKludge) {
15353         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15354         seconds = st % 60;
15355         if (seconds == 0) {
15356           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15357         } else {
15358           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15359         }
15360       } else {
15361         snprintf(buf, MSG_SIZ, "st %d\n", st);
15362       }
15363     } else {
15364       /* Set conventional or incremental time control, using level command */
15365       if (seconds == 0) {
15366         /* Note old gnuchess bug -- minutes:seconds used to not work.
15367            Fixed in later versions, but still avoid :seconds
15368            when seconds is 0. */
15369         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15370       } else {
15371         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15372                  seconds, inc/1000.);
15373       }
15374     }
15375     SendToProgram(buf, cps);
15376
15377     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15378     /* Orthogonally, limit search to given depth */
15379     if (sd > 0) {
15380       if (cps->sdKludge) {
15381         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15382       } else {
15383         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15384       }
15385       SendToProgram(buf, cps);
15386     }
15387
15388     if(cps->nps >= 0) { /* [HGM] nps */
15389         if(cps->supportsNPS == FALSE)
15390           cps->nps = -1; // don't use if engine explicitly says not supported!
15391         else {
15392           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15393           SendToProgram(buf, cps);
15394         }
15395     }
15396 }
15397
15398 ChessProgramState *
15399 WhitePlayer ()
15400 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15401 {
15402     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15403        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15404         return &second;
15405     return &first;
15406 }
15407
15408 void
15409 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15410 {
15411     char message[MSG_SIZ];
15412     long time, otime;
15413
15414     /* Note: this routine must be called when the clocks are stopped
15415        or when they have *just* been set or switched; otherwise
15416        it will be off by the time since the current tick started.
15417     */
15418     if (machineWhite) {
15419         time = whiteTimeRemaining / 10;
15420         otime = blackTimeRemaining / 10;
15421     } else {
15422         time = blackTimeRemaining / 10;
15423         otime = whiteTimeRemaining / 10;
15424     }
15425     /* [HGM] translate opponent's time by time-odds factor */
15426     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15427
15428     if (time <= 0) time = 1;
15429     if (otime <= 0) otime = 1;
15430
15431     snprintf(message, MSG_SIZ, "time %ld\n", time);
15432     SendToProgram(message, cps);
15433
15434     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15435     SendToProgram(message, cps);
15436 }
15437
15438 int
15439 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15440 {
15441   char buf[MSG_SIZ];
15442   int len = strlen(name);
15443   int val;
15444
15445   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15446     (*p) += len + 1;
15447     sscanf(*p, "%d", &val);
15448     *loc = (val != 0);
15449     while (**p && **p != ' ')
15450       (*p)++;
15451     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15452     SendToProgram(buf, cps);
15453     return TRUE;
15454   }
15455   return FALSE;
15456 }
15457
15458 int
15459 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15460 {
15461   char buf[MSG_SIZ];
15462   int len = strlen(name);
15463   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15464     (*p) += len + 1;
15465     sscanf(*p, "%d", loc);
15466     while (**p && **p != ' ') (*p)++;
15467     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15468     SendToProgram(buf, cps);
15469     return TRUE;
15470   }
15471   return FALSE;
15472 }
15473
15474 int
15475 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15476 {
15477   char buf[MSG_SIZ];
15478   int len = strlen(name);
15479   if (strncmp((*p), name, len) == 0
15480       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15481     (*p) += len + 2;
15482     sscanf(*p, "%[^\"]", loc);
15483     while (**p && **p != '\"') (*p)++;
15484     if (**p == '\"') (*p)++;
15485     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15486     SendToProgram(buf, cps);
15487     return TRUE;
15488   }
15489   return FALSE;
15490 }
15491
15492 int
15493 ParseOption (Option *opt, ChessProgramState *cps)
15494 // [HGM] options: process the string that defines an engine option, and determine
15495 // name, type, default value, and allowed value range
15496 {
15497         char *p, *q, buf[MSG_SIZ];
15498         int n, min = (-1)<<31, max = 1<<31, def;
15499
15500         if(p = strstr(opt->name, " -spin ")) {
15501             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15502             if(max < min) max = min; // enforce consistency
15503             if(def < min) def = min;
15504             if(def > max) def = max;
15505             opt->value = def;
15506             opt->min = min;
15507             opt->max = max;
15508             opt->type = Spin;
15509         } else if((p = strstr(opt->name, " -slider "))) {
15510             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15511             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15512             if(max < min) max = min; // enforce consistency
15513             if(def < min) def = min;
15514             if(def > max) def = max;
15515             opt->value = def;
15516             opt->min = min;
15517             opt->max = max;
15518             opt->type = Spin; // Slider;
15519         } else if((p = strstr(opt->name, " -string "))) {
15520             opt->textValue = p+9;
15521             opt->type = TextBox;
15522         } else if((p = strstr(opt->name, " -file "))) {
15523             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15524             opt->textValue = p+7;
15525             opt->type = FileName; // FileName;
15526         } else if((p = strstr(opt->name, " -path "))) {
15527             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15528             opt->textValue = p+7;
15529             opt->type = PathName; // PathName;
15530         } else if(p = strstr(opt->name, " -check ")) {
15531             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15532             opt->value = (def != 0);
15533             opt->type = CheckBox;
15534         } else if(p = strstr(opt->name, " -combo ")) {
15535             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15536             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15537             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15538             opt->value = n = 0;
15539             while(q = StrStr(q, " /// ")) {
15540                 n++; *q = 0;    // count choices, and null-terminate each of them
15541                 q += 5;
15542                 if(*q == '*') { // remember default, which is marked with * prefix
15543                     q++;
15544                     opt->value = n;
15545                 }
15546                 cps->comboList[cps->comboCnt++] = q;
15547             }
15548             cps->comboList[cps->comboCnt++] = NULL;
15549             opt->max = n + 1;
15550             opt->type = ComboBox;
15551         } else if(p = strstr(opt->name, " -button")) {
15552             opt->type = Button;
15553         } else if(p = strstr(opt->name, " -save")) {
15554             opt->type = SaveButton;
15555         } else return FALSE;
15556         *p = 0; // terminate option name
15557         // now look if the command-line options define a setting for this engine option.
15558         if(cps->optionSettings && cps->optionSettings[0])
15559             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15560         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15561           snprintf(buf, MSG_SIZ, "option %s", p);
15562                 if(p = strstr(buf, ",")) *p = 0;
15563                 if(q = strchr(buf, '=')) switch(opt->type) {
15564                     case ComboBox:
15565                         for(n=0; n<opt->max; n++)
15566                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15567                         break;
15568                     case TextBox:
15569                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15570                         break;
15571                     case Spin:
15572                     case CheckBox:
15573                         opt->value = atoi(q+1);
15574                     default:
15575                         break;
15576                 }
15577                 strcat(buf, "\n");
15578                 SendToProgram(buf, cps);
15579         }
15580         return TRUE;
15581 }
15582
15583 void
15584 FeatureDone (ChessProgramState *cps, int val)
15585 {
15586   DelayedEventCallback cb = GetDelayedEvent();
15587   if ((cb == InitBackEnd3 && cps == &first) ||
15588       (cb == SettingsMenuIfReady && cps == &second) ||
15589       (cb == LoadEngine) ||
15590       (cb == TwoMachinesEventIfReady)) {
15591     CancelDelayedEvent();
15592     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15593   }
15594   cps->initDone = val;
15595 }
15596
15597 /* Parse feature command from engine */
15598 void
15599 ParseFeatures (char *args, ChessProgramState *cps)
15600 {
15601   char *p = args;
15602   char *q;
15603   int val;
15604   char buf[MSG_SIZ];
15605
15606   for (;;) {
15607     while (*p == ' ') p++;
15608     if (*p == NULLCHAR) return;
15609
15610     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15611     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15612     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15613     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15614     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15615     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15616     if (BoolFeature(&p, "reuse", &val, cps)) {
15617       /* Engine can disable reuse, but can't enable it if user said no */
15618       if (!val) cps->reuse = FALSE;
15619       continue;
15620     }
15621     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15622     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15623       if (gameMode == TwoMachinesPlay) {
15624         DisplayTwoMachinesTitle();
15625       } else {
15626         DisplayTitle("");
15627       }
15628       continue;
15629     }
15630     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15631     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15632     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15633     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15634     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15635     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15636     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15637     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15638     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15639     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15640     if (IntFeature(&p, "done", &val, cps)) {
15641       FeatureDone(cps, val);
15642       continue;
15643     }
15644     /* Added by Tord: */
15645     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15646     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15647     /* End of additions by Tord */
15648
15649     /* [HGM] added features: */
15650     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15651     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15652     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15653     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15654     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15655     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15656     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15657         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15658           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15659             SendToProgram(buf, cps);
15660             continue;
15661         }
15662         if(cps->nrOptions >= MAX_OPTIONS) {
15663             cps->nrOptions--;
15664             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15665             DisplayError(buf, 0);
15666         }
15667         continue;
15668     }
15669     /* End of additions by HGM */
15670
15671     /* unknown feature: complain and skip */
15672     q = p;
15673     while (*q && *q != '=') q++;
15674     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15675     SendToProgram(buf, cps);
15676     p = q;
15677     if (*p == '=') {
15678       p++;
15679       if (*p == '\"') {
15680         p++;
15681         while (*p && *p != '\"') p++;
15682         if (*p == '\"') p++;
15683       } else {
15684         while (*p && *p != ' ') p++;
15685       }
15686     }
15687   }
15688
15689 }
15690
15691 void
15692 PeriodicUpdatesEvent (int newState)
15693 {
15694     if (newState == appData.periodicUpdates)
15695       return;
15696
15697     appData.periodicUpdates=newState;
15698
15699     /* Display type changes, so update it now */
15700 //    DisplayAnalysis();
15701
15702     /* Get the ball rolling again... */
15703     if (newState) {
15704         AnalysisPeriodicEvent(1);
15705         StartAnalysisClock();
15706     }
15707 }
15708
15709 void
15710 PonderNextMoveEvent (int newState)
15711 {
15712     if (newState == appData.ponderNextMove) return;
15713     if (gameMode == EditPosition) EditPositionDone(TRUE);
15714     if (newState) {
15715         SendToProgram("hard\n", &first);
15716         if (gameMode == TwoMachinesPlay) {
15717             SendToProgram("hard\n", &second);
15718         }
15719     } else {
15720         SendToProgram("easy\n", &first);
15721         thinkOutput[0] = NULLCHAR;
15722         if (gameMode == TwoMachinesPlay) {
15723             SendToProgram("easy\n", &second);
15724         }
15725     }
15726     appData.ponderNextMove = newState;
15727 }
15728
15729 void
15730 NewSettingEvent (int option, int *feature, char *command, int value)
15731 {
15732     char buf[MSG_SIZ];
15733
15734     if (gameMode == EditPosition) EditPositionDone(TRUE);
15735     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15736     if(feature == NULL || *feature) SendToProgram(buf, &first);
15737     if (gameMode == TwoMachinesPlay) {
15738         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15739     }
15740 }
15741
15742 void
15743 ShowThinkingEvent ()
15744 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15745 {
15746     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15747     int newState = appData.showThinking
15748         // [HGM] thinking: other features now need thinking output as well
15749         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15750
15751     if (oldState == newState) return;
15752     oldState = newState;
15753     if (gameMode == EditPosition) EditPositionDone(TRUE);
15754     if (oldState) {
15755         SendToProgram("post\n", &first);
15756         if (gameMode == TwoMachinesPlay) {
15757             SendToProgram("post\n", &second);
15758         }
15759     } else {
15760         SendToProgram("nopost\n", &first);
15761         thinkOutput[0] = NULLCHAR;
15762         if (gameMode == TwoMachinesPlay) {
15763             SendToProgram("nopost\n", &second);
15764         }
15765     }
15766 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15767 }
15768
15769 void
15770 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15771 {
15772   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15773   if (pr == NoProc) return;
15774   AskQuestion(title, question, replyPrefix, pr);
15775 }
15776
15777 void
15778 TypeInEvent (char firstChar)
15779 {
15780     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15781         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15782         gameMode == AnalyzeMode || gameMode == EditGame || 
15783         gameMode == EditPosition || gameMode == IcsExamining ||
15784         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15785         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15786                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15787                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15788         gameMode == Training) PopUpMoveDialog(firstChar);
15789 }
15790
15791 void
15792 TypeInDoneEvent (char *move)
15793 {
15794         Board board;
15795         int n, fromX, fromY, toX, toY;
15796         char promoChar;
15797         ChessMove moveType;
15798
15799         // [HGM] FENedit
15800         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15801                 EditPositionPasteFEN(move);
15802                 return;
15803         }
15804         // [HGM] movenum: allow move number to be typed in any mode
15805         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15806           ToNrEvent(2*n-1);
15807           return;
15808         }
15809         // undocumented kludge: allow command-line option to be typed in!
15810         // (potentially fatal, and does not implement the effect of the option.)
15811         // should only be used for options that are values on which future decisions will be made,
15812         // and definitely not on options that would be used during initialization.
15813         if(strstr(move, "!!! -") == move) {
15814             ParseArgsFromString(move+4);
15815             return;
15816         }
15817
15818       if (gameMode != EditGame && currentMove != forwardMostMove && 
15819         gameMode != Training) {
15820         DisplayMoveError(_("Displayed move is not current"));
15821       } else {
15822         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15823           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15824         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15825         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15826           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15827           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15828         } else {
15829           DisplayMoveError(_("Could not parse move"));
15830         }
15831       }
15832 }
15833
15834 void
15835 DisplayMove (int moveNumber)
15836 {
15837     char message[MSG_SIZ];
15838     char res[MSG_SIZ];
15839     char cpThinkOutput[MSG_SIZ];
15840
15841     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15842
15843     if (moveNumber == forwardMostMove - 1 ||
15844         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15845
15846         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15847
15848         if (strchr(cpThinkOutput, '\n')) {
15849             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15850         }
15851     } else {
15852         *cpThinkOutput = NULLCHAR;
15853     }
15854
15855     /* [AS] Hide thinking from human user */
15856     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15857         *cpThinkOutput = NULLCHAR;
15858         if( thinkOutput[0] != NULLCHAR ) {
15859             int i;
15860
15861             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15862                 cpThinkOutput[i] = '.';
15863             }
15864             cpThinkOutput[i] = NULLCHAR;
15865             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15866         }
15867     }
15868
15869     if (moveNumber == forwardMostMove - 1 &&
15870         gameInfo.resultDetails != NULL) {
15871         if (gameInfo.resultDetails[0] == NULLCHAR) {
15872           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15873         } else {
15874           snprintf(res, MSG_SIZ, " {%s} %s",
15875                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15876         }
15877     } else {
15878         res[0] = NULLCHAR;
15879     }
15880
15881     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15882         DisplayMessage(res, cpThinkOutput);
15883     } else {
15884       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15885                 WhiteOnMove(moveNumber) ? " " : ".. ",
15886                 parseList[moveNumber], res);
15887         DisplayMessage(message, cpThinkOutput);
15888     }
15889 }
15890
15891 void
15892 DisplayComment (int moveNumber, char *text)
15893 {
15894     char title[MSG_SIZ];
15895
15896     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15897       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15898     } else {
15899       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15900               WhiteOnMove(moveNumber) ? " " : ".. ",
15901               parseList[moveNumber]);
15902     }
15903     if (text != NULL && (appData.autoDisplayComment || commentUp))
15904         CommentPopUp(title, text);
15905 }
15906
15907 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15908  * might be busy thinking or pondering.  It can be omitted if your
15909  * gnuchess is configured to stop thinking immediately on any user
15910  * input.  However, that gnuchess feature depends on the FIONREAD
15911  * ioctl, which does not work properly on some flavors of Unix.
15912  */
15913 void
15914 Attention (ChessProgramState *cps)
15915 {
15916 #if ATTENTION
15917     if (!cps->useSigint) return;
15918     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15919     switch (gameMode) {
15920       case MachinePlaysWhite:
15921       case MachinePlaysBlack:
15922       case TwoMachinesPlay:
15923       case IcsPlayingWhite:
15924       case IcsPlayingBlack:
15925       case AnalyzeMode:
15926       case AnalyzeFile:
15927         /* Skip if we know it isn't thinking */
15928         if (!cps->maybeThinking) return;
15929         if (appData.debugMode)
15930           fprintf(debugFP, "Interrupting %s\n", cps->which);
15931         InterruptChildProcess(cps->pr);
15932         cps->maybeThinking = FALSE;
15933         break;
15934       default:
15935         break;
15936     }
15937 #endif /*ATTENTION*/
15938 }
15939
15940 int
15941 CheckFlags ()
15942 {
15943     if (whiteTimeRemaining <= 0) {
15944         if (!whiteFlag) {
15945             whiteFlag = TRUE;
15946             if (appData.icsActive) {
15947                 if (appData.autoCallFlag &&
15948                     gameMode == IcsPlayingBlack && !blackFlag) {
15949                   SendToICS(ics_prefix);
15950                   SendToICS("flag\n");
15951                 }
15952             } else {
15953                 if (blackFlag) {
15954                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15955                 } else {
15956                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15957                     if (appData.autoCallFlag) {
15958                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15959                         return TRUE;
15960                     }
15961                 }
15962             }
15963         }
15964     }
15965     if (blackTimeRemaining <= 0) {
15966         if (!blackFlag) {
15967             blackFlag = TRUE;
15968             if (appData.icsActive) {
15969                 if (appData.autoCallFlag &&
15970                     gameMode == IcsPlayingWhite && !whiteFlag) {
15971                   SendToICS(ics_prefix);
15972                   SendToICS("flag\n");
15973                 }
15974             } else {
15975                 if (whiteFlag) {
15976                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15977                 } else {
15978                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15979                     if (appData.autoCallFlag) {
15980                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15981                         return TRUE;
15982                     }
15983                 }
15984             }
15985         }
15986     }
15987     return FALSE;
15988 }
15989
15990 void
15991 CheckTimeControl ()
15992 {
15993     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15994         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15995
15996     /*
15997      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15998      */
15999     if ( !WhiteOnMove(forwardMostMove) ) {
16000         /* White made time control */
16001         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16002         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16003         /* [HGM] time odds: correct new time quota for time odds! */
16004                                             / WhitePlayer()->timeOdds;
16005         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16006     } else {
16007         lastBlack -= blackTimeRemaining;
16008         /* Black made time control */
16009         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16010                                             / WhitePlayer()->other->timeOdds;
16011         lastWhite = whiteTimeRemaining;
16012     }
16013 }
16014
16015 void
16016 DisplayBothClocks ()
16017 {
16018     int wom = gameMode == EditPosition ?
16019       !blackPlaysFirst : WhiteOnMove(currentMove);
16020     DisplayWhiteClock(whiteTimeRemaining, wom);
16021     DisplayBlackClock(blackTimeRemaining, !wom);
16022 }
16023
16024
16025 /* Timekeeping seems to be a portability nightmare.  I think everyone
16026    has ftime(), but I'm really not sure, so I'm including some ifdefs
16027    to use other calls if you don't.  Clocks will be less accurate if
16028    you have neither ftime nor gettimeofday.
16029 */
16030
16031 /* VS 2008 requires the #include outside of the function */
16032 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16033 #include <sys/timeb.h>
16034 #endif
16035
16036 /* Get the current time as a TimeMark */
16037 void
16038 GetTimeMark (TimeMark *tm)
16039 {
16040 #if HAVE_GETTIMEOFDAY
16041
16042     struct timeval timeVal;
16043     struct timezone timeZone;
16044
16045     gettimeofday(&timeVal, &timeZone);
16046     tm->sec = (long) timeVal.tv_sec;
16047     tm->ms = (int) (timeVal.tv_usec / 1000L);
16048
16049 #else /*!HAVE_GETTIMEOFDAY*/
16050 #if HAVE_FTIME
16051
16052 // include <sys/timeb.h> / moved to just above start of function
16053     struct timeb timeB;
16054
16055     ftime(&timeB);
16056     tm->sec = (long) timeB.time;
16057     tm->ms = (int) timeB.millitm;
16058
16059 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16060     tm->sec = (long) time(NULL);
16061     tm->ms = 0;
16062 #endif
16063 #endif
16064 }
16065
16066 /* Return the difference in milliseconds between two
16067    time marks.  We assume the difference will fit in a long!
16068 */
16069 long
16070 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16071 {
16072     return 1000L*(tm2->sec - tm1->sec) +
16073            (long) (tm2->ms - tm1->ms);
16074 }
16075
16076
16077 /*
16078  * Code to manage the game clocks.
16079  *
16080  * In tournament play, black starts the clock and then white makes a move.
16081  * We give the human user a slight advantage if he is playing white---the
16082  * clocks don't run until he makes his first move, so it takes zero time.
16083  * Also, we don't account for network lag, so we could get out of sync
16084  * with GNU Chess's clock -- but then, referees are always right.
16085  */
16086
16087 static TimeMark tickStartTM;
16088 static long intendedTickLength;
16089
16090 long
16091 NextTickLength (long timeRemaining)
16092 {
16093     long nominalTickLength, nextTickLength;
16094
16095     if (timeRemaining > 0L && timeRemaining <= 10000L)
16096       nominalTickLength = 100L;
16097     else
16098       nominalTickLength = 1000L;
16099     nextTickLength = timeRemaining % nominalTickLength;
16100     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16101
16102     return nextTickLength;
16103 }
16104
16105 /* Adjust clock one minute up or down */
16106 void
16107 AdjustClock (Boolean which, int dir)
16108 {
16109     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16110     if(which) blackTimeRemaining += 60000*dir;
16111     else      whiteTimeRemaining += 60000*dir;
16112     DisplayBothClocks();
16113     adjustedClock = TRUE;
16114 }
16115
16116 /* Stop clocks and reset to a fresh time control */
16117 void
16118 ResetClocks ()
16119 {
16120     (void) StopClockTimer();
16121     if (appData.icsActive) {
16122         whiteTimeRemaining = blackTimeRemaining = 0;
16123     } else if (searchTime) {
16124         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16125         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16126     } else { /* [HGM] correct new time quote for time odds */
16127         whiteTC = blackTC = fullTimeControlString;
16128         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16129         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16130     }
16131     if (whiteFlag || blackFlag) {
16132         DisplayTitle("");
16133         whiteFlag = blackFlag = FALSE;
16134     }
16135     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16136     DisplayBothClocks();
16137     adjustedClock = FALSE;
16138 }
16139
16140 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16141
16142 /* Decrement running clock by amount of time that has passed */
16143 void
16144 DecrementClocks ()
16145 {
16146     long timeRemaining;
16147     long lastTickLength, fudge;
16148     TimeMark now;
16149
16150     if (!appData.clockMode) return;
16151     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16152
16153     GetTimeMark(&now);
16154
16155     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16156
16157     /* Fudge if we woke up a little too soon */
16158     fudge = intendedTickLength - lastTickLength;
16159     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16160
16161     if (WhiteOnMove(forwardMostMove)) {
16162         if(whiteNPS >= 0) lastTickLength = 0;
16163         timeRemaining = whiteTimeRemaining -= lastTickLength;
16164         if(timeRemaining < 0 && !appData.icsActive) {
16165             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16166             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16167                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16168                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16169             }
16170         }
16171         DisplayWhiteClock(whiteTimeRemaining - fudge,
16172                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16173     } else {
16174         if(blackNPS >= 0) lastTickLength = 0;
16175         timeRemaining = blackTimeRemaining -= lastTickLength;
16176         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16177             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16178             if(suddenDeath) {
16179                 blackStartMove = forwardMostMove;
16180                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16181             }
16182         }
16183         DisplayBlackClock(blackTimeRemaining - fudge,
16184                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16185     }
16186     if (CheckFlags()) return;
16187
16188     tickStartTM = now;
16189     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16190     StartClockTimer(intendedTickLength);
16191
16192     /* if the time remaining has fallen below the alarm threshold, sound the
16193      * alarm. if the alarm has sounded and (due to a takeback or time control
16194      * with increment) the time remaining has increased to a level above the
16195      * threshold, reset the alarm so it can sound again.
16196      */
16197
16198     if (appData.icsActive && appData.icsAlarm) {
16199
16200         /* make sure we are dealing with the user's clock */
16201         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16202                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16203            )) return;
16204
16205         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16206             alarmSounded = FALSE;
16207         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16208             PlayAlarmSound();
16209             alarmSounded = TRUE;
16210         }
16211     }
16212 }
16213
16214
16215 /* A player has just moved, so stop the previously running
16216    clock and (if in clock mode) start the other one.
16217    We redisplay both clocks in case we're in ICS mode, because
16218    ICS gives us an update to both clocks after every move.
16219    Note that this routine is called *after* forwardMostMove
16220    is updated, so the last fractional tick must be subtracted
16221    from the color that is *not* on move now.
16222 */
16223 void
16224 SwitchClocks (int newMoveNr)
16225 {
16226     long lastTickLength;
16227     TimeMark now;
16228     int flagged = FALSE;
16229
16230     GetTimeMark(&now);
16231
16232     if (StopClockTimer() && appData.clockMode) {
16233         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16234         if (!WhiteOnMove(forwardMostMove)) {
16235             if(blackNPS >= 0) lastTickLength = 0;
16236             blackTimeRemaining -= lastTickLength;
16237            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16238 //         if(pvInfoList[forwardMostMove].time == -1)
16239                  pvInfoList[forwardMostMove].time =               // use GUI time
16240                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16241         } else {
16242            if(whiteNPS >= 0) lastTickLength = 0;
16243            whiteTimeRemaining -= lastTickLength;
16244            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16245 //         if(pvInfoList[forwardMostMove].time == -1)
16246                  pvInfoList[forwardMostMove].time =
16247                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16248         }
16249         flagged = CheckFlags();
16250     }
16251     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16252     CheckTimeControl();
16253
16254     if (flagged || !appData.clockMode) return;
16255
16256     switch (gameMode) {
16257       case MachinePlaysBlack:
16258       case MachinePlaysWhite:
16259       case BeginningOfGame:
16260         if (pausing) return;
16261         break;
16262
16263       case EditGame:
16264       case PlayFromGameFile:
16265       case IcsExamining:
16266         return;
16267
16268       default:
16269         break;
16270     }
16271
16272     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16273         if(WhiteOnMove(forwardMostMove))
16274              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16275         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16276     }
16277
16278     tickStartTM = now;
16279     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16280       whiteTimeRemaining : blackTimeRemaining);
16281     StartClockTimer(intendedTickLength);
16282 }
16283
16284
16285 /* Stop both clocks */
16286 void
16287 StopClocks ()
16288 {
16289     long lastTickLength;
16290     TimeMark now;
16291
16292     if (!StopClockTimer()) return;
16293     if (!appData.clockMode) return;
16294
16295     GetTimeMark(&now);
16296
16297     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16298     if (WhiteOnMove(forwardMostMove)) {
16299         if(whiteNPS >= 0) lastTickLength = 0;
16300         whiteTimeRemaining -= lastTickLength;
16301         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16302     } else {
16303         if(blackNPS >= 0) lastTickLength = 0;
16304         blackTimeRemaining -= lastTickLength;
16305         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16306     }
16307     CheckFlags();
16308 }
16309
16310 /* Start clock of player on move.  Time may have been reset, so
16311    if clock is already running, stop and restart it. */
16312 void
16313 StartClocks ()
16314 {
16315     (void) StopClockTimer(); /* in case it was running already */
16316     DisplayBothClocks();
16317     if (CheckFlags()) return;
16318
16319     if (!appData.clockMode) return;
16320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16321
16322     GetTimeMark(&tickStartTM);
16323     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16324       whiteTimeRemaining : blackTimeRemaining);
16325
16326    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16327     whiteNPS = blackNPS = -1;
16328     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16329        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16330         whiteNPS = first.nps;
16331     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16332        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16333         blackNPS = first.nps;
16334     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16335         whiteNPS = second.nps;
16336     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16337         blackNPS = second.nps;
16338     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16339
16340     StartClockTimer(intendedTickLength);
16341 }
16342
16343 char *
16344 TimeString (long ms)
16345 {
16346     long second, minute, hour, day;
16347     char *sign = "";
16348     static char buf[32];
16349
16350     if (ms > 0 && ms <= 9900) {
16351       /* convert milliseconds to tenths, rounding up */
16352       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16353
16354       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16355       return buf;
16356     }
16357
16358     /* convert milliseconds to seconds, rounding up */
16359     /* use floating point to avoid strangeness of integer division
16360        with negative dividends on many machines */
16361     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16362
16363     if (second < 0) {
16364         sign = "-";
16365         second = -second;
16366     }
16367
16368     day = second / (60 * 60 * 24);
16369     second = second % (60 * 60 * 24);
16370     hour = second / (60 * 60);
16371     second = second % (60 * 60);
16372     minute = second / 60;
16373     second = second % 60;
16374
16375     if (day > 0)
16376       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16377               sign, day, hour, minute, second);
16378     else if (hour > 0)
16379       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16380     else
16381       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16382
16383     return buf;
16384 }
16385
16386
16387 /*
16388  * This is necessary because some C libraries aren't ANSI C compliant yet.
16389  */
16390 char *
16391 StrStr (char *string, char *match)
16392 {
16393     int i, length;
16394
16395     length = strlen(match);
16396
16397     for (i = strlen(string) - length; i >= 0; i--, string++)
16398       if (!strncmp(match, string, length))
16399         return string;
16400
16401     return NULL;
16402 }
16403
16404 char *
16405 StrCaseStr (char *string, char *match)
16406 {
16407     int i, j, length;
16408
16409     length = strlen(match);
16410
16411     for (i = strlen(string) - length; i >= 0; i--, string++) {
16412         for (j = 0; j < length; j++) {
16413             if (ToLower(match[j]) != ToLower(string[j]))
16414               break;
16415         }
16416         if (j == length) return string;
16417     }
16418
16419     return NULL;
16420 }
16421
16422 #ifndef _amigados
16423 int
16424 StrCaseCmp (char *s1, char *s2)
16425 {
16426     char c1, c2;
16427
16428     for (;;) {
16429         c1 = ToLower(*s1++);
16430         c2 = ToLower(*s2++);
16431         if (c1 > c2) return 1;
16432         if (c1 < c2) return -1;
16433         if (c1 == NULLCHAR) return 0;
16434     }
16435 }
16436
16437
16438 int
16439 ToLower (int c)
16440 {
16441     return isupper(c) ? tolower(c) : c;
16442 }
16443
16444
16445 int
16446 ToUpper (int c)
16447 {
16448     return islower(c) ? toupper(c) : c;
16449 }
16450 #endif /* !_amigados    */
16451
16452 char *
16453 StrSave (char *s)
16454 {
16455   char *ret;
16456
16457   if ((ret = (char *) malloc(strlen(s) + 1)))
16458     {
16459       safeStrCpy(ret, s, strlen(s)+1);
16460     }
16461   return ret;
16462 }
16463
16464 char *
16465 StrSavePtr (char *s, char **savePtr)
16466 {
16467     if (*savePtr) {
16468         free(*savePtr);
16469     }
16470     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16471       safeStrCpy(*savePtr, s, strlen(s)+1);
16472     }
16473     return(*savePtr);
16474 }
16475
16476 char *
16477 PGNDate ()
16478 {
16479     time_t clock;
16480     struct tm *tm;
16481     char buf[MSG_SIZ];
16482
16483     clock = time((time_t *)NULL);
16484     tm = localtime(&clock);
16485     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16486             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16487     return StrSave(buf);
16488 }
16489
16490
16491 char *
16492 PositionToFEN (int move, char *overrideCastling)
16493 {
16494     int i, j, fromX, fromY, toX, toY;
16495     int whiteToPlay;
16496     char buf[MSG_SIZ];
16497     char *p, *q;
16498     int emptycount;
16499     ChessSquare piece;
16500
16501     whiteToPlay = (gameMode == EditPosition) ?
16502       !blackPlaysFirst : (move % 2 == 0);
16503     p = buf;
16504
16505     /* Piece placement data */
16506     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16507         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16508         emptycount = 0;
16509         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16510             if (boards[move][i][j] == EmptySquare) {
16511                 emptycount++;
16512             } else { ChessSquare piece = boards[move][i][j];
16513                 if (emptycount > 0) {
16514                     if(emptycount<10) /* [HGM] can be >= 10 */
16515                         *p++ = '0' + emptycount;
16516                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16517                     emptycount = 0;
16518                 }
16519                 if(PieceToChar(piece) == '+') {
16520                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16521                     *p++ = '+';
16522                     piece = (ChessSquare)(DEMOTED piece);
16523                 }
16524                 *p++ = PieceToChar(piece);
16525                 if(p[-1] == '~') {
16526                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16527                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16528                     *p++ = '~';
16529                 }
16530             }
16531         }
16532         if (emptycount > 0) {
16533             if(emptycount<10) /* [HGM] can be >= 10 */
16534                 *p++ = '0' + emptycount;
16535             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16536             emptycount = 0;
16537         }
16538         *p++ = '/';
16539     }
16540     *(p - 1) = ' ';
16541
16542     /* [HGM] print Crazyhouse or Shogi holdings */
16543     if( gameInfo.holdingsWidth ) {
16544         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16545         q = p;
16546         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16547             piece = boards[move][i][BOARD_WIDTH-1];
16548             if( piece != EmptySquare )
16549               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16550                   *p++ = PieceToChar(piece);
16551         }
16552         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16553             piece = boards[move][BOARD_HEIGHT-i-1][0];
16554             if( piece != EmptySquare )
16555               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16556                   *p++ = PieceToChar(piece);
16557         }
16558
16559         if( q == p ) *p++ = '-';
16560         *p++ = ']';
16561         *p++ = ' ';
16562     }
16563
16564     /* Active color */
16565     *p++ = whiteToPlay ? 'w' : 'b';
16566     *p++ = ' ';
16567
16568   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16569     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16570   } else {
16571   if(nrCastlingRights) {
16572      q = p;
16573      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16574        /* [HGM] write directly from rights */
16575            if(boards[move][CASTLING][2] != NoRights &&
16576               boards[move][CASTLING][0] != NoRights   )
16577                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16578            if(boards[move][CASTLING][2] != NoRights &&
16579               boards[move][CASTLING][1] != NoRights   )
16580                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16581            if(boards[move][CASTLING][5] != NoRights &&
16582               boards[move][CASTLING][3] != NoRights   )
16583                 *p++ = boards[move][CASTLING][3] + AAA;
16584            if(boards[move][CASTLING][5] != NoRights &&
16585               boards[move][CASTLING][4] != NoRights   )
16586                 *p++ = boards[move][CASTLING][4] + AAA;
16587      } else {
16588
16589         /* [HGM] write true castling rights */
16590         if( nrCastlingRights == 6 ) {
16591             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16592                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16593             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16594                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16595             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16596                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16597             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16598                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16599         }
16600      }
16601      if (q == p) *p++ = '-'; /* No castling rights */
16602      *p++ = ' ';
16603   }
16604
16605   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16606      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16607     /* En passant target square */
16608     if (move > backwardMostMove) {
16609         fromX = moveList[move - 1][0] - AAA;
16610         fromY = moveList[move - 1][1] - ONE;
16611         toX = moveList[move - 1][2] - AAA;
16612         toY = moveList[move - 1][3] - ONE;
16613         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16614             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16615             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16616             fromX == toX) {
16617             /* 2-square pawn move just happened */
16618             *p++ = toX + AAA;
16619             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16620         } else {
16621             *p++ = '-';
16622         }
16623     } else if(move == backwardMostMove) {
16624         // [HGM] perhaps we should always do it like this, and forget the above?
16625         if((signed char)boards[move][EP_STATUS] >= 0) {
16626             *p++ = boards[move][EP_STATUS] + AAA;
16627             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16628         } else {
16629             *p++ = '-';
16630         }
16631     } else {
16632         *p++ = '-';
16633     }
16634     *p++ = ' ';
16635   }
16636   }
16637
16638     /* [HGM] find reversible plies */
16639     {   int i = 0, j=move;
16640
16641         if (appData.debugMode) { int k;
16642             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16643             for(k=backwardMostMove; k<=forwardMostMove; k++)
16644                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16645
16646         }
16647
16648         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16649         if( j == backwardMostMove ) i += initialRulePlies;
16650         sprintf(p, "%d ", i);
16651         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16652     }
16653     /* Fullmove number */
16654     sprintf(p, "%d", (move / 2) + 1);
16655
16656     return StrSave(buf);
16657 }
16658
16659 Boolean
16660 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16661 {
16662     int i, j;
16663     char *p, c;
16664     int emptycount;
16665     ChessSquare piece;
16666
16667     p = fen;
16668
16669     /* [HGM] by default clear Crazyhouse holdings, if present */
16670     if(gameInfo.holdingsWidth) {
16671        for(i=0; i<BOARD_HEIGHT; i++) {
16672            board[i][0]             = EmptySquare; /* black holdings */
16673            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16674            board[i][1]             = (ChessSquare) 0; /* black counts */
16675            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16676        }
16677     }
16678
16679     /* Piece placement data */
16680     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16681         j = 0;
16682         for (;;) {
16683             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16684                 if (*p == '/') p++;
16685                 emptycount = gameInfo.boardWidth - j;
16686                 while (emptycount--)
16687                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16688                 break;
16689 #if(BOARD_FILES >= 10)
16690             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16691                 p++; emptycount=10;
16692                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16693                 while (emptycount--)
16694                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16695 #endif
16696             } else if (isdigit(*p)) {
16697                 emptycount = *p++ - '0';
16698                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16699                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16700                 while (emptycount--)
16701                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16702             } else if (*p == '+' || isalpha(*p)) {
16703                 if (j >= gameInfo.boardWidth) return FALSE;
16704                 if(*p=='+') {
16705                     piece = CharToPiece(*++p);
16706                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16707                     piece = (ChessSquare) (PROMOTED piece ); p++;
16708                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16709                 } else piece = CharToPiece(*p++);
16710
16711                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16712                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16713                     piece = (ChessSquare) (PROMOTED piece);
16714                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16715                     p++;
16716                 }
16717                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16718             } else {
16719                 return FALSE;
16720             }
16721         }
16722     }
16723     while (*p == '/' || *p == ' ') p++;
16724
16725     /* [HGM] look for Crazyhouse holdings here */
16726     while(*p==' ') p++;
16727     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16728         if(*p == '[') p++;
16729         if(*p == '-' ) p++; /* empty holdings */ else {
16730             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16731             /* if we would allow FEN reading to set board size, we would   */
16732             /* have to add holdings and shift the board read so far here   */
16733             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16734                 p++;
16735                 if((int) piece >= (int) BlackPawn ) {
16736                     i = (int)piece - (int)BlackPawn;
16737                     i = PieceToNumber((ChessSquare)i);
16738                     if( i >= gameInfo.holdingsSize ) return FALSE;
16739                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16740                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16741                 } else {
16742                     i = (int)piece - (int)WhitePawn;
16743                     i = PieceToNumber((ChessSquare)i);
16744                     if( i >= gameInfo.holdingsSize ) return FALSE;
16745                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16746                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16747                 }
16748             }
16749         }
16750         if(*p == ']') p++;
16751     }
16752
16753     while(*p == ' ') p++;
16754
16755     /* Active color */
16756     c = *p++;
16757     if(appData.colorNickNames) {
16758       if( c == appData.colorNickNames[0] ) c = 'w'; else
16759       if( c == appData.colorNickNames[1] ) c = 'b';
16760     }
16761     switch (c) {
16762       case 'w':
16763         *blackPlaysFirst = FALSE;
16764         break;
16765       case 'b':
16766         *blackPlaysFirst = TRUE;
16767         break;
16768       default:
16769         return FALSE;
16770     }
16771
16772     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16773     /* return the extra info in global variiables             */
16774
16775     /* set defaults in case FEN is incomplete */
16776     board[EP_STATUS] = EP_UNKNOWN;
16777     for(i=0; i<nrCastlingRights; i++ ) {
16778         board[CASTLING][i] =
16779             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16780     }   /* assume possible unless obviously impossible */
16781     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16782     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16783     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16784                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16785     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16786     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16787     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16788                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16789     FENrulePlies = 0;
16790
16791     while(*p==' ') p++;
16792     if(nrCastlingRights) {
16793       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16794           /* castling indicator present, so default becomes no castlings */
16795           for(i=0; i<nrCastlingRights; i++ ) {
16796                  board[CASTLING][i] = NoRights;
16797           }
16798       }
16799       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16800              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16801              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16802              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16803         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16804
16805         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16806             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16807             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16808         }
16809         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16810             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16811         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16812                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16813         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16814                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16815         switch(c) {
16816           case'K':
16817               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16818               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16819               board[CASTLING][2] = whiteKingFile;
16820               break;
16821           case'Q':
16822               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16823               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16824               board[CASTLING][2] = whiteKingFile;
16825               break;
16826           case'k':
16827               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16828               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16829               board[CASTLING][5] = blackKingFile;
16830               break;
16831           case'q':
16832               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16833               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16834               board[CASTLING][5] = blackKingFile;
16835           case '-':
16836               break;
16837           default: /* FRC castlings */
16838               if(c >= 'a') { /* black rights */
16839                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16840                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16841                   if(i == BOARD_RGHT) break;
16842                   board[CASTLING][5] = i;
16843                   c -= AAA;
16844                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16845                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16846                   if(c > i)
16847                       board[CASTLING][3] = c;
16848                   else
16849                       board[CASTLING][4] = c;
16850               } else { /* white rights */
16851                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16852                     if(board[0][i] == WhiteKing) break;
16853                   if(i == BOARD_RGHT) break;
16854                   board[CASTLING][2] = i;
16855                   c -= AAA - 'a' + 'A';
16856                   if(board[0][c] >= WhiteKing) break;
16857                   if(c > i)
16858                       board[CASTLING][0] = c;
16859                   else
16860                       board[CASTLING][1] = c;
16861               }
16862         }
16863       }
16864       for(i=0; i<nrCastlingRights; i++)
16865         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16866     if (appData.debugMode) {
16867         fprintf(debugFP, "FEN castling rights:");
16868         for(i=0; i<nrCastlingRights; i++)
16869         fprintf(debugFP, " %d", board[CASTLING][i]);
16870         fprintf(debugFP, "\n");
16871     }
16872
16873       while(*p==' ') p++;
16874     }
16875
16876     /* read e.p. field in games that know e.p. capture */
16877     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16878        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16879       if(*p=='-') {
16880         p++; board[EP_STATUS] = EP_NONE;
16881       } else {
16882          char c = *p++ - AAA;
16883
16884          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16885          if(*p >= '0' && *p <='9') p++;
16886          board[EP_STATUS] = c;
16887       }
16888     }
16889
16890
16891     if(sscanf(p, "%d", &i) == 1) {
16892         FENrulePlies = i; /* 50-move ply counter */
16893         /* (The move number is still ignored)    */
16894     }
16895
16896     return TRUE;
16897 }
16898
16899 void
16900 EditPositionPasteFEN (char *fen)
16901 {
16902   if (fen != NULL) {
16903     Board initial_position;
16904
16905     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16906       DisplayError(_("Bad FEN position in clipboard"), 0);
16907       return ;
16908     } else {
16909       int savedBlackPlaysFirst = blackPlaysFirst;
16910       EditPositionEvent();
16911       blackPlaysFirst = savedBlackPlaysFirst;
16912       CopyBoard(boards[0], initial_position);
16913       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16914       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16915       DisplayBothClocks();
16916       DrawPosition(FALSE, boards[currentMove]);
16917     }
16918   }
16919 }
16920
16921 static char cseq[12] = "\\   ";
16922
16923 Boolean
16924 set_cont_sequence (char *new_seq)
16925 {
16926     int len;
16927     Boolean ret;
16928
16929     // handle bad attempts to set the sequence
16930         if (!new_seq)
16931                 return 0; // acceptable error - no debug
16932
16933     len = strlen(new_seq);
16934     ret = (len > 0) && (len < sizeof(cseq));
16935     if (ret)
16936       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16937     else if (appData.debugMode)
16938       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16939     return ret;
16940 }
16941
16942 /*
16943     reformat a source message so words don't cross the width boundary.  internal
16944     newlines are not removed.  returns the wrapped size (no null character unless
16945     included in source message).  If dest is NULL, only calculate the size required
16946     for the dest buffer.  lp argument indicats line position upon entry, and it's
16947     passed back upon exit.
16948 */
16949 int
16950 wrap (char *dest, char *src, int count, int width, int *lp)
16951 {
16952     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16953
16954     cseq_len = strlen(cseq);
16955     old_line = line = *lp;
16956     ansi = len = clen = 0;
16957
16958     for (i=0; i < count; i++)
16959     {
16960         if (src[i] == '\033')
16961             ansi = 1;
16962
16963         // if we hit the width, back up
16964         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16965         {
16966             // store i & len in case the word is too long
16967             old_i = i, old_len = len;
16968
16969             // find the end of the last word
16970             while (i && src[i] != ' ' && src[i] != '\n')
16971             {
16972                 i--;
16973                 len--;
16974             }
16975
16976             // word too long?  restore i & len before splitting it
16977             if ((old_i-i+clen) >= width)
16978             {
16979                 i = old_i;
16980                 len = old_len;
16981             }
16982
16983             // extra space?
16984             if (i && src[i-1] == ' ')
16985                 len--;
16986
16987             if (src[i] != ' ' && src[i] != '\n')
16988             {
16989                 i--;
16990                 if (len)
16991                     len--;
16992             }
16993
16994             // now append the newline and continuation sequence
16995             if (dest)
16996                 dest[len] = '\n';
16997             len++;
16998             if (dest)
16999                 strncpy(dest+len, cseq, cseq_len);
17000             len += cseq_len;
17001             line = cseq_len;
17002             clen = cseq_len;
17003             continue;
17004         }
17005
17006         if (dest)
17007             dest[len] = src[i];
17008         len++;
17009         if (!ansi)
17010             line++;
17011         if (src[i] == '\n')
17012             line = 0;
17013         if (src[i] == 'm')
17014             ansi = 0;
17015     }
17016     if (dest && appData.debugMode)
17017     {
17018         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17019             count, width, line, len, *lp);
17020         show_bytes(debugFP, src, count);
17021         fprintf(debugFP, "\ndest: ");
17022         show_bytes(debugFP, dest, len);
17023         fprintf(debugFP, "\n");
17024     }
17025     *lp = dest ? line : old_line;
17026
17027     return len;
17028 }
17029
17030 // [HGM] vari: routines for shelving variations
17031 Boolean modeRestore = FALSE;
17032
17033 void
17034 PushInner (int firstMove, int lastMove)
17035 {
17036         int i, j, nrMoves = lastMove - firstMove;
17037
17038         // push current tail of game on stack
17039         savedResult[storedGames] = gameInfo.result;
17040         savedDetails[storedGames] = gameInfo.resultDetails;
17041         gameInfo.resultDetails = NULL;
17042         savedFirst[storedGames] = firstMove;
17043         savedLast [storedGames] = lastMove;
17044         savedFramePtr[storedGames] = framePtr;
17045         framePtr -= nrMoves; // reserve space for the boards
17046         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17047             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17048             for(j=0; j<MOVE_LEN; j++)
17049                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17050             for(j=0; j<2*MOVE_LEN; j++)
17051                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17052             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17053             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17054             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17055             pvInfoList[firstMove+i-1].depth = 0;
17056             commentList[framePtr+i] = commentList[firstMove+i];
17057             commentList[firstMove+i] = NULL;
17058         }
17059
17060         storedGames++;
17061         forwardMostMove = firstMove; // truncate game so we can start variation
17062 }
17063
17064 void
17065 PushTail (int firstMove, int lastMove)
17066 {
17067         if(appData.icsActive) { // only in local mode
17068                 forwardMostMove = currentMove; // mimic old ICS behavior
17069                 return;
17070         }
17071         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17072
17073         PushInner(firstMove, lastMove);
17074         if(storedGames == 1) GreyRevert(FALSE);
17075         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17076 }
17077
17078 void
17079 PopInner (Boolean annotate)
17080 {
17081         int i, j, nrMoves;
17082         char buf[8000], moveBuf[20];
17083
17084         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17085         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17086         nrMoves = savedLast[storedGames] - currentMove;
17087         if(annotate) {
17088                 int cnt = 10;
17089                 if(!WhiteOnMove(currentMove))
17090                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17091                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17092                 for(i=currentMove; i<forwardMostMove; i++) {
17093                         if(WhiteOnMove(i))
17094                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17095                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17096                         strcat(buf, moveBuf);
17097                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17098                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17099                 }
17100                 strcat(buf, ")");
17101         }
17102         for(i=1; i<=nrMoves; i++) { // copy last variation back
17103             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17104             for(j=0; j<MOVE_LEN; j++)
17105                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17106             for(j=0; j<2*MOVE_LEN; j++)
17107                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17108             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17109             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17110             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17111             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17112             commentList[currentMove+i] = commentList[framePtr+i];
17113             commentList[framePtr+i] = NULL;
17114         }
17115         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17116         framePtr = savedFramePtr[storedGames];
17117         gameInfo.result = savedResult[storedGames];
17118         if(gameInfo.resultDetails != NULL) {
17119             free(gameInfo.resultDetails);
17120       }
17121         gameInfo.resultDetails = savedDetails[storedGames];
17122         forwardMostMove = currentMove + nrMoves;
17123 }
17124
17125 Boolean
17126 PopTail (Boolean annotate)
17127 {
17128         if(appData.icsActive) return FALSE; // only in local mode
17129         if(!storedGames) return FALSE; // sanity
17130         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17131
17132         PopInner(annotate);
17133         if(currentMove < forwardMostMove) ForwardEvent(); else
17134         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17135
17136         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17137         return TRUE;
17138 }
17139
17140 void
17141 CleanupTail ()
17142 {       // remove all shelved variations
17143         int i;
17144         for(i=0; i<storedGames; i++) {
17145             if(savedDetails[i])
17146                 free(savedDetails[i]);
17147             savedDetails[i] = NULL;
17148         }
17149         for(i=framePtr; i<MAX_MOVES; i++) {
17150                 if(commentList[i]) free(commentList[i]);
17151                 commentList[i] = NULL;
17152         }
17153         framePtr = MAX_MOVES-1;
17154         storedGames = 0;
17155 }
17156
17157 void
17158 LoadVariation (int index, char *text)
17159 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17160         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17161         int level = 0, move;
17162
17163         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17164         // first find outermost bracketing variation
17165         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17166             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17167                 if(*p == '{') wait = '}'; else
17168                 if(*p == '[') wait = ']'; else
17169                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17170                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17171             }
17172             if(*p == wait) wait = NULLCHAR; // closing ]} found
17173             p++;
17174         }
17175         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17176         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17177         end[1] = NULLCHAR; // clip off comment beyond variation
17178         ToNrEvent(currentMove-1);
17179         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17180         // kludge: use ParsePV() to append variation to game
17181         move = currentMove;
17182         ParsePV(start, TRUE, TRUE);
17183         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17184         ClearPremoveHighlights();
17185         CommentPopDown();
17186         ToNrEvent(currentMove+1);
17187 }
17188