Switch to using listboxes for engine-selection in WinBoard
[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 char *wbOptions;
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir);
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
959         if(q)   free(q);
960         FloatToFront(&appData.recentEngineList, buf);
961     }
962     ReplaceEngine(cps, i);
963 }
964
965 void
966 InitTimeControls ()
967 {
968     int matched, min, sec;
969     /*
970      * Parse timeControl resource
971      */
972     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
973                           appData.movesPerSession)) {
974         char buf[MSG_SIZ];
975         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
976         DisplayFatalError(buf, 0, 2);
977     }
978
979     /*
980      * Parse searchTime resource
981      */
982     if (*appData.searchTime != NULLCHAR) {
983         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
984         if (matched == 1) {
985             searchTime = min * 60;
986         } else if (matched == 2) {
987             searchTime = min * 60 + sec;
988         } else {
989             char buf[MSG_SIZ];
990             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
991             DisplayFatalError(buf, 0, 2);
992         }
993     }
994 }
995
996 void
997 InitBackEnd1 ()
998 {
999
1000     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1001     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1002
1003     GetTimeMark(&programStartTime);
1004     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1005     appData.seedBase = random() + (random()<<15);
1006     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1007
1008     ClearProgramStats();
1009     programStats.ok_to_send = 1;
1010     programStats.seen_stat = 0;
1011
1012     /*
1013      * Initialize game list
1014      */
1015     ListNew(&gameList);
1016
1017
1018     /*
1019      * Internet chess server status
1020      */
1021     if (appData.icsActive) {
1022         appData.matchMode = FALSE;
1023         appData.matchGames = 0;
1024 #if ZIPPY
1025         appData.noChessProgram = !appData.zippyPlay;
1026 #else
1027         appData.zippyPlay = FALSE;
1028         appData.zippyTalk = FALSE;
1029         appData.noChessProgram = TRUE;
1030 #endif
1031         if (*appData.icsHelper != NULLCHAR) {
1032             appData.useTelnet = TRUE;
1033             appData.telnetProgram = appData.icsHelper;
1034         }
1035     } else {
1036         appData.zippyTalk = appData.zippyPlay = FALSE;
1037     }
1038
1039     /* [AS] Initialize pv info list [HGM] and game state */
1040     {
1041         int i, j;
1042
1043         for( i=0; i<=framePtr; i++ ) {
1044             pvInfoList[i].depth = -1;
1045             boards[i][EP_STATUS] = EP_NONE;
1046             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1047         }
1048     }
1049
1050     InitTimeControls();
1051
1052     /* [AS] Adjudication threshold */
1053     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1054
1055     InitEngine(&first, 0);
1056     InitEngine(&second, 1);
1057     CommonEngineInit();
1058
1059     pairing.which = "pairing"; // pairing engine
1060     pairing.pr = NoProc;
1061     pairing.isr = NULL;
1062     pairing.program = appData.pairingEngine;
1063     pairing.host = "localhost";
1064     pairing.dir = ".";
1065
1066     if (appData.icsActive) {
1067         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1068     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1069         appData.clockMode = FALSE;
1070         first.sendTime = second.sendTime = 0;
1071     }
1072
1073 #if ZIPPY
1074     /* Override some settings from environment variables, for backward
1075        compatibility.  Unfortunately it's not feasible to have the env
1076        vars just set defaults, at least in xboard.  Ugh.
1077     */
1078     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1079       ZippyInit();
1080     }
1081 #endif
1082
1083     if (!appData.icsActive) {
1084       char buf[MSG_SIZ];
1085       int len;
1086
1087       /* Check for variants that are supported only in ICS mode,
1088          or not at all.  Some that are accepted here nevertheless
1089          have bugs; see comments below.
1090       */
1091       VariantClass variant = StringToVariant(appData.variant);
1092       switch (variant) {
1093       case VariantBughouse:     /* need four players and two boards */
1094       case VariantKriegspiel:   /* need to hide pieces and move details */
1095         /* case VariantFischeRandom: (Fabien: moved below) */
1096         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1097         if( (len >= MSG_SIZ) && appData.debugMode )
1098           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1099
1100         DisplayFatalError(buf, 0, 2);
1101         return;
1102
1103       case VariantUnknown:
1104       case VariantLoadable:
1105       case Variant29:
1106       case Variant30:
1107       case Variant31:
1108       case Variant32:
1109       case Variant33:
1110       case Variant34:
1111       case Variant35:
1112       case Variant36:
1113       default:
1114         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1115         if( (len >= MSG_SIZ) && appData.debugMode )
1116           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1117
1118         DisplayFatalError(buf, 0, 2);
1119         return;
1120
1121       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1122       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1123       case VariantGothic:     /* [HGM] should work */
1124       case VariantCapablanca: /* [HGM] should work */
1125       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1126       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1127       case VariantKnightmate: /* [HGM] should work */
1128       case VariantCylinder:   /* [HGM] untested */
1129       case VariantFalcon:     /* [HGM] untested */
1130       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1131                                  offboard interposition not understood */
1132       case VariantNormal:     /* definitely works! */
1133       case VariantWildCastle: /* pieces not automatically shuffled */
1134       case VariantNoCastle:   /* pieces not automatically shuffled */
1135       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1136       case VariantLosers:     /* should work except for win condition,
1137                                  and doesn't know captures are mandatory */
1138       case VariantSuicide:    /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantGiveaway:   /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantTwoKings:   /* should work */
1143       case VariantAtomic:     /* should work except for win condition */
1144       case Variant3Check:     /* should work except for win condition */
1145       case VariantShatranj:   /* should work except for all win conditions */
1146       case VariantMakruk:     /* should work except for draw countdown */
1147       case VariantBerolina:   /* might work if TestLegality is off */
1148       case VariantCapaRandom: /* should work */
1149       case VariantJanus:      /* should work */
1150       case VariantSuper:      /* experimental */
1151       case VariantGreat:      /* experimental, requires legality testing to be off */
1152       case VariantSChess:     /* S-Chess, should work */
1153       case VariantGrand:      /* should work */
1154       case VariantSpartan:    /* should work */
1155         break;
1156       }
1157     }
1158
1159 }
1160
1161 int
1162 NextIntegerFromString (char ** str, long * value)
1163 {
1164     int result = -1;
1165     char * s = *str;
1166
1167     while( *s == ' ' || *s == '\t' ) {
1168         s++;
1169     }
1170
1171     *value = 0;
1172
1173     if( *s >= '0' && *s <= '9' ) {
1174         while( *s >= '0' && *s <= '9' ) {
1175             *value = *value * 10 + (*s - '0');
1176             s++;
1177         }
1178
1179         result = 0;
1180     }
1181
1182     *str = s;
1183
1184     return result;
1185 }
1186
1187 int
1188 NextTimeControlFromString (char ** str, long * value)
1189 {
1190     long temp;
1191     int result = NextIntegerFromString( str, &temp );
1192
1193     if( result == 0 ) {
1194         *value = temp * 60; /* Minutes */
1195         if( **str == ':' ) {
1196             (*str)++;
1197             result = NextIntegerFromString( str, &temp );
1198             *value += temp; /* Seconds */
1199         }
1200     }
1201
1202     return result;
1203 }
1204
1205 int
1206 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1207 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1208     int result = -1, type = 0; long temp, temp2;
1209
1210     if(**str != ':') return -1; // old params remain in force!
1211     (*str)++;
1212     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1213     if( NextIntegerFromString( str, &temp ) ) return -1;
1214     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1215
1216     if(**str != '/') {
1217         /* time only: incremental or sudden-death time control */
1218         if(**str == '+') { /* increment follows; read it */
1219             (*str)++;
1220             if(**str == '!') type = *(*str)++; // Bronstein TC
1221             if(result = NextIntegerFromString( str, &temp2)) return -1;
1222             *inc = temp2 * 1000;
1223             if(**str == '.') { // read fraction of increment
1224                 char *start = ++(*str);
1225                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1226                 temp2 *= 1000;
1227                 while(start++ < *str) temp2 /= 10;
1228                 *inc += temp2;
1229             }
1230         } else *inc = 0;
1231         *moves = 0; *tc = temp * 1000; *incType = type;
1232         return 0;
1233     }
1234
1235     (*str)++; /* classical time control */
1236     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1237
1238     if(result == 0) {
1239         *moves = temp;
1240         *tc    = temp2 * 1000;
1241         *inc   = 0;
1242         *incType = type;
1243     }
1244     return result;
1245 }
1246
1247 int
1248 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1249 {   /* [HGM] get time to add from the multi-session time-control string */
1250     int incType, moves=1; /* kludge to force reading of first session */
1251     long time, increment;
1252     char *s = tcString;
1253
1254     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1255     do {
1256         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1257         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1258         if(movenr == -1) return time;    /* last move before new session     */
1259         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1260         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1261         if(!moves) return increment;     /* current session is incremental   */
1262         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1263     } while(movenr >= -1);               /* try again for next session       */
1264
1265     return 0; // no new time quota on this move
1266 }
1267
1268 int
1269 ParseTimeControl (char *tc, float ti, int mps)
1270 {
1271   long tc1;
1272   long tc2;
1273   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1274   int min, sec=0;
1275
1276   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1277   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1278       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1279   if(ti > 0) {
1280
1281     if(mps)
1282       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1283     else 
1284       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1285   } else {
1286     if(mps)
1287       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1288     else 
1289       snprintf(buf, MSG_SIZ, ":%s", mytc);
1290   }
1291   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1292   
1293   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1294     return FALSE;
1295   }
1296
1297   if( *tc == '/' ) {
1298     /* Parse second time control */
1299     tc++;
1300
1301     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1302       return FALSE;
1303     }
1304
1305     if( tc2 == 0 ) {
1306       return FALSE;
1307     }
1308
1309     timeControl_2 = tc2 * 1000;
1310   }
1311   else {
1312     timeControl_2 = 0;
1313   }
1314
1315   if( tc1 == 0 ) {
1316     return FALSE;
1317   }
1318
1319   timeControl = tc1 * 1000;
1320
1321   if (ti >= 0) {
1322     timeIncrement = ti * 1000;  /* convert to ms */
1323     movesPerSession = 0;
1324   } else {
1325     timeIncrement = 0;
1326     movesPerSession = mps;
1327   }
1328   return TRUE;
1329 }
1330
1331 void
1332 InitBackEnd2 ()
1333 {
1334     if (appData.debugMode) {
1335         fprintf(debugFP, "%s\n", programVersion);
1336     }
1337     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1338
1339     set_cont_sequence(appData.wrapContSeq);
1340     if (appData.matchGames > 0) {
1341         appData.matchMode = TRUE;
1342     } else if (appData.matchMode) {
1343         appData.matchGames = 1;
1344     }
1345     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1346         appData.matchGames = appData.sameColorGames;
1347     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1348         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1349         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1350     }
1351     Reset(TRUE, FALSE);
1352     if (appData.noChessProgram || first.protocolVersion == 1) {
1353       InitBackEnd3();
1354     } else {
1355       /* kludge: allow timeout for initial "feature" commands */
1356       FreezeUI();
1357       DisplayMessage("", _("Starting chess program"));
1358       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1359     }
1360 }
1361
1362 int
1363 CalculateIndex (int index, int gameNr)
1364 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1365     int res;
1366     if(index > 0) return index; // fixed nmber
1367     if(index == 0) return 1;
1368     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1369     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1370     return res;
1371 }
1372
1373 int
1374 LoadGameOrPosition (int gameNr)
1375 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1376     if (*appData.loadGameFile != NULLCHAR) {
1377         if (!LoadGameFromFile(appData.loadGameFile,
1378                 CalculateIndex(appData.loadGameIndex, gameNr),
1379                               appData.loadGameFile, FALSE)) {
1380             DisplayFatalError(_("Bad game file"), 0, 1);
1381             return 0;
1382         }
1383     } else if (*appData.loadPositionFile != NULLCHAR) {
1384         if (!LoadPositionFromFile(appData.loadPositionFile,
1385                 CalculateIndex(appData.loadPositionIndex, gameNr),
1386                                   appData.loadPositionFile)) {
1387             DisplayFatalError(_("Bad position file"), 0, 1);
1388             return 0;
1389         }
1390     }
1391     return 1;
1392 }
1393
1394 void
1395 ReserveGame (int gameNr, char resChar)
1396 {
1397     FILE *tf = fopen(appData.tourneyFile, "r+");
1398     char *p, *q, c, buf[MSG_SIZ];
1399     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1400     safeStrCpy(buf, lastMsg, MSG_SIZ);
1401     DisplayMessage(_("Pick new game"), "");
1402     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1403     ParseArgsFromFile(tf);
1404     p = q = appData.results;
1405     if(appData.debugMode) {
1406       char *r = appData.participants;
1407       fprintf(debugFP, "results = '%s'\n", p);
1408       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1409       fprintf(debugFP, "\n");
1410     }
1411     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1412     nextGame = q - p;
1413     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1414     safeStrCpy(q, p, strlen(p) + 2);
1415     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1416     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1417     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1418         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1419         q[nextGame] = '*';
1420     }
1421     fseek(tf, -(strlen(p)+4), SEEK_END);
1422     c = fgetc(tf);
1423     if(c != '"') // depending on DOS or Unix line endings we can be one off
1424          fseek(tf, -(strlen(p)+2), SEEK_END);
1425     else fseek(tf, -(strlen(p)+3), SEEK_END);
1426     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1427     DisplayMessage(buf, "");
1428     free(p); appData.results = q;
1429     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1430        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1431       int round = appData.defaultMatchGames * appData.tourneyType;
1432       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1433          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1434         UnloadEngine(&first);  // next game belongs to other pairing;
1435         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1436     }
1437     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1438 }
1439
1440 void
1441 MatchEvent (int mode)
1442 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1443         int dummy;
1444         if(matchMode) { // already in match mode: switch it off
1445             abortMatch = TRUE;
1446             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1447             return;
1448         }
1449 //      if(gameMode != BeginningOfGame) {
1450 //          DisplayError(_("You can only start a match from the initial position."), 0);
1451 //          return;
1452 //      }
1453         abortMatch = FALSE;
1454         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1455         /* Set up machine vs. machine match */
1456         nextGame = 0;
1457         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1458         if(appData.tourneyFile[0]) {
1459             ReserveGame(-1, 0);
1460             if(nextGame > appData.matchGames) {
1461                 char buf[MSG_SIZ];
1462                 if(strchr(appData.results, '*') == NULL) {
1463                     FILE *f;
1464                     appData.tourneyCycles++;
1465                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1466                         fclose(f);
1467                         NextTourneyGame(-1, &dummy);
1468                         ReserveGame(-1, 0);
1469                         if(nextGame <= appData.matchGames) {
1470                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1471                             matchMode = mode;
1472                             ScheduleDelayedEvent(NextMatchGame, 10000);
1473                             return;
1474                         }
1475                     }
1476                 }
1477                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1478                 DisplayError(buf, 0);
1479                 appData.tourneyFile[0] = 0;
1480                 return;
1481             }
1482         } else
1483         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1484             DisplayFatalError(_("Can't have a match with no chess programs"),
1485                               0, 2);
1486             return;
1487         }
1488         matchMode = mode;
1489         matchGame = roundNr = 1;
1490         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1491         NextMatchGame();
1492 }
1493
1494 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1495
1496 void
1497 InitBackEnd3 P((void))
1498 {
1499     GameMode initialMode;
1500     char buf[MSG_SIZ];
1501     int err, len;
1502
1503     InitChessProgram(&first, startedFromSetupPosition);
1504
1505     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1506         free(programVersion);
1507         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1508         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1509         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1510     }
1511
1512     if (appData.icsActive) {
1513 #ifdef WIN32
1514         /* [DM] Make a console window if needed [HGM] merged ifs */
1515         ConsoleCreate();
1516 #endif
1517         err = establish();
1518         if (err != 0)
1519           {
1520             if (*appData.icsCommPort != NULLCHAR)
1521               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1522                              appData.icsCommPort);
1523             else
1524               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1525                         appData.icsHost, appData.icsPort);
1526
1527             if( (len >= MSG_SIZ) && appData.debugMode )
1528               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1529
1530             DisplayFatalError(buf, err, 1);
1531             return;
1532         }
1533         SetICSMode();
1534         telnetISR =
1535           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1536         fromUserISR =
1537           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1538         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1539             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1540     } else if (appData.noChessProgram) {
1541         SetNCPMode();
1542     } else {
1543         SetGNUMode();
1544     }
1545
1546     if (*appData.cmailGameName != NULLCHAR) {
1547         SetCmailMode();
1548         OpenLoopback(&cmailPR);
1549         cmailISR =
1550           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1551     }
1552
1553     ThawUI();
1554     DisplayMessage("", "");
1555     if (StrCaseCmp(appData.initialMode, "") == 0) {
1556       initialMode = BeginningOfGame;
1557       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1558         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1559         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1560         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1561         ModeHighlight();
1562       }
1563     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1564       initialMode = TwoMachinesPlay;
1565     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1566       initialMode = AnalyzeFile;
1567     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1568       initialMode = AnalyzeMode;
1569     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1570       initialMode = MachinePlaysWhite;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1572       initialMode = MachinePlaysBlack;
1573     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1574       initialMode = EditGame;
1575     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1576       initialMode = EditPosition;
1577     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1578       initialMode = Training;
1579     } else {
1580       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1581       if( (len >= MSG_SIZ) && appData.debugMode )
1582         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1583
1584       DisplayFatalError(buf, 0, 2);
1585       return;
1586     }
1587
1588     if (appData.matchMode) {
1589         if(appData.tourneyFile[0]) { // start tourney from command line
1590             FILE *f;
1591             if(f = fopen(appData.tourneyFile, "r")) {
1592                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1593                 fclose(f);
1594                 appData.clockMode = TRUE;
1595                 SetGNUMode();
1596             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1597         }
1598         MatchEvent(TRUE);
1599     } else if (*appData.cmailGameName != NULLCHAR) {
1600         /* Set up cmail mode */
1601         ReloadCmailMsgEvent(TRUE);
1602     } else {
1603         /* Set up other modes */
1604         if (initialMode == AnalyzeFile) {
1605           if (*appData.loadGameFile == NULLCHAR) {
1606             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1607             return;
1608           }
1609         }
1610         if (*appData.loadGameFile != NULLCHAR) {
1611             (void) LoadGameFromFile(appData.loadGameFile,
1612                                     appData.loadGameIndex,
1613                                     appData.loadGameFile, TRUE);
1614         } else if (*appData.loadPositionFile != NULLCHAR) {
1615             (void) LoadPositionFromFile(appData.loadPositionFile,
1616                                         appData.loadPositionIndex,
1617                                         appData.loadPositionFile);
1618             /* [HGM] try to make self-starting even after FEN load */
1619             /* to allow automatic setup of fairy variants with wtm */
1620             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1621                 gameMode = BeginningOfGame;
1622                 setboardSpoiledMachineBlack = 1;
1623             }
1624             /* [HGM] loadPos: make that every new game uses the setup */
1625             /* from file as long as we do not switch variant          */
1626             if(!blackPlaysFirst) {
1627                 startedFromPositionFile = TRUE;
1628                 CopyBoard(filePosition, boards[0]);
1629             }
1630         }
1631         if (initialMode == AnalyzeMode) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1638             return;
1639           }
1640           AnalyzeModeEvent();
1641         } else if (initialMode == AnalyzeFile) {
1642           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1643           ShowThinkingEvent();
1644           AnalyzeFileEvent();
1645           AnalysisPeriodicEvent(1);
1646         } else if (initialMode == MachinePlaysWhite) {
1647           if (appData.noChessProgram) {
1648             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1649                               0, 2);
1650             return;
1651           }
1652           if (appData.icsActive) {
1653             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1654                               0, 2);
1655             return;
1656           }
1657           MachineWhiteEvent();
1658         } else if (initialMode == MachinePlaysBlack) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           MachineBlackEvent();
1670         } else if (initialMode == TwoMachinesPlay) {
1671           if (appData.noChessProgram) {
1672             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1673                               0, 2);
1674             return;
1675           }
1676           if (appData.icsActive) {
1677             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1678                               0, 2);
1679             return;
1680           }
1681           TwoMachinesEvent();
1682         } else if (initialMode == EditGame) {
1683           EditGameEvent();
1684         } else if (initialMode == EditPosition) {
1685           EditPositionEvent();
1686         } else if (initialMode == Training) {
1687           if (*appData.loadGameFile == NULLCHAR) {
1688             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1689             return;
1690           }
1691           TrainingEvent();
1692         }
1693     }
1694 }
1695
1696 void
1697 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1698 {
1699     DisplayBook(current+1);
1700
1701     MoveHistorySet( movelist, first, last, current, pvInfoList );
1702
1703     EvalGraphSet( first, last, current, pvInfoList );
1704
1705     MakeEngineOutputTitle();
1706 }
1707
1708 /*
1709  * Establish will establish a contact to a remote host.port.
1710  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1711  *  used to talk to the host.
1712  * Returns 0 if okay, error code if not.
1713  */
1714 int
1715 establish ()
1716 {
1717     char buf[MSG_SIZ];
1718
1719     if (*appData.icsCommPort != NULLCHAR) {
1720         /* Talk to the host through a serial comm port */
1721         return OpenCommPort(appData.icsCommPort, &icsPR);
1722
1723     } else if (*appData.gateway != NULLCHAR) {
1724         if (*appData.remoteShell == NULLCHAR) {
1725             /* Use the rcmd protocol to run telnet program on a gateway host */
1726             snprintf(buf, sizeof(buf), "%s %s %s",
1727                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1728             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1729
1730         } else {
1731             /* Use the rsh program to run telnet program on a gateway host */
1732             if (*appData.remoteUser == NULLCHAR) {
1733                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1734                         appData.gateway, appData.telnetProgram,
1735                         appData.icsHost, appData.icsPort);
1736             } else {
1737                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1738                         appData.remoteShell, appData.gateway,
1739                         appData.remoteUser, appData.telnetProgram,
1740                         appData.icsHost, appData.icsPort);
1741             }
1742             return StartChildProcess(buf, "", &icsPR);
1743
1744         }
1745     } else if (appData.useTelnet) {
1746         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1747
1748     } else {
1749         /* TCP socket interface differs somewhat between
1750            Unix and NT; handle details in the front end.
1751            */
1752         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1753     }
1754 }
1755
1756 void
1757 EscapeExpand (char *p, char *q)
1758 {       // [HGM] initstring: routine to shape up string arguments
1759         while(*p++ = *q++) if(p[-1] == '\\')
1760             switch(*q++) {
1761                 case 'n': p[-1] = '\n'; break;
1762                 case 'r': p[-1] = '\r'; break;
1763                 case 't': p[-1] = '\t'; break;
1764                 case '\\': p[-1] = '\\'; break;
1765                 case 0: *p = 0; return;
1766                 default: p[-1] = q[-1]; break;
1767             }
1768 }
1769
1770 void
1771 show_bytes (FILE *fp, char *buf, int count)
1772 {
1773     while (count--) {
1774         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1775             fprintf(fp, "\\%03o", *buf & 0xff);
1776         } else {
1777             putc(*buf, fp);
1778         }
1779         buf++;
1780     }
1781     fflush(fp);
1782 }
1783
1784 /* Returns an errno value */
1785 int
1786 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1787 {
1788     char buf[8192], *p, *q, *buflim;
1789     int left, newcount, outcount;
1790
1791     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1792         *appData.gateway != NULLCHAR) {
1793         if (appData.debugMode) {
1794             fprintf(debugFP, ">ICS: ");
1795             show_bytes(debugFP, message, count);
1796             fprintf(debugFP, "\n");
1797         }
1798         return OutputToProcess(pr, message, count, outError);
1799     }
1800
1801     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1802     p = message;
1803     q = buf;
1804     left = count;
1805     newcount = 0;
1806     while (left) {
1807         if (q >= buflim) {
1808             if (appData.debugMode) {
1809                 fprintf(debugFP, ">ICS: ");
1810                 show_bytes(debugFP, buf, newcount);
1811                 fprintf(debugFP, "\n");
1812             }
1813             outcount = OutputToProcess(pr, buf, newcount, outError);
1814             if (outcount < newcount) return -1; /* to be sure */
1815             q = buf;
1816             newcount = 0;
1817         }
1818         if (*p == '\n') {
1819             *q++ = '\r';
1820             newcount++;
1821         } else if (((unsigned char) *p) == TN_IAC) {
1822             *q++ = (char) TN_IAC;
1823             newcount ++;
1824         }
1825         *q++ = *p++;
1826         newcount++;
1827         left--;
1828     }
1829     if (appData.debugMode) {
1830         fprintf(debugFP, ">ICS: ");
1831         show_bytes(debugFP, buf, newcount);
1832         fprintf(debugFP, "\n");
1833     }
1834     outcount = OutputToProcess(pr, buf, newcount, outError);
1835     if (outcount < newcount) return -1; /* to be sure */
1836     return count;
1837 }
1838
1839 void
1840 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1841 {
1842     int outError, outCount;
1843     static int gotEof = 0;
1844
1845     /* Pass data read from player on to ICS */
1846     if (count > 0) {
1847         gotEof = 0;
1848         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1849         if (outCount < count) {
1850             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1851         }
1852     } else if (count < 0) {
1853         RemoveInputSource(isr);
1854         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1855     } else if (gotEof++ > 0) {
1856         RemoveInputSource(isr);
1857         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1858     }
1859 }
1860
1861 void
1862 KeepAlive ()
1863 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1864     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1865     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1866     SendToICS("date\n");
1867     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1868 }
1869
1870 /* added routine for printf style output to ics */
1871 void
1872 ics_printf (char *format, ...)
1873 {
1874     char buffer[MSG_SIZ];
1875     va_list args;
1876
1877     va_start(args, format);
1878     vsnprintf(buffer, sizeof(buffer), format, args);
1879     buffer[sizeof(buffer)-1] = '\0';
1880     SendToICS(buffer);
1881     va_end(args);
1882 }
1883
1884 void
1885 SendToICS (char *s)
1886 {
1887     int count, outCount, outError;
1888
1889     if (icsPR == NoProc) return;
1890
1891     count = strlen(s);
1892     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1893     if (outCount < count) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 /* This is used for sending logon scripts to the ICS. Sending
1899    without a delay causes problems when using timestamp on ICC
1900    (at least on my machine). */
1901 void
1902 SendToICSDelayed (char *s, long msdelay)
1903 {
1904     int count, outCount, outError;
1905
1906     if (icsPR == NoProc) return;
1907
1908     count = strlen(s);
1909     if (appData.debugMode) {
1910         fprintf(debugFP, ">ICS: ");
1911         show_bytes(debugFP, s, count);
1912         fprintf(debugFP, "\n");
1913     }
1914     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1915                                       msdelay);
1916     if (outCount < count) {
1917         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1918     }
1919 }
1920
1921
1922 /* Remove all highlighting escape sequences in s
1923    Also deletes any suffix starting with '('
1924    */
1925 char *
1926 StripHighlightAndTitle (char *s)
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             if (*s == '(' || *s == '[') {
1938                 *p = NULLCHAR;
1939                 return retbuf;
1940             }
1941             *p++ = *s++;
1942         }
1943     }
1944     *p = NULLCHAR;
1945     return retbuf;
1946 }
1947
1948 /* Remove all highlighting escape sequences in s */
1949 char *
1950 StripHighlight (char *s)
1951 {
1952     static char retbuf[MSG_SIZ];
1953     char *p = retbuf;
1954
1955     while (*s != NULLCHAR) {
1956         while (*s == '\033') {
1957             while (*s != NULLCHAR && !isalpha(*s)) s++;
1958             if (*s != NULLCHAR) s++;
1959         }
1960         while (*s != NULLCHAR && *s != '\033') {
1961             *p++ = *s++;
1962         }
1963     }
1964     *p = NULLCHAR;
1965     return retbuf;
1966 }
1967
1968 char *variantNames[] = VARIANT_NAMES;
1969 char *
1970 VariantName (VariantClass v)
1971 {
1972     return variantNames[v];
1973 }
1974
1975
1976 /* Identify a variant from the strings the chess servers use or the
1977    PGN Variant tag names we use. */
1978 VariantClass
1979 StringToVariant (char *e)
1980 {
1981     char *p;
1982     int wnum = -1;
1983     VariantClass v = VariantNormal;
1984     int i, found = FALSE;
1985     char buf[MSG_SIZ];
1986     int len;
1987
1988     if (!e) return v;
1989
1990     /* [HGM] skip over optional board-size prefixes */
1991     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1992         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1993         while( *e++ != '_');
1994     }
1995
1996     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1997         v = VariantNormal;
1998         found = TRUE;
1999     } else
2000     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2001       if (StrCaseStr(e, variantNames[i])) {
2002         v = (VariantClass) i;
2003         found = TRUE;
2004         break;
2005       }
2006     }
2007
2008     if (!found) {
2009       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2010           || StrCaseStr(e, "wild/fr")
2011           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2012         v = VariantFischeRandom;
2013       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2014                  (i = 1, p = StrCaseStr(e, "w"))) {
2015         p += i;
2016         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2017         if (isdigit(*p)) {
2018           wnum = atoi(p);
2019         } else {
2020           wnum = -1;
2021         }
2022         switch (wnum) {
2023         case 0: /* FICS only, actually */
2024         case 1:
2025           /* Castling legal even if K starts on d-file */
2026           v = VariantWildCastle;
2027           break;
2028         case 2:
2029         case 3:
2030         case 4:
2031           /* Castling illegal even if K & R happen to start in
2032              normal positions. */
2033           v = VariantNoCastle;
2034           break;
2035         case 5:
2036         case 7:
2037         case 8:
2038         case 10:
2039         case 11:
2040         case 12:
2041         case 13:
2042         case 14:
2043         case 15:
2044         case 18:
2045         case 19:
2046           /* Castling legal iff K & R start in normal positions */
2047           v = VariantNormal;
2048           break;
2049         case 6:
2050         case 20:
2051         case 21:
2052           /* Special wilds for position setup; unclear what to do here */
2053           v = VariantLoadable;
2054           break;
2055         case 9:
2056           /* Bizarre ICC game */
2057           v = VariantTwoKings;
2058           break;
2059         case 16:
2060           v = VariantKriegspiel;
2061           break;
2062         case 17:
2063           v = VariantLosers;
2064           break;
2065         case 22:
2066           v = VariantFischeRandom;
2067           break;
2068         case 23:
2069           v = VariantCrazyhouse;
2070           break;
2071         case 24:
2072           v = VariantBughouse;
2073           break;
2074         case 25:
2075           v = Variant3Check;
2076           break;
2077         case 26:
2078           /* Not quite the same as FICS suicide! */
2079           v = VariantGiveaway;
2080           break;
2081         case 27:
2082           v = VariantAtomic;
2083           break;
2084         case 28:
2085           v = VariantShatranj;
2086           break;
2087
2088         /* Temporary names for future ICC types.  The name *will* change in
2089            the next xboard/WinBoard release after ICC defines it. */
2090         case 29:
2091           v = Variant29;
2092           break;
2093         case 30:
2094           v = Variant30;
2095           break;
2096         case 31:
2097           v = Variant31;
2098           break;
2099         case 32:
2100           v = Variant32;
2101           break;
2102         case 33:
2103           v = Variant33;
2104           break;
2105         case 34:
2106           v = Variant34;
2107           break;
2108         case 35:
2109           v = Variant35;
2110           break;
2111         case 36:
2112           v = Variant36;
2113           break;
2114         case 37:
2115           v = VariantShogi;
2116           break;
2117         case 38:
2118           v = VariantXiangqi;
2119           break;
2120         case 39:
2121           v = VariantCourier;
2122           break;
2123         case 40:
2124           v = VariantGothic;
2125           break;
2126         case 41:
2127           v = VariantCapablanca;
2128           break;
2129         case 42:
2130           v = VariantKnightmate;
2131           break;
2132         case 43:
2133           v = VariantFairy;
2134           break;
2135         case 44:
2136           v = VariantCylinder;
2137           break;
2138         case 45:
2139           v = VariantFalcon;
2140           break;
2141         case 46:
2142           v = VariantCapaRandom;
2143           break;
2144         case 47:
2145           v = VariantBerolina;
2146           break;
2147         case 48:
2148           v = VariantJanus;
2149           break;
2150         case 49:
2151           v = VariantSuper;
2152           break;
2153         case 50:
2154           v = VariantGreat;
2155           break;
2156         case -1:
2157           /* Found "wild" or "w" in the string but no number;
2158              must assume it's normal chess. */
2159           v = VariantNormal;
2160           break;
2161         default:
2162           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2163           if( (len >= MSG_SIZ) && appData.debugMode )
2164             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2165
2166           DisplayError(buf, 0);
2167           v = VariantUnknown;
2168           break;
2169         }
2170       }
2171     }
2172     if (appData.debugMode) {
2173       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2174               e, wnum, VariantName(v));
2175     }
2176     return v;
2177 }
2178
2179 static int leftover_start = 0, leftover_len = 0;
2180 char star_match[STAR_MATCH_N][MSG_SIZ];
2181
2182 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2183    advance *index beyond it, and set leftover_start to the new value of
2184    *index; else return FALSE.  If pattern contains the character '*', it
2185    matches any sequence of characters not containing '\r', '\n', or the
2186    character following the '*' (if any), and the matched sequence(s) are
2187    copied into star_match.
2188    */
2189 int
2190 looking_at ( char *buf, int *index, char *pattern)
2191 {
2192     char *bufp = &buf[*index], *patternp = pattern;
2193     int star_count = 0;
2194     char *matchp = star_match[0];
2195
2196     for (;;) {
2197         if (*patternp == NULLCHAR) {
2198             *index = leftover_start = bufp - buf;
2199             *matchp = NULLCHAR;
2200             return TRUE;
2201         }
2202         if (*bufp == NULLCHAR) return FALSE;
2203         if (*patternp == '*') {
2204             if (*bufp == *(patternp + 1)) {
2205                 *matchp = NULLCHAR;
2206                 matchp = star_match[++star_count];
2207                 patternp += 2;
2208                 bufp++;
2209                 continue;
2210             } else if (*bufp == '\n' || *bufp == '\r') {
2211                 patternp++;
2212                 if (*patternp == NULLCHAR)
2213                   continue;
2214                 else
2215                   return FALSE;
2216             } else {
2217                 *matchp++ = *bufp++;
2218                 continue;
2219             }
2220         }
2221         if (*patternp != *bufp) return FALSE;
2222         patternp++;
2223         bufp++;
2224     }
2225 }
2226
2227 void
2228 SendToPlayer (char *data, int length)
2229 {
2230     int error, outCount;
2231     outCount = OutputToProcess(NoProc, data, length, &error);
2232     if (outCount < length) {
2233         DisplayFatalError(_("Error writing to display"), error, 1);
2234     }
2235 }
2236
2237 void
2238 PackHolding (char packed[], char *holding)
2239 {
2240     char *p = holding;
2241     char *q = packed;
2242     int runlength = 0;
2243     int curr = 9999;
2244     do {
2245         if (*p == curr) {
2246             runlength++;
2247         } else {
2248             switch (runlength) {
2249               case 0:
2250                 break;
2251               case 1:
2252                 *q++ = curr;
2253                 break;
2254               case 2:
2255                 *q++ = curr;
2256                 *q++ = curr;
2257                 break;
2258               default:
2259                 sprintf(q, "%d", runlength);
2260                 while (*q) q++;
2261                 *q++ = curr;
2262                 break;
2263             }
2264             runlength = 1;
2265             curr = *p;
2266         }
2267     } while (*p++);
2268     *q = NULLCHAR;
2269 }
2270
2271 /* Telnet protocol requests from the front end */
2272 void
2273 TelnetRequest (unsigned char ddww, unsigned char option)
2274 {
2275     unsigned char msg[3];
2276     int outCount, outError;
2277
2278     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2279
2280     if (appData.debugMode) {
2281         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2282         switch (ddww) {
2283           case TN_DO:
2284             ddwwStr = "DO";
2285             break;
2286           case TN_DONT:
2287             ddwwStr = "DONT";
2288             break;
2289           case TN_WILL:
2290             ddwwStr = "WILL";
2291             break;
2292           case TN_WONT:
2293             ddwwStr = "WONT";
2294             break;
2295           default:
2296             ddwwStr = buf1;
2297             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2298             break;
2299         }
2300         switch (option) {
2301           case TN_ECHO:
2302             optionStr = "ECHO";
2303             break;
2304           default:
2305             optionStr = buf2;
2306             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2307             break;
2308         }
2309         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2310     }
2311     msg[0] = TN_IAC;
2312     msg[1] = ddww;
2313     msg[2] = option;
2314     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2315     if (outCount < 3) {
2316         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2317     }
2318 }
2319
2320 void
2321 DoEcho ()
2322 {
2323     if (!appData.icsActive) return;
2324     TelnetRequest(TN_DO, TN_ECHO);
2325 }
2326
2327 void
2328 DontEcho ()
2329 {
2330     if (!appData.icsActive) return;
2331     TelnetRequest(TN_DONT, TN_ECHO);
2332 }
2333
2334 void
2335 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2336 {
2337     /* put the holdings sent to us by the server on the board holdings area */
2338     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2339     char p;
2340     ChessSquare piece;
2341
2342     if(gameInfo.holdingsWidth < 2)  return;
2343     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2344         return; // prevent overwriting by pre-board holdings
2345
2346     if( (int)lowestPiece >= BlackPawn ) {
2347         holdingsColumn = 0;
2348         countsColumn = 1;
2349         holdingsStartRow = BOARD_HEIGHT-1;
2350         direction = -1;
2351     } else {
2352         holdingsColumn = BOARD_WIDTH-1;
2353         countsColumn = BOARD_WIDTH-2;
2354         holdingsStartRow = 0;
2355         direction = 1;
2356     }
2357
2358     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2359         board[i][holdingsColumn] = EmptySquare;
2360         board[i][countsColumn]   = (ChessSquare) 0;
2361     }
2362     while( (p=*holdings++) != NULLCHAR ) {
2363         piece = CharToPiece( ToUpper(p) );
2364         if(piece == EmptySquare) continue;
2365         /*j = (int) piece - (int) WhitePawn;*/
2366         j = PieceToNumber(piece);
2367         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2368         if(j < 0) continue;               /* should not happen */
2369         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2370         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2371         board[holdingsStartRow+j*direction][countsColumn]++;
2372     }
2373 }
2374
2375
2376 void
2377 VariantSwitch (Board board, VariantClass newVariant)
2378 {
2379    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2380    static Board oldBoard;
2381
2382    startedFromPositionFile = FALSE;
2383    if(gameInfo.variant == newVariant) return;
2384
2385    /* [HGM] This routine is called each time an assignment is made to
2386     * gameInfo.variant during a game, to make sure the board sizes
2387     * are set to match the new variant. If that means adding or deleting
2388     * holdings, we shift the playing board accordingly
2389     * This kludge is needed because in ICS observe mode, we get boards
2390     * of an ongoing game without knowing the variant, and learn about the
2391     * latter only later. This can be because of the move list we requested,
2392     * in which case the game history is refilled from the beginning anyway,
2393     * but also when receiving holdings of a crazyhouse game. In the latter
2394     * case we want to add those holdings to the already received position.
2395     */
2396
2397
2398    if (appData.debugMode) {
2399      fprintf(debugFP, "Switch board from %s to %s\n",
2400              VariantName(gameInfo.variant), VariantName(newVariant));
2401      setbuf(debugFP, NULL);
2402    }
2403    shuffleOpenings = 0;       /* [HGM] shuffle */
2404    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2405    switch(newVariant)
2406      {
2407      case VariantShogi:
2408        newWidth = 9;  newHeight = 9;
2409        gameInfo.holdingsSize = 7;
2410      case VariantBughouse:
2411      case VariantCrazyhouse:
2412        newHoldingsWidth = 2; break;
2413      case VariantGreat:
2414        newWidth = 10;
2415      case VariantSuper:
2416        newHoldingsWidth = 2;
2417        gameInfo.holdingsSize = 8;
2418        break;
2419      case VariantGothic:
2420      case VariantCapablanca:
2421      case VariantCapaRandom:
2422        newWidth = 10;
2423      default:
2424        newHoldingsWidth = gameInfo.holdingsSize = 0;
2425      };
2426
2427    if(newWidth  != gameInfo.boardWidth  ||
2428       newHeight != gameInfo.boardHeight ||
2429       newHoldingsWidth != gameInfo.holdingsWidth ) {
2430
2431      /* shift position to new playing area, if needed */
2432      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2433        for(i=0; i<BOARD_HEIGHT; i++)
2434          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2435            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2436              board[i][j];
2437        for(i=0; i<newHeight; i++) {
2438          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2439          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2440        }
2441      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2442        for(i=0; i<BOARD_HEIGHT; i++)
2443          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2444            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2445              board[i][j];
2446      }
2447      gameInfo.boardWidth  = newWidth;
2448      gameInfo.boardHeight = newHeight;
2449      gameInfo.holdingsWidth = newHoldingsWidth;
2450      gameInfo.variant = newVariant;
2451      InitDrawingSizes(-2, 0);
2452    } else gameInfo.variant = newVariant;
2453    CopyBoard(oldBoard, board);   // remember correctly formatted board
2454      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2455    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2456 }
2457
2458 static int loggedOn = FALSE;
2459
2460 /*-- Game start info cache: --*/
2461 int gs_gamenum;
2462 char gs_kind[MSG_SIZ];
2463 static char player1Name[128] = "";
2464 static char player2Name[128] = "";
2465 static char cont_seq[] = "\n\\   ";
2466 static int player1Rating = -1;
2467 static int player2Rating = -1;
2468 /*----------------------------*/
2469
2470 ColorClass curColor = ColorNormal;
2471 int suppressKibitz = 0;
2472
2473 // [HGM] seekgraph
2474 Boolean soughtPending = FALSE;
2475 Boolean seekGraphUp;
2476 #define MAX_SEEK_ADS 200
2477 #define SQUARE 0x80
2478 char *seekAdList[MAX_SEEK_ADS];
2479 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2480 float tcList[MAX_SEEK_ADS];
2481 char colorList[MAX_SEEK_ADS];
2482 int nrOfSeekAds = 0;
2483 int minRating = 1010, maxRating = 2800;
2484 int hMargin = 10, vMargin = 20, h, w;
2485 extern int squareSize, lineGap;
2486
2487 void
2488 PlotSeekAd (int i)
2489 {
2490         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2491         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2492         if(r < minRating+100 && r >=0 ) r = minRating+100;
2493         if(r > maxRating) r = maxRating;
2494         if(tc < 1.) tc = 1.;
2495         if(tc > 95.) tc = 95.;
2496         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2497         y = ((double)r - minRating)/(maxRating - minRating)
2498             * (h-vMargin-squareSize/8-1) + vMargin;
2499         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2500         if(strstr(seekAdList[i], " u ")) color = 1;
2501         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2502            !strstr(seekAdList[i], "bullet") &&
2503            !strstr(seekAdList[i], "blitz") &&
2504            !strstr(seekAdList[i], "standard") ) color = 2;
2505         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2506         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2507 }
2508
2509 void
2510 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2511 {
2512         char buf[MSG_SIZ], *ext = "";
2513         VariantClass v = StringToVariant(type);
2514         if(strstr(type, "wild")) {
2515             ext = type + 4; // append wild number
2516             if(v == VariantFischeRandom) type = "chess960"; else
2517             if(v == VariantLoadable) type = "setup"; else
2518             type = VariantName(v);
2519         }
2520         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2521         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2522             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2523             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2524             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2525             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2526             seekNrList[nrOfSeekAds] = nr;
2527             zList[nrOfSeekAds] = 0;
2528             seekAdList[nrOfSeekAds++] = StrSave(buf);
2529             if(plot) PlotSeekAd(nrOfSeekAds-1);
2530         }
2531 }
2532
2533 void
2534 EraseSeekDot (int i)
2535 {
2536     int x = xList[i], y = yList[i], d=squareSize/4, k;
2537     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2538     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2539     // now replot every dot that overlapped
2540     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2541         int xx = xList[k], yy = yList[k];
2542         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2543             DrawSeekDot(xx, yy, colorList[k]);
2544     }
2545 }
2546
2547 void
2548 RemoveSeekAd (int nr)
2549 {
2550         int i;
2551         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2552             EraseSeekDot(i);
2553             if(seekAdList[i]) free(seekAdList[i]);
2554             seekAdList[i] = seekAdList[--nrOfSeekAds];
2555             seekNrList[i] = seekNrList[nrOfSeekAds];
2556             ratingList[i] = ratingList[nrOfSeekAds];
2557             colorList[i]  = colorList[nrOfSeekAds];
2558             tcList[i] = tcList[nrOfSeekAds];
2559             xList[i]  = xList[nrOfSeekAds];
2560             yList[i]  = yList[nrOfSeekAds];
2561             zList[i]  = zList[nrOfSeekAds];
2562             seekAdList[nrOfSeekAds] = NULL;
2563             break;
2564         }
2565 }
2566
2567 Boolean
2568 MatchSoughtLine (char *line)
2569 {
2570     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2571     int nr, base, inc, u=0; char dummy;
2572
2573     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2574        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2575        (u=1) &&
2576        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2578         // match: compact and save the line
2579         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2580         return TRUE;
2581     }
2582     return FALSE;
2583 }
2584
2585 int
2586 DrawSeekGraph ()
2587 {
2588     int i;
2589     if(!seekGraphUp) return FALSE;
2590     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2591     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2592
2593     DrawSeekBackground(0, 0, w, h);
2594     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2595     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2596     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2597         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2598         yy = h-1-yy;
2599         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2600         if(i%500 == 0) {
2601             char buf[MSG_SIZ];
2602             snprintf(buf, MSG_SIZ, "%d", i);
2603             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2604         }
2605     }
2606     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2607     for(i=1; i<100; i+=(i<10?1:5)) {
2608         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2609         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2610         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2611             char buf[MSG_SIZ];
2612             snprintf(buf, MSG_SIZ, "%d", i);
2613             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2614         }
2615     }
2616     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2617     return TRUE;
2618 }
2619
2620 int
2621 SeekGraphClick (ClickType click, int x, int y, int moving)
2622 {
2623     static int lastDown = 0, displayed = 0, lastSecond;
2624     if(y < 0) return FALSE;
2625     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2626         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2627         if(!seekGraphUp) return FALSE;
2628         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2629         DrawPosition(TRUE, NULL);
2630         return TRUE;
2631     }
2632     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2633         if(click == Release || moving) return FALSE;
2634         nrOfSeekAds = 0;
2635         soughtPending = TRUE;
2636         SendToICS(ics_prefix);
2637         SendToICS("sought\n"); // should this be "sought all"?
2638     } else { // issue challenge based on clicked ad
2639         int dist = 10000; int i, closest = 0, second = 0;
2640         for(i=0; i<nrOfSeekAds; i++) {
2641             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2642             if(d < dist) { dist = d; closest = i; }
2643             second += (d - zList[i] < 120); // count in-range ads
2644             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2645         }
2646         if(dist < 120) {
2647             char buf[MSG_SIZ];
2648             second = (second > 1);
2649             if(displayed != closest || second != lastSecond) {
2650                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2651                 lastSecond = second; displayed = closest;
2652             }
2653             if(click == Press) {
2654                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2655                 lastDown = closest;
2656                 return TRUE;
2657             } // on press 'hit', only show info
2658             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2659             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2660             SendToICS(ics_prefix);
2661             SendToICS(buf);
2662             return TRUE; // let incoming board of started game pop down the graph
2663         } else if(click == Release) { // release 'miss' is ignored
2664             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2665             if(moving == 2) { // right up-click
2666                 nrOfSeekAds = 0; // refresh graph
2667                 soughtPending = TRUE;
2668                 SendToICS(ics_prefix);
2669                 SendToICS("sought\n"); // should this be "sought all"?
2670             }
2671             return TRUE;
2672         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2673         // press miss or release hit 'pop down' seek graph
2674         seekGraphUp = FALSE;
2675         DrawPosition(TRUE, NULL);
2676     }
2677     return TRUE;
2678 }
2679
2680 void
2681 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2682 {
2683 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2684 #define STARTED_NONE 0
2685 #define STARTED_MOVES 1
2686 #define STARTED_BOARD 2
2687 #define STARTED_OBSERVE 3
2688 #define STARTED_HOLDINGS 4
2689 #define STARTED_CHATTER 5
2690 #define STARTED_COMMENT 6
2691 #define STARTED_MOVES_NOHIDE 7
2692
2693     static int started = STARTED_NONE;
2694     static char parse[20000];
2695     static int parse_pos = 0;
2696     static char buf[BUF_SIZE + 1];
2697     static int firstTime = TRUE, intfSet = FALSE;
2698     static ColorClass prevColor = ColorNormal;
2699     static int savingComment = FALSE;
2700     static int cmatch = 0; // continuation sequence match
2701     char *bp;
2702     char str[MSG_SIZ];
2703     int i, oldi;
2704     int buf_len;
2705     int next_out;
2706     int tkind;
2707     int backup;    /* [DM] For zippy color lines */
2708     char *p;
2709     char talker[MSG_SIZ]; // [HGM] chat
2710     int channel;
2711
2712     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2713
2714     if (appData.debugMode) {
2715       if (!error) {
2716         fprintf(debugFP, "<ICS: ");
2717         show_bytes(debugFP, data, count);
2718         fprintf(debugFP, "\n");
2719       }
2720     }
2721
2722     if (appData.debugMode) { int f = forwardMostMove;
2723         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2724                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2725                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2726     }
2727     if (count > 0) {
2728         /* If last read ended with a partial line that we couldn't parse,
2729            prepend it to the new read and try again. */
2730         if (leftover_len > 0) {
2731             for (i=0; i<leftover_len; i++)
2732               buf[i] = buf[leftover_start + i];
2733         }
2734
2735     /* copy new characters into the buffer */
2736     bp = buf + leftover_len;
2737     buf_len=leftover_len;
2738     for (i=0; i<count; i++)
2739     {
2740         // ignore these
2741         if (data[i] == '\r')
2742             continue;
2743
2744         // join lines split by ICS?
2745         if (!appData.noJoin)
2746         {
2747             /*
2748                 Joining just consists of finding matches against the
2749                 continuation sequence, and discarding that sequence
2750                 if found instead of copying it.  So, until a match
2751                 fails, there's nothing to do since it might be the
2752                 complete sequence, and thus, something we don't want
2753                 copied.
2754             */
2755             if (data[i] == cont_seq[cmatch])
2756             {
2757                 cmatch++;
2758                 if (cmatch == strlen(cont_seq))
2759                 {
2760                     cmatch = 0; // complete match.  just reset the counter
2761
2762                     /*
2763                         it's possible for the ICS to not include the space
2764                         at the end of the last word, making our [correct]
2765                         join operation fuse two separate words.  the server
2766                         does this when the space occurs at the width setting.
2767                     */
2768                     if (!buf_len || buf[buf_len-1] != ' ')
2769                     {
2770                         *bp++ = ' ';
2771                         buf_len++;
2772                     }
2773                 }
2774                 continue;
2775             }
2776             else if (cmatch)
2777             {
2778                 /*
2779                     match failed, so we have to copy what matched before
2780                     falling through and copying this character.  In reality,
2781                     this will only ever be just the newline character, but
2782                     it doesn't hurt to be precise.
2783                 */
2784                 strncpy(bp, cont_seq, cmatch);
2785                 bp += cmatch;
2786                 buf_len += cmatch;
2787                 cmatch = 0;
2788             }
2789         }
2790
2791         // copy this char
2792         *bp++ = data[i];
2793         buf_len++;
2794     }
2795
2796         buf[buf_len] = NULLCHAR;
2797 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2798         next_out = 0;
2799         leftover_start = 0;
2800
2801         i = 0;
2802         while (i < buf_len) {
2803             /* Deal with part of the TELNET option negotiation
2804                protocol.  We refuse to do anything beyond the
2805                defaults, except that we allow the WILL ECHO option,
2806                which ICS uses to turn off password echoing when we are
2807                directly connected to it.  We reject this option
2808                if localLineEditing mode is on (always on in xboard)
2809                and we are talking to port 23, which might be a real
2810                telnet server that will try to keep WILL ECHO on permanently.
2811              */
2812             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2813                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2814                 unsigned char option;
2815                 oldi = i;
2816                 switch ((unsigned char) buf[++i]) {
2817                   case TN_WILL:
2818                     if (appData.debugMode)
2819                       fprintf(debugFP, "\n<WILL ");
2820                     switch (option = (unsigned char) buf[++i]) {
2821                       case TN_ECHO:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "ECHO ");
2824                         /* Reply only if this is a change, according
2825                            to the protocol rules. */
2826                         if (remoteEchoOption) break;
2827                         if (appData.localLineEditing &&
2828                             atoi(appData.icsPort) == TN_PORT) {
2829                             TelnetRequest(TN_DONT, TN_ECHO);
2830                         } else {
2831                             EchoOff();
2832                             TelnetRequest(TN_DO, TN_ECHO);
2833                             remoteEchoOption = TRUE;
2834                         }
2835                         break;
2836                       default:
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "%d ", option);
2839                         /* Whatever this is, we don't want it. */
2840                         TelnetRequest(TN_DONT, option);
2841                         break;
2842                     }
2843                     break;
2844                   case TN_WONT:
2845                     if (appData.debugMode)
2846                       fprintf(debugFP, "\n<WONT ");
2847                     switch (option = (unsigned char) buf[++i]) {
2848                       case TN_ECHO:
2849                         if (appData.debugMode)
2850                           fprintf(debugFP, "ECHO ");
2851                         /* Reply only if this is a change, according
2852                            to the protocol rules. */
2853                         if (!remoteEchoOption) break;
2854                         EchoOn();
2855                         TelnetRequest(TN_DONT, TN_ECHO);
2856                         remoteEchoOption = FALSE;
2857                         break;
2858                       default:
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "%d ", (unsigned char) option);
2861                         /* Whatever this is, it must already be turned
2862                            off, because we never agree to turn on
2863                            anything non-default, so according to the
2864                            protocol rules, we don't reply. */
2865                         break;
2866                     }
2867                     break;
2868                   case TN_DO:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<DO ");
2871                     switch (option = (unsigned char) buf[++i]) {
2872                       default:
2873                         /* Whatever this is, we refuse to do it. */
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "%d ", option);
2876                         TelnetRequest(TN_WONT, option);
2877                         break;
2878                     }
2879                     break;
2880                   case TN_DONT:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<DONT ");
2883                     switch (option = (unsigned char) buf[++i]) {
2884                       default:
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "%d ", option);
2887                         /* Whatever this is, we are already not doing
2888                            it, because we never agree to do anything
2889                            non-default, so according to the protocol
2890                            rules, we don't reply. */
2891                         break;
2892                     }
2893                     break;
2894                   case TN_IAC:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<IAC ");
2897                     /* Doubled IAC; pass it through */
2898                     i--;
2899                     break;
2900                   default:
2901                     if (appData.debugMode)
2902                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2903                     /* Drop all other telnet commands on the floor */
2904                     break;
2905                 }
2906                 if (oldi > next_out)
2907                   SendToPlayer(&buf[next_out], oldi - next_out);
2908                 if (++i > next_out)
2909                   next_out = i;
2910                 continue;
2911             }
2912
2913             /* OK, this at least will *usually* work */
2914             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2915                 loggedOn = TRUE;
2916             }
2917
2918             if (loggedOn && !intfSet) {
2919                 if (ics_type == ICS_ICC) {
2920                   snprintf(str, MSG_SIZ,
2921                           "/set-quietly interface %s\n/set-quietly style 12\n",
2922                           programVersion);
2923                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2924                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2925                 } else if (ics_type == ICS_CHESSNET) {
2926                   snprintf(str, MSG_SIZ, "/style 12\n");
2927                 } else {
2928                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2929                   strcat(str, programVersion);
2930                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2931                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2932                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2933 #ifdef WIN32
2934                   strcat(str, "$iset nohighlight 1\n");
2935 #endif
2936                   strcat(str, "$iset lock 1\n$style 12\n");
2937                 }
2938                 SendToICS(str);
2939                 NotifyFrontendLogin();
2940                 intfSet = TRUE;
2941             }
2942
2943             if (started == STARTED_COMMENT) {
2944                 /* Accumulate characters in comment */
2945                 parse[parse_pos++] = buf[i];
2946                 if (buf[i] == '\n') {
2947                     parse[parse_pos] = NULLCHAR;
2948                     if(chattingPartner>=0) {
2949                         char mess[MSG_SIZ];
2950                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2951                         OutputChatMessage(chattingPartner, mess);
2952                         chattingPartner = -1;
2953                         next_out = i+1; // [HGM] suppress printing in ICS window
2954                     } else
2955                     if(!suppressKibitz) // [HGM] kibitz
2956                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2957                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2958                         int nrDigit = 0, nrAlph = 0, j;
2959                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2960                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2961                         parse[parse_pos] = NULLCHAR;
2962                         // try to be smart: if it does not look like search info, it should go to
2963                         // ICS interaction window after all, not to engine-output window.
2964                         for(j=0; j<parse_pos; j++) { // count letters and digits
2965                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2966                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2967                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2968                         }
2969                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2970                             int depth=0; float score;
2971                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2972                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2973                                 pvInfoList[forwardMostMove-1].depth = depth;
2974                                 pvInfoList[forwardMostMove-1].score = 100*score;
2975                             }
2976                             OutputKibitz(suppressKibitz, parse);
2977                         } else {
2978                             char tmp[MSG_SIZ];
2979                             if(gameMode == IcsObserving) // restore original ICS messages
2980                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2981                             else
2982                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2983                             SendToPlayer(tmp, strlen(tmp));
2984                         }
2985                         next_out = i+1; // [HGM] suppress printing in ICS window
2986                     }
2987                     started = STARTED_NONE;
2988                 } else {
2989                     /* Don't match patterns against characters in comment */
2990                     i++;
2991                     continue;
2992                 }
2993             }
2994             if (started == STARTED_CHATTER) {
2995                 if (buf[i] != '\n') {
2996                     /* Don't match patterns against characters in chatter */
2997                     i++;
2998                     continue;
2999                 }
3000                 started = STARTED_NONE;
3001                 if(suppressKibitz) next_out = i+1;
3002             }
3003
3004             /* Kludge to deal with rcmd protocol */
3005             if (firstTime && looking_at(buf, &i, "\001*")) {
3006                 DisplayFatalError(&buf[1], 0, 1);
3007                 continue;
3008             } else {
3009                 firstTime = FALSE;
3010             }
3011
3012             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3013                 ics_type = ICS_ICC;
3014                 ics_prefix = "/";
3015                 if (appData.debugMode)
3016                   fprintf(debugFP, "ics_type %d\n", ics_type);
3017                 continue;
3018             }
3019             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3020                 ics_type = ICS_FICS;
3021                 ics_prefix = "$";
3022                 if (appData.debugMode)
3023                   fprintf(debugFP, "ics_type %d\n", ics_type);
3024                 continue;
3025             }
3026             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3027                 ics_type = ICS_CHESSNET;
3028                 ics_prefix = "/";
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, "ics_type %d\n", ics_type);
3031                 continue;
3032             }
3033
3034             if (!loggedOn &&
3035                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3036                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3037                  looking_at(buf, &i, "will be \"*\""))) {
3038               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3039               continue;
3040             }
3041
3042             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3043               char buf[MSG_SIZ];
3044               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3045               DisplayIcsInteractionTitle(buf);
3046               have_set_title = TRUE;
3047             }
3048
3049             /* skip finger notes */
3050             if (started == STARTED_NONE &&
3051                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3052                  (buf[i] == '1' && buf[i+1] == '0')) &&
3053                 buf[i+2] == ':' && buf[i+3] == ' ') {
3054               started = STARTED_CHATTER;
3055               i += 3;
3056               continue;
3057             }
3058
3059             oldi = i;
3060             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3061             if(appData.seekGraph) {
3062                 if(soughtPending && MatchSoughtLine(buf+i)) {
3063                     i = strstr(buf+i, "rated") - buf;
3064                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                     next_out = leftover_start = i;
3066                     started = STARTED_CHATTER;
3067                     suppressKibitz = TRUE;
3068                     continue;
3069                 }
3070                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3071                         && looking_at(buf, &i, "* ads displayed")) {
3072                     soughtPending = FALSE;
3073                     seekGraphUp = TRUE;
3074                     DrawSeekGraph();
3075                     continue;
3076                 }
3077                 if(appData.autoRefresh) {
3078                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3079                         int s = (ics_type == ICS_ICC); // ICC format differs
3080                         if(seekGraphUp)
3081                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3082                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3083                         looking_at(buf, &i, "*% "); // eat prompt
3084                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3085                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086                         next_out = i; // suppress
3087                         continue;
3088                     }
3089                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3090                         char *p = star_match[0];
3091                         while(*p) {
3092                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3093                             while(*p && *p++ != ' '); // next
3094                         }
3095                         looking_at(buf, &i, "*% "); // eat prompt
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i;
3098                         continue;
3099                     }
3100                 }
3101             }
3102
3103             /* skip formula vars */
3104             if (started == STARTED_NONE &&
3105                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3106               started = STARTED_CHATTER;
3107               i += 3;
3108               continue;
3109             }
3110
3111             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3112             if (appData.autoKibitz && started == STARTED_NONE &&
3113                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3114                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3115                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3116                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3117                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3118                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3119                         suppressKibitz = TRUE;
3120                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3121                         next_out = i;
3122                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3123                                 && (gameMode == IcsPlayingWhite)) ||
3124                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3125                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3126                             started = STARTED_CHATTER; // own kibitz we simply discard
3127                         else {
3128                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3129                             parse_pos = 0; parse[0] = NULLCHAR;
3130                             savingComment = TRUE;
3131                             suppressKibitz = gameMode != IcsObserving ? 2 :
3132                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3133                         }
3134                         continue;
3135                 } else
3136                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3137                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3138                          && atoi(star_match[0])) {
3139                     // suppress the acknowledgements of our own autoKibitz
3140                     char *p;
3141                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3142                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3143                     SendToPlayer(star_match[0], strlen(star_match[0]));
3144                     if(looking_at(buf, &i, "*% ")) // eat prompt
3145                         suppressKibitz = FALSE;
3146                     next_out = i;
3147                     continue;
3148                 }
3149             } // [HGM] kibitz: end of patch
3150
3151             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3152
3153             // [HGM] chat: intercept tells by users for which we have an open chat window
3154             channel = -1;
3155             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3156                                            looking_at(buf, &i, "* whispers:") ||
3157                                            looking_at(buf, &i, "* kibitzes:") ||
3158                                            looking_at(buf, &i, "* shouts:") ||
3159                                            looking_at(buf, &i, "* c-shouts:") ||
3160                                            looking_at(buf, &i, "--> * ") ||
3161                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3162                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3163                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3165                 int p;
3166                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3167                 chattingPartner = -1;
3168
3169                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3172                     talker[0] = '['; strcat(talker, "] ");
3173                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3174                     chattingPartner = p; break;
3175                     }
3176                 } else
3177                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3178                 for(p=0; p<MAX_CHAT; p++) {
3179                     if(!strcmp("kibitzes", chatPartner[p])) {
3180                         talker[0] = '['; strcat(talker, "] ");
3181                         chattingPartner = p; break;
3182                     }
3183                 } else
3184                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3185                 for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("whispers", chatPartner[p])) {
3187                         talker[0] = '['; strcat(talker, "] ");
3188                         chattingPartner = p; break;
3189                     }
3190                 } else
3191                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3192                   if(buf[i-8] == '-' && buf[i-3] == 't')
3193                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3194                     if(!strcmp("c-shouts", chatPartner[p])) {
3195                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3196                         chattingPartner = p; break;
3197                     }
3198                   }
3199                   if(chattingPartner < 0)
3200                   for(p=0; p<MAX_CHAT; p++) {
3201                     if(!strcmp("shouts", chatPartner[p])) {
3202                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3203                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3204                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3205                         chattingPartner = p; break;
3206                     }
3207                   }
3208                 }
3209                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3210                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3211                     talker[0] = 0; Colorize(ColorTell, FALSE);
3212                     chattingPartner = p; break;
3213                 }
3214                 if(chattingPartner<0) i = oldi; else {
3215                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3216                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3217                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3218                     started = STARTED_COMMENT;
3219                     parse_pos = 0; parse[0] = NULLCHAR;
3220                     savingComment = 3 + chattingPartner; // counts as TRUE
3221                     suppressKibitz = TRUE;
3222                     continue;
3223                 }
3224             } // [HGM] chat: end of patch
3225
3226           backup = i;
3227             if (appData.zippyTalk || appData.zippyPlay) {
3228                 /* [DM] Backup address for color zippy lines */
3229 #if ZIPPY
3230                if (loggedOn == TRUE)
3231                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3232                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3233 #endif
3234             } // [DM] 'else { ' deleted
3235                 if (
3236                     /* Regular tells and says */
3237                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3238                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3239                     looking_at(buf, &i, "* says: ") ||
3240                     /* Don't color "message" or "messages" output */
3241                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3242                     looking_at(buf, &i, "*. * at *:*: ") ||
3243                     looking_at(buf, &i, "--* (*:*): ") ||
3244                     /* Message notifications (same color as tells) */
3245                     looking_at(buf, &i, "* has left a message ") ||
3246                     looking_at(buf, &i, "* just sent you a message:\n") ||
3247                     /* Whispers and kibitzes */
3248                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3249                     looking_at(buf, &i, "* kibitzes: ") ||
3250                     /* Channel tells */
3251                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3252
3253                   if (tkind == 1 && strchr(star_match[0], ':')) {
3254                       /* Avoid "tells you:" spoofs in channels */
3255                      tkind = 3;
3256                   }
3257                   if (star_match[0][0] == NULLCHAR ||
3258                       strchr(star_match[0], ' ') ||
3259                       (tkind == 3 && strchr(star_match[1], ' '))) {
3260                     /* Reject bogus matches */
3261                     i = oldi;
3262                   } else {
3263                     if (appData.colorize) {
3264                       if (oldi > next_out) {
3265                         SendToPlayer(&buf[next_out], oldi - next_out);
3266                         next_out = oldi;
3267                       }
3268                       switch (tkind) {
3269                       case 1:
3270                         Colorize(ColorTell, FALSE);
3271                         curColor = ColorTell;
3272                         break;
3273                       case 2:
3274                         Colorize(ColorKibitz, FALSE);
3275                         curColor = ColorKibitz;
3276                         break;
3277                       case 3:
3278                         p = strrchr(star_match[1], '(');
3279                         if (p == NULL) {
3280                           p = star_match[1];
3281                         } else {
3282                           p++;
3283                         }
3284                         if (atoi(p) == 1) {
3285                           Colorize(ColorChannel1, FALSE);
3286                           curColor = ColorChannel1;
3287                         } else {
3288                           Colorize(ColorChannel, FALSE);
3289                           curColor = ColorChannel;
3290                         }
3291                         break;
3292                       case 5:
3293                         curColor = ColorNormal;
3294                         break;
3295                       }
3296                     }
3297                     if (started == STARTED_NONE && appData.autoComment &&
3298                         (gameMode == IcsObserving ||
3299                          gameMode == IcsPlayingWhite ||
3300                          gameMode == IcsPlayingBlack)) {
3301                       parse_pos = i - oldi;
3302                       memcpy(parse, &buf[oldi], parse_pos);
3303                       parse[parse_pos] = NULLCHAR;
3304                       started = STARTED_COMMENT;
3305                       savingComment = TRUE;
3306                     } else {
3307                       started = STARTED_CHATTER;
3308                       savingComment = FALSE;
3309                     }
3310                     loggedOn = TRUE;
3311                     continue;
3312                   }
3313                 }
3314
3315                 if (looking_at(buf, &i, "* s-shouts: ") ||
3316                     looking_at(buf, &i, "* c-shouts: ")) {
3317                     if (appData.colorize) {
3318                         if (oldi > next_out) {
3319                             SendToPlayer(&buf[next_out], oldi - next_out);
3320                             next_out = oldi;
3321                         }
3322                         Colorize(ColorSShout, FALSE);
3323                         curColor = ColorSShout;
3324                     }
3325                     loggedOn = TRUE;
3326                     started = STARTED_CHATTER;
3327                     continue;
3328                 }
3329
3330                 if (looking_at(buf, &i, "--->")) {
3331                     loggedOn = TRUE;
3332                     continue;
3333                 }
3334
3335                 if (looking_at(buf, &i, "* shouts: ") ||
3336                     looking_at(buf, &i, "--> ")) {
3337                     if (appData.colorize) {
3338                         if (oldi > next_out) {
3339                             SendToPlayer(&buf[next_out], oldi - next_out);
3340                             next_out = oldi;
3341                         }
3342                         Colorize(ColorShout, FALSE);
3343                         curColor = ColorShout;
3344                     }
3345                     loggedOn = TRUE;
3346                     started = STARTED_CHATTER;
3347                     continue;
3348                 }
3349
3350                 if (looking_at( buf, &i, "Challenge:")) {
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorChallenge, FALSE);
3357                         curColor = ColorChallenge;
3358                     }
3359                     loggedOn = TRUE;
3360                     continue;
3361                 }
3362
3363                 if (looking_at(buf, &i, "* offers you") ||
3364                     looking_at(buf, &i, "* offers to be") ||
3365                     looking_at(buf, &i, "* would like to") ||
3366                     looking_at(buf, &i, "* requests to") ||
3367                     looking_at(buf, &i, "Your opponent offers") ||
3368                     looking_at(buf, &i, "Your opponent requests")) {
3369
3370                     if (appData.colorize) {
3371                         if (oldi > next_out) {
3372                             SendToPlayer(&buf[next_out], oldi - next_out);
3373                             next_out = oldi;
3374                         }
3375                         Colorize(ColorRequest, FALSE);
3376                         curColor = ColorRequest;
3377                     }
3378                     continue;
3379                 }
3380
3381                 if (looking_at(buf, &i, "* (*) seeking")) {
3382                     if (appData.colorize) {
3383                         if (oldi > next_out) {
3384                             SendToPlayer(&buf[next_out], oldi - next_out);
3385                             next_out = oldi;
3386                         }
3387                         Colorize(ColorSeek, FALSE);
3388                         curColor = ColorSeek;
3389                     }
3390                     continue;
3391             }
3392
3393           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3394
3395             if (looking_at(buf, &i, "\\   ")) {
3396                 if (prevColor != ColorNormal) {
3397                     if (oldi > next_out) {
3398                         SendToPlayer(&buf[next_out], oldi - next_out);
3399                         next_out = oldi;
3400                     }
3401                     Colorize(prevColor, TRUE);
3402                     curColor = prevColor;
3403                 }
3404                 if (savingComment) {
3405                     parse_pos = i - oldi;
3406                     memcpy(parse, &buf[oldi], parse_pos);
3407                     parse[parse_pos] = NULLCHAR;
3408                     started = STARTED_COMMENT;
3409                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3410                         chattingPartner = savingComment - 3; // kludge to remember the box
3411                 } else {
3412                     started = STARTED_CHATTER;
3413                 }
3414                 continue;
3415             }
3416
3417             if (looking_at(buf, &i, "Black Strength :") ||
3418                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3419                 looking_at(buf, &i, "<10>") ||
3420                 looking_at(buf, &i, "#@#")) {
3421                 /* Wrong board style */
3422                 loggedOn = TRUE;
3423                 SendToICS(ics_prefix);
3424                 SendToICS("set style 12\n");
3425                 SendToICS(ics_prefix);
3426                 SendToICS("refresh\n");
3427                 continue;
3428             }
3429
3430             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3431                 ICSInitScript();
3432                 have_sent_ICS_logon = 1;
3433                 continue;
3434             }
3435
3436             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3437                 (looking_at(buf, &i, "\n<12> ") ||
3438                  looking_at(buf, &i, "<12> "))) {
3439                 loggedOn = TRUE;
3440                 if (oldi > next_out) {
3441                     SendToPlayer(&buf[next_out], oldi - next_out);
3442                 }
3443                 next_out = i;
3444                 started = STARTED_BOARD;
3445                 parse_pos = 0;
3446                 continue;
3447             }
3448
3449             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3450                 looking_at(buf, &i, "<b1> ")) {
3451                 if (oldi > next_out) {
3452                     SendToPlayer(&buf[next_out], oldi - next_out);
3453                 }
3454                 next_out = i;
3455                 started = STARTED_HOLDINGS;
3456                 parse_pos = 0;
3457                 continue;
3458             }
3459
3460             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3461                 loggedOn = TRUE;
3462                 /* Header for a move list -- first line */
3463
3464                 switch (ics_getting_history) {
3465                   case H_FALSE:
3466                     switch (gameMode) {
3467                       case IcsIdle:
3468                       case BeginningOfGame:
3469                         /* User typed "moves" or "oldmoves" while we
3470                            were idle.  Pretend we asked for these
3471                            moves and soak them up so user can step
3472                            through them and/or save them.
3473                            */
3474                         Reset(FALSE, TRUE);
3475                         gameMode = IcsObserving;
3476                         ModeHighlight();
3477                         ics_gamenum = -1;
3478                         ics_getting_history = H_GOT_UNREQ_HEADER;
3479                         break;
3480                       case EditGame: /*?*/
3481                       case EditPosition: /*?*/
3482                         /* Should above feature work in these modes too? */
3483                         /* For now it doesn't */
3484                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3485                         break;
3486                       default:
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                     }
3490                     break;
3491                   case H_REQUESTED:
3492                     /* Is this the right one? */
3493                     if (gameInfo.white && gameInfo.black &&
3494                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3495                         strcmp(gameInfo.black, star_match[2]) == 0) {
3496                         /* All is well */
3497                         ics_getting_history = H_GOT_REQ_HEADER;
3498                     }
3499                     break;
3500                   case H_GOT_REQ_HEADER:
3501                   case H_GOT_UNREQ_HEADER:
3502                   case H_GOT_UNWANTED_HEADER:
3503                   case H_GETTING_MOVES:
3504                     /* Should not happen */
3505                     DisplayError(_("Error gathering move list: two headers"), 0);
3506                     ics_getting_history = H_FALSE;
3507                     break;
3508                 }
3509
3510                 /* Save player ratings into gameInfo if needed */
3511                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3512                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3513                     (gameInfo.whiteRating == -1 ||
3514                      gameInfo.blackRating == -1)) {
3515
3516                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3517                     gameInfo.blackRating = string_to_rating(star_match[3]);
3518                     if (appData.debugMode)
3519                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3520                               gameInfo.whiteRating, gameInfo.blackRating);
3521                 }
3522                 continue;
3523             }
3524
3525             if (looking_at(buf, &i,
3526               "* * match, initial time: * minute*, increment: * second")) {
3527                 /* Header for a move list -- second line */
3528                 /* Initial board will follow if this is a wild game */
3529                 if (gameInfo.event != NULL) free(gameInfo.event);
3530                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3531                 gameInfo.event = StrSave(str);
3532                 /* [HGM] we switched variant. Translate boards if needed. */
3533                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3534                 continue;
3535             }
3536
3537             if (looking_at(buf, &i, "Move  ")) {
3538                 /* Beginning of a move list */
3539                 switch (ics_getting_history) {
3540                   case H_FALSE:
3541                     /* Normally should not happen */
3542                     /* Maybe user hit reset while we were parsing */
3543                     break;
3544                   case H_REQUESTED:
3545                     /* Happens if we are ignoring a move list that is not
3546                      * the one we just requested.  Common if the user
3547                      * tries to observe two games without turning off
3548                      * getMoveList */
3549                     break;
3550                   case H_GETTING_MOVES:
3551                     /* Should not happen */
3552                     DisplayError(_("Error gathering move list: nested"), 0);
3553                     ics_getting_history = H_FALSE;
3554                     break;
3555                   case H_GOT_REQ_HEADER:
3556                     ics_getting_history = H_GETTING_MOVES;
3557                     started = STARTED_MOVES;
3558                     parse_pos = 0;
3559                     if (oldi > next_out) {
3560                         SendToPlayer(&buf[next_out], oldi - next_out);
3561                     }
3562                     break;
3563                   case H_GOT_UNREQ_HEADER:
3564                     ics_getting_history = H_GETTING_MOVES;
3565                     started = STARTED_MOVES_NOHIDE;
3566                     parse_pos = 0;
3567                     break;
3568                   case H_GOT_UNWANTED_HEADER:
3569                     ics_getting_history = H_FALSE;
3570                     break;
3571                 }
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "% ") ||
3576                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3577                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3578                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3579                     soughtPending = FALSE;
3580                     seekGraphUp = TRUE;
3581                     DrawSeekGraph();
3582                 }
3583                 if(suppressKibitz) next_out = i;
3584                 savingComment = FALSE;
3585                 suppressKibitz = 0;
3586                 switch (started) {
3587                   case STARTED_MOVES:
3588                   case STARTED_MOVES_NOHIDE:
3589                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3590                     parse[parse_pos + i - oldi] = NULLCHAR;
3591                     ParseGameHistory(parse);
3592 #if ZIPPY
3593                     if (appData.zippyPlay && first.initDone) {
3594                         FeedMovesToProgram(&first, forwardMostMove);
3595                         if (gameMode == IcsPlayingWhite) {
3596                             if (WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("black\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, TRUE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, TRUE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         } else if (gameMode == IcsPlayingBlack) {
3620                             if (!WhiteOnMove(forwardMostMove)) {
3621                                 if (first.sendTime) {
3622                                   if (first.useColors) {
3623                                     SendToProgram("white\n", &first);
3624                                   }
3625                                   SendTimeRemaining(&first, FALSE);
3626                                 }
3627                                 if (first.useColors) {
3628                                   SendToProgram("black\n", &first);
3629                                 }
3630                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3631                                 first.maybeThinking = TRUE;
3632                             } else {
3633                                 if (first.usePlayother) {
3634                                   if (first.sendTime) {
3635                                     SendTimeRemaining(&first, FALSE);
3636                                   }
3637                                   SendToProgram("playother\n", &first);
3638                                   firstMove = FALSE;
3639                                 } else {
3640                                   firstMove = TRUE;
3641                                 }
3642                             }
3643                         }
3644                     }
3645 #endif
3646                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3647                         /* Moves came from oldmoves or moves command
3648                            while we weren't doing anything else.
3649                            */
3650                         currentMove = forwardMostMove;
3651                         ClearHighlights();/*!!could figure this out*/
3652                         flipView = appData.flipView;
3653                         DrawPosition(TRUE, boards[currentMove]);
3654                         DisplayBothClocks();
3655                         snprintf(str, MSG_SIZ, "%s %s %s",
3656                                 gameInfo.white, _("vs."),  gameInfo.black);
3657                         DisplayTitle(str);
3658                         gameMode = IcsIdle;
3659                     } else {
3660                         /* Moves were history of an active game */
3661                         if (gameInfo.resultDetails != NULL) {
3662                             free(gameInfo.resultDetails);
3663                             gameInfo.resultDetails = NULL;
3664                         }
3665                     }
3666                     HistorySet(parseList, backwardMostMove,
3667                                forwardMostMove, currentMove-1);
3668                     DisplayMove(currentMove - 1);
3669                     if (started == STARTED_MOVES) next_out = i;
3670                     started = STARTED_NONE;
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673
3674                   case STARTED_OBSERVE:
3675                     started = STARTED_NONE;
3676                     SendToICS(ics_prefix);
3677                     SendToICS("refresh\n");
3678                     break;
3679
3680                   default:
3681                     break;
3682                 }
3683                 if(bookHit) { // [HGM] book: simulate book reply
3684                     static char bookMove[MSG_SIZ]; // a bit generous?
3685
3686                     programStats.nodes = programStats.depth = programStats.time =
3687                     programStats.score = programStats.got_only_move = 0;
3688                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3689
3690                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3691                     strcat(bookMove, bookHit);
3692                     HandleMachineMove(bookMove, &first);
3693                 }
3694                 continue;
3695             }
3696
3697             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3698                  started == STARTED_HOLDINGS ||
3699                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3700                 /* Accumulate characters in move list or board */
3701                 parse[parse_pos++] = buf[i];
3702             }
3703
3704             /* Start of game messages.  Mostly we detect start of game
3705                when the first board image arrives.  On some versions
3706                of the ICS, though, we need to do a "refresh" after starting
3707                to observe in order to get the current board right away. */
3708             if (looking_at(buf, &i, "Adding game * to observation list")) {
3709                 started = STARTED_OBSERVE;
3710                 continue;
3711             }
3712
3713             /* Handle auto-observe */
3714             if (appData.autoObserve &&
3715                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3716                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3717                 char *player;
3718                 /* Choose the player that was highlighted, if any. */
3719                 if (star_match[0][0] == '\033' ||
3720                     star_match[1][0] != '\033') {
3721                     player = star_match[0];
3722                 } else {
3723                     player = star_match[2];
3724                 }
3725                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3726                         ics_prefix, StripHighlightAndTitle(player));
3727                 SendToICS(str);
3728
3729                 /* Save ratings from notify string */
3730                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3731                 player1Rating = string_to_rating(star_match[1]);
3732                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3733                 player2Rating = string_to_rating(star_match[3]);
3734
3735                 if (appData.debugMode)
3736                   fprintf(debugFP,
3737                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3738                           player1Name, player1Rating,
3739                           player2Name, player2Rating);
3740
3741                 continue;
3742             }
3743
3744             /* Deal with automatic examine mode after a game,
3745                and with IcsObserving -> IcsExamining transition */
3746             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3747                 looking_at(buf, &i, "has made you an examiner of game *")) {
3748
3749                 int gamenum = atoi(star_match[0]);
3750                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3751                     gamenum == ics_gamenum) {
3752                     /* We were already playing or observing this game;
3753                        no need to refetch history */
3754                     gameMode = IcsExamining;
3755                     if (pausing) {
3756                         pauseExamForwardMostMove = forwardMostMove;
3757                     } else if (currentMove < forwardMostMove) {
3758                         ForwardInner(forwardMostMove);
3759                     }
3760                 } else {
3761                     /* I don't think this case really can happen */
3762                     SendToICS(ics_prefix);
3763                     SendToICS("refresh\n");
3764                 }
3765                 continue;
3766             }
3767
3768             /* Error messages */
3769 //          if (ics_user_moved) {
3770             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3771                 if (looking_at(buf, &i, "Illegal move") ||
3772                     looking_at(buf, &i, "Not a legal move") ||
3773                     looking_at(buf, &i, "Your king is in check") ||
3774                     looking_at(buf, &i, "It isn't your turn") ||
3775                     looking_at(buf, &i, "It is not your move")) {
3776                     /* Illegal move */
3777                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3778                         currentMove = forwardMostMove-1;
3779                         DisplayMove(currentMove - 1); /* before DMError */
3780                         DrawPosition(FALSE, boards[currentMove]);
3781                         SwitchClocks(forwardMostMove-1); // [HGM] race
3782                         DisplayBothClocks();
3783                     }
3784                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3785                     ics_user_moved = 0;
3786                     continue;
3787                 }
3788             }
3789
3790             if (looking_at(buf, &i, "still have time") ||
3791                 looking_at(buf, &i, "not out of time") ||
3792                 looking_at(buf, &i, "either player is out of time") ||
3793                 looking_at(buf, &i, "has timeseal; checking")) {
3794                 /* We must have called his flag a little too soon */
3795                 whiteFlag = blackFlag = FALSE;
3796                 continue;
3797             }
3798
3799             if (looking_at(buf, &i, "added * seconds to") ||
3800                 looking_at(buf, &i, "seconds were added to")) {
3801                 /* Update the clocks */
3802                 SendToICS(ics_prefix);
3803                 SendToICS("refresh\n");
3804                 continue;
3805             }
3806
3807             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3808                 ics_clock_paused = TRUE;
3809                 StopClocks();
3810                 continue;
3811             }
3812
3813             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3814                 ics_clock_paused = FALSE;
3815                 StartClocks();
3816                 continue;
3817             }
3818
3819             /* Grab player ratings from the Creating: message.
3820                Note we have to check for the special case when
3821                the ICS inserts things like [white] or [black]. */
3822             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3823                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3824                 /* star_matches:
3825                    0    player 1 name (not necessarily white)
3826                    1    player 1 rating
3827                    2    empty, white, or black (IGNORED)
3828                    3    player 2 name (not necessarily black)
3829                    4    player 2 rating
3830
3831                    The names/ratings are sorted out when the game
3832                    actually starts (below).
3833                 */
3834                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3835                 player1Rating = string_to_rating(star_match[1]);
3836                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3837                 player2Rating = string_to_rating(star_match[4]);
3838
3839                 if (appData.debugMode)
3840                   fprintf(debugFP,
3841                           "Ratings from 'Creating:' %s %d, %s %d\n",
3842                           player1Name, player1Rating,
3843                           player2Name, player2Rating);
3844
3845                 continue;
3846             }
3847
3848             /* Improved generic start/end-of-game messages */
3849             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3850                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3851                 /* If tkind == 0: */
3852                 /* star_match[0] is the game number */
3853                 /*           [1] is the white player's name */
3854                 /*           [2] is the black player's name */
3855                 /* For end-of-game: */
3856                 /*           [3] is the reason for the game end */
3857                 /*           [4] is a PGN end game-token, preceded by " " */
3858                 /* For start-of-game: */
3859                 /*           [3] begins with "Creating" or "Continuing" */
3860                 /*           [4] is " *" or empty (don't care). */
3861                 int gamenum = atoi(star_match[0]);
3862                 char *whitename, *blackname, *why, *endtoken;
3863                 ChessMove endtype = EndOfFile;
3864
3865                 if (tkind == 0) {
3866                   whitename = star_match[1];
3867                   blackname = star_match[2];
3868                   why = star_match[3];
3869                   endtoken = star_match[4];
3870                 } else {
3871                   whitename = star_match[1];
3872                   blackname = star_match[3];
3873                   why = star_match[5];
3874                   endtoken = star_match[6];
3875                 }
3876
3877                 /* Game start messages */
3878                 if (strncmp(why, "Creating ", 9) == 0 ||
3879                     strncmp(why, "Continuing ", 11) == 0) {
3880                     gs_gamenum = gamenum;
3881                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3882                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3883                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3884 #if ZIPPY
3885                     if (appData.zippyPlay) {
3886                         ZippyGameStart(whitename, blackname);
3887                     }
3888 #endif /*ZIPPY*/
3889                     partnerBoardValid = FALSE; // [HGM] bughouse
3890                     continue;
3891                 }
3892
3893                 /* Game end messages */
3894                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3895                     ics_gamenum != gamenum) {
3896                     continue;
3897                 }
3898                 while (endtoken[0] == ' ') endtoken++;
3899                 switch (endtoken[0]) {
3900                   case '*':
3901                   default:
3902                     endtype = GameUnfinished;
3903                     break;
3904                   case '0':
3905                     endtype = BlackWins;
3906                     break;
3907                   case '1':
3908                     if (endtoken[1] == '/')
3909                       endtype = GameIsDrawn;
3910                     else
3911                       endtype = WhiteWins;
3912                     break;
3913                 }
3914                 GameEnds(endtype, why, GE_ICS);
3915 #if ZIPPY
3916                 if (appData.zippyPlay && first.initDone) {
3917                     ZippyGameEnd(endtype, why);
3918                     if (first.pr == NoProc) {
3919                       /* Start the next process early so that we'll
3920                          be ready for the next challenge */
3921                       StartChessProgram(&first);
3922                     }
3923                     /* Send "new" early, in case this command takes
3924                        a long time to finish, so that we'll be ready
3925                        for the next challenge. */
3926                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3927                     Reset(TRUE, TRUE);
3928                 }
3929 #endif /*ZIPPY*/
3930                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "Removing game * from observation") ||
3935                 looking_at(buf, &i, "no longer observing game *") ||
3936                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3937                 if (gameMode == IcsObserving &&
3938                     atoi(star_match[0]) == ics_gamenum)
3939                   {
3940                       /* icsEngineAnalyze */
3941                       if (appData.icsEngineAnalyze) {
3942                             ExitAnalyzeMode();
3943                             ModeHighlight();
3944                       }
3945                       StopClocks();
3946                       gameMode = IcsIdle;
3947                       ics_gamenum = -1;
3948                       ics_user_moved = FALSE;
3949                   }
3950                 continue;
3951             }
3952
3953             if (looking_at(buf, &i, "no longer examining game *")) {
3954                 if (gameMode == IcsExamining &&
3955                     atoi(star_match[0]) == ics_gamenum)
3956                   {
3957                       gameMode = IcsIdle;
3958                       ics_gamenum = -1;
3959                       ics_user_moved = FALSE;
3960                   }
3961                 continue;
3962             }
3963
3964             /* Advance leftover_start past any newlines we find,
3965                so only partial lines can get reparsed */
3966             if (looking_at(buf, &i, "\n")) {
3967                 prevColor = curColor;
3968                 if (curColor != ColorNormal) {
3969                     if (oldi > next_out) {
3970                         SendToPlayer(&buf[next_out], oldi - next_out);
3971                         next_out = oldi;
3972                     }
3973                     Colorize(ColorNormal, FALSE);
3974                     curColor = ColorNormal;
3975                 }
3976                 if (started == STARTED_BOARD) {
3977                     started = STARTED_NONE;
3978                     parse[parse_pos] = NULLCHAR;
3979                     ParseBoard12(parse);
3980                     ics_user_moved = 0;
3981
3982                     /* Send premove here */
3983                     if (appData.premove) {
3984                       char str[MSG_SIZ];
3985                       if (currentMove == 0 &&
3986                           gameMode == IcsPlayingWhite &&
3987                           appData.premoveWhite) {
3988                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3989                         if (appData.debugMode)
3990                           fprintf(debugFP, "Sending premove:\n");
3991                         SendToICS(str);
3992                       } else if (currentMove == 1 &&
3993                                  gameMode == IcsPlayingBlack &&
3994                                  appData.premoveBlack) {
3995                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3996                         if (appData.debugMode)
3997                           fprintf(debugFP, "Sending premove:\n");
3998                         SendToICS(str);
3999                       } else if (gotPremove) {
4000                         gotPremove = 0;
4001                         ClearPremoveHighlights();
4002                         if (appData.debugMode)
4003                           fprintf(debugFP, "Sending premove:\n");
4004                           UserMoveEvent(premoveFromX, premoveFromY,
4005                                         premoveToX, premoveToY,
4006                                         premovePromoChar);
4007                       }
4008                     }
4009
4010                     /* Usually suppress following prompt */
4011                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4012                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4013                         if (looking_at(buf, &i, "*% ")) {
4014                             savingComment = FALSE;
4015                             suppressKibitz = 0;
4016                         }
4017                     }
4018                     next_out = i;
4019                 } else if (started == STARTED_HOLDINGS) {
4020                     int gamenum;
4021                     char new_piece[MSG_SIZ];
4022                     started = STARTED_NONE;
4023                     parse[parse_pos] = NULLCHAR;
4024                     if (appData.debugMode)
4025                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4026                                                         parse, currentMove);
4027                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4028                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4029                         if (gameInfo.variant == VariantNormal) {
4030                           /* [HGM] We seem to switch variant during a game!
4031                            * Presumably no holdings were displayed, so we have
4032                            * to move the position two files to the right to
4033                            * create room for them!
4034                            */
4035                           VariantClass newVariant;
4036                           switch(gameInfo.boardWidth) { // base guess on board width
4037                                 case 9:  newVariant = VariantShogi; break;
4038                                 case 10: newVariant = VariantGreat; break;
4039                                 default: newVariant = VariantCrazyhouse; break;
4040                           }
4041                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4042                           /* Get a move list just to see the header, which
4043                              will tell us whether this is really bug or zh */
4044                           if (ics_getting_history == H_FALSE) {
4045                             ics_getting_history = H_REQUESTED;
4046                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047                             SendToICS(str);
4048                           }
4049                         }
4050                         new_piece[0] = NULLCHAR;
4051                         sscanf(parse, "game %d white [%s black [%s <- %s",
4052                                &gamenum, white_holding, black_holding,
4053                                new_piece);
4054                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4055                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4056                         /* [HGM] copy holdings to board holdings area */
4057                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4058                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4059                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4060 #if ZIPPY
4061                         if (appData.zippyPlay && first.initDone) {
4062                             ZippyHoldings(white_holding, black_holding,
4063                                           new_piece);
4064                         }
4065 #endif /*ZIPPY*/
4066                         if (tinyLayout || smallLayout) {
4067                             char wh[16], bh[16];
4068                             PackHolding(wh, white_holding);
4069                             PackHolding(bh, black_holding);
4070                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4071                                     gameInfo.white, gameInfo.black);
4072                         } else {
4073                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4074                                     gameInfo.white, white_holding, _("vs."),
4075                                     gameInfo.black, black_holding);
4076                         }
4077                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4078                         DrawPosition(FALSE, boards[currentMove]);
4079                         DisplayTitle(str);
4080                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4081                         sscanf(parse, "game %d white [%s black [%s <- %s",
4082                                &gamenum, white_holding, black_holding,
4083                                new_piece);
4084                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4085                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4086                         /* [HGM] copy holdings to partner-board holdings area */
4087                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4088                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4089                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4090                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4091                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4092                       }
4093                     }
4094                     /* Suppress following prompt */
4095                     if (looking_at(buf, &i, "*% ")) {
4096                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4097                         savingComment = FALSE;
4098                         suppressKibitz = 0;
4099                     }
4100                     next_out = i;
4101                 }
4102                 continue;
4103             }
4104
4105             i++;                /* skip unparsed character and loop back */
4106         }
4107
4108         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4109 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4110 //          SendToPlayer(&buf[next_out], i - next_out);
4111             started != STARTED_HOLDINGS && leftover_start > next_out) {
4112             SendToPlayer(&buf[next_out], leftover_start - next_out);
4113             next_out = i;
4114         }
4115
4116         leftover_len = buf_len - leftover_start;
4117         /* if buffer ends with something we couldn't parse,
4118            reparse it after appending the next read */
4119
4120     } else if (count == 0) {
4121         RemoveInputSource(isr);
4122         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4123     } else {
4124         DisplayFatalError(_("Error reading from ICS"), error, 1);
4125     }
4126 }
4127
4128
4129 /* Board style 12 looks like this:
4130
4131    <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
4132
4133  * The "<12> " is stripped before it gets to this routine.  The two
4134  * trailing 0's (flip state and clock ticking) are later addition, and
4135  * some chess servers may not have them, or may have only the first.
4136  * Additional trailing fields may be added in the future.
4137  */
4138
4139 #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"
4140
4141 #define RELATION_OBSERVING_PLAYED    0
4142 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4143 #define RELATION_PLAYING_MYMOVE      1
4144 #define RELATION_PLAYING_NOTMYMOVE  -1
4145 #define RELATION_EXAMINING           2
4146 #define RELATION_ISOLATED_BOARD     -3
4147 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4148
4149 void
4150 ParseBoard12 (char *string)
4151 {
4152     GameMode newGameMode;
4153     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4154     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4155     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4156     char to_play, board_chars[200];
4157     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4158     char black[32], white[32];
4159     Board board;
4160     int prevMove = currentMove;
4161     int ticking = 2;
4162     ChessMove moveType;
4163     int fromX, fromY, toX, toY;
4164     char promoChar;
4165     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4166     char *bookHit = NULL; // [HGM] book
4167     Boolean weird = FALSE, reqFlag = FALSE;
4168
4169     fromX = fromY = toX = toY = -1;
4170
4171     newGame = FALSE;
4172
4173     if (appData.debugMode)
4174       fprintf(debugFP, _("Parsing board: %s\n"), string);
4175
4176     move_str[0] = NULLCHAR;
4177     elapsed_time[0] = NULLCHAR;
4178     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4179         int  i = 0, j;
4180         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4181             if(string[i] == ' ') { ranks++; files = 0; }
4182             else files++;
4183             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4184             i++;
4185         }
4186         for(j = 0; j <i; j++) board_chars[j] = string[j];
4187         board_chars[i] = '\0';
4188         string += i + 1;
4189     }
4190     n = sscanf(string, PATTERN, &to_play, &double_push,
4191                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4192                &gamenum, white, black, &relation, &basetime, &increment,
4193                &white_stren, &black_stren, &white_time, &black_time,
4194                &moveNum, str, elapsed_time, move_str, &ics_flip,
4195                &ticking);
4196
4197     if (n < 21) {
4198         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4199         DisplayError(str, 0);
4200         return;
4201     }
4202
4203     /* Convert the move number to internal form */
4204     moveNum = (moveNum - 1) * 2;
4205     if (to_play == 'B') moveNum++;
4206     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4207       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4208                         0, 1);
4209       return;
4210     }
4211
4212     switch (relation) {
4213       case RELATION_OBSERVING_PLAYED:
4214       case RELATION_OBSERVING_STATIC:
4215         if (gamenum == -1) {
4216             /* Old ICC buglet */
4217             relation = RELATION_OBSERVING_STATIC;
4218         }
4219         newGameMode = IcsObserving;
4220         break;
4221       case RELATION_PLAYING_MYMOVE:
4222       case RELATION_PLAYING_NOTMYMOVE:
4223         newGameMode =
4224           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4225             IcsPlayingWhite : IcsPlayingBlack;
4226         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4227         break;
4228       case RELATION_EXAMINING:
4229         newGameMode = IcsExamining;
4230         break;
4231       case RELATION_ISOLATED_BOARD:
4232       default:
4233         /* Just display this board.  If user was doing something else,
4234            we will forget about it until the next board comes. */
4235         newGameMode = IcsIdle;
4236         break;
4237       case RELATION_STARTING_POSITION:
4238         newGameMode = gameMode;
4239         break;
4240     }
4241
4242     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4243          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4244       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4245       char *toSqr;
4246       for (k = 0; k < ranks; k++) {
4247         for (j = 0; j < files; j++)
4248           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4249         if(gameInfo.holdingsWidth > 1) {
4250              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4251              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4252         }
4253       }
4254       CopyBoard(partnerBoard, board);
4255       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4256         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4257         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4258       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4259       if(toSqr = strchr(str, '-')) {
4260         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4261         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4262       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4263       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4264       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4265       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4266       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4267       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4268                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4269       DisplayMessage(partnerStatus, "");
4270         partnerBoardValid = TRUE;
4271       return;
4272     }
4273
4274     /* Modify behavior for initial board display on move listing
4275        of wild games.
4276        */
4277     switch (ics_getting_history) {
4278       case H_FALSE:
4279       case H_REQUESTED:
4280         break;
4281       case H_GOT_REQ_HEADER:
4282       case H_GOT_UNREQ_HEADER:
4283         /* This is the initial position of the current game */
4284         gamenum = ics_gamenum;
4285         moveNum = 0;            /* old ICS bug workaround */
4286         if (to_play == 'B') {
4287           startedFromSetupPosition = TRUE;
4288           blackPlaysFirst = TRUE;
4289           moveNum = 1;
4290           if (forwardMostMove == 0) forwardMostMove = 1;
4291           if (backwardMostMove == 0) backwardMostMove = 1;
4292           if (currentMove == 0) currentMove = 1;
4293         }
4294         newGameMode = gameMode;
4295         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4296         break;
4297       case H_GOT_UNWANTED_HEADER:
4298         /* This is an initial board that we don't want */
4299         return;
4300       case H_GETTING_MOVES:
4301         /* Should not happen */
4302         DisplayError(_("Error gathering move list: extra board"), 0);
4303         ics_getting_history = H_FALSE;
4304         return;
4305     }
4306
4307    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4308                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4309      /* [HGM] We seem to have switched variant unexpectedly
4310       * Try to guess new variant from board size
4311       */
4312           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4313           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4314           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4315           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4316           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4317           if(!weird) newVariant = VariantNormal;
4318           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4319           /* Get a move list just to see the header, which
4320              will tell us whether this is really bug or zh */
4321           if (ics_getting_history == H_FALSE) {
4322             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4323             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4324             SendToICS(str);
4325           }
4326     }
4327
4328     /* Take action if this is the first board of a new game, or of a
4329        different game than is currently being displayed.  */
4330     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4331         relation == RELATION_ISOLATED_BOARD) {
4332
4333         /* Forget the old game and get the history (if any) of the new one */
4334         if (gameMode != BeginningOfGame) {
4335           Reset(TRUE, TRUE);
4336         }
4337         newGame = TRUE;
4338         if (appData.autoRaiseBoard) BoardToTop();
4339         prevMove = -3;
4340         if (gamenum == -1) {
4341             newGameMode = IcsIdle;
4342         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4343                    appData.getMoveList && !reqFlag) {
4344             /* Need to get game history */
4345             ics_getting_history = H_REQUESTED;
4346             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4347             SendToICS(str);
4348         }
4349
4350         /* Initially flip the board to have black on the bottom if playing
4351            black or if the ICS flip flag is set, but let the user change
4352            it with the Flip View button. */
4353         flipView = appData.autoFlipView ?
4354           (newGameMode == IcsPlayingBlack) || ics_flip :
4355           appData.flipView;
4356
4357         /* Done with values from previous mode; copy in new ones */
4358         gameMode = newGameMode;
4359         ModeHighlight();
4360         ics_gamenum = gamenum;
4361         if (gamenum == gs_gamenum) {
4362             int klen = strlen(gs_kind);
4363             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4364             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4365             gameInfo.event = StrSave(str);
4366         } else {
4367             gameInfo.event = StrSave("ICS game");
4368         }
4369         gameInfo.site = StrSave(appData.icsHost);
4370         gameInfo.date = PGNDate();
4371         gameInfo.round = StrSave("-");
4372         gameInfo.white = StrSave(white);
4373         gameInfo.black = StrSave(black);
4374         timeControl = basetime * 60 * 1000;
4375         timeControl_2 = 0;
4376         timeIncrement = increment * 1000;
4377         movesPerSession = 0;
4378         gameInfo.timeControl = TimeControlTagValue();
4379         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4380   if (appData.debugMode) {
4381     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4382     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4383     setbuf(debugFP, NULL);
4384   }
4385
4386         gameInfo.outOfBook = NULL;
4387
4388         /* Do we have the ratings? */
4389         if (strcmp(player1Name, white) == 0 &&
4390             strcmp(player2Name, black) == 0) {
4391             if (appData.debugMode)
4392               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4393                       player1Rating, player2Rating);
4394             gameInfo.whiteRating = player1Rating;
4395             gameInfo.blackRating = player2Rating;
4396         } else if (strcmp(player2Name, white) == 0 &&
4397                    strcmp(player1Name, black) == 0) {
4398             if (appData.debugMode)
4399               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4400                       player2Rating, player1Rating);
4401             gameInfo.whiteRating = player2Rating;
4402             gameInfo.blackRating = player1Rating;
4403         }
4404         player1Name[0] = player2Name[0] = NULLCHAR;
4405
4406         /* Silence shouts if requested */
4407         if (appData.quietPlay &&
4408             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4409             SendToICS(ics_prefix);
4410             SendToICS("set shout 0\n");
4411         }
4412     }
4413
4414     /* Deal with midgame name changes */
4415     if (!newGame) {
4416         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4417             if (gameInfo.white) free(gameInfo.white);
4418             gameInfo.white = StrSave(white);
4419         }
4420         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4421             if (gameInfo.black) free(gameInfo.black);
4422             gameInfo.black = StrSave(black);
4423         }
4424     }
4425
4426     /* Throw away game result if anything actually changes in examine mode */
4427     if (gameMode == IcsExamining && !newGame) {
4428         gameInfo.result = GameUnfinished;
4429         if (gameInfo.resultDetails != NULL) {
4430             free(gameInfo.resultDetails);
4431             gameInfo.resultDetails = NULL;
4432         }
4433     }
4434
4435     /* In pausing && IcsExamining mode, we ignore boards coming
4436        in if they are in a different variation than we are. */
4437     if (pauseExamInvalid) return;
4438     if (pausing && gameMode == IcsExamining) {
4439         if (moveNum <= pauseExamForwardMostMove) {
4440             pauseExamInvalid = TRUE;
4441             forwardMostMove = pauseExamForwardMostMove;
4442             return;
4443         }
4444     }
4445
4446   if (appData.debugMode) {
4447     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4448   }
4449     /* Parse the board */
4450     for (k = 0; k < ranks; k++) {
4451       for (j = 0; j < files; j++)
4452         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4453       if(gameInfo.holdingsWidth > 1) {
4454            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4455            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4456       }
4457     }
4458     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4459       board[5][BOARD_RGHT+1] = WhiteAngel;
4460       board[6][BOARD_RGHT+1] = WhiteMarshall;
4461       board[1][0] = BlackMarshall;
4462       board[2][0] = BlackAngel;
4463       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4464     }
4465     CopyBoard(boards[moveNum], board);
4466     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4467     if (moveNum == 0) {
4468         startedFromSetupPosition =
4469           !CompareBoards(board, initialPosition);
4470         if(startedFromSetupPosition)
4471             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4472     }
4473
4474     /* [HGM] Set castling rights. Take the outermost Rooks,
4475        to make it also work for FRC opening positions. Note that board12
4476        is really defective for later FRC positions, as it has no way to
4477        indicate which Rook can castle if they are on the same side of King.
4478        For the initial position we grant rights to the outermost Rooks,
4479        and remember thos rights, and we then copy them on positions
4480        later in an FRC game. This means WB might not recognize castlings with
4481        Rooks that have moved back to their original position as illegal,
4482        but in ICS mode that is not its job anyway.
4483     */
4484     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4485     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4486
4487         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4488             if(board[0][i] == WhiteRook) j = i;
4489         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4490         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4491             if(board[0][i] == WhiteRook) j = i;
4492         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4493         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4494             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4495         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4496         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4497             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4498         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4499
4500         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4501         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4502         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4503             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4504         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4505             if(board[BOARD_HEIGHT-1][k] == bKing)
4506                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4507         if(gameInfo.variant == VariantTwoKings) {
4508             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4509             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4510             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4511         }
4512     } else { int r;
4513         r = boards[moveNum][CASTLING][0] = initialRights[0];
4514         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4515         r = boards[moveNum][CASTLING][1] = initialRights[1];
4516         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4517         r = boards[moveNum][CASTLING][3] = initialRights[3];
4518         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4519         r = boards[moveNum][CASTLING][4] = initialRights[4];
4520         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4521         /* wildcastle kludge: always assume King has rights */
4522         r = boards[moveNum][CASTLING][2] = initialRights[2];
4523         r = boards[moveNum][CASTLING][5] = initialRights[5];
4524     }
4525     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4526     boards[moveNum][EP_STATUS] = EP_NONE;
4527     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4528     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4529     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4530
4531
4532     if (ics_getting_history == H_GOT_REQ_HEADER ||
4533         ics_getting_history == H_GOT_UNREQ_HEADER) {
4534         /* This was an initial position from a move list, not
4535            the current position */
4536         return;
4537     }
4538
4539     /* Update currentMove and known move number limits */
4540     newMove = newGame || moveNum > forwardMostMove;
4541
4542     if (newGame) {
4543         forwardMostMove = backwardMostMove = currentMove = moveNum;
4544         if (gameMode == IcsExamining && moveNum == 0) {
4545           /* Workaround for ICS limitation: we are not told the wild
4546              type when starting to examine a game.  But if we ask for
4547              the move list, the move list header will tell us */
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4553                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4554 #if ZIPPY
4555         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4556         /* [HGM] applied this also to an engine that is silently watching        */
4557         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4558             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4559             gameInfo.variant == currentlyInitializedVariant) {
4560           takeback = forwardMostMove - moveNum;
4561           for (i = 0; i < takeback; i++) {
4562             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4563             SendToProgram("undo\n", &first);
4564           }
4565         }
4566 #endif
4567
4568         forwardMostMove = moveNum;
4569         if (!pausing || currentMove > forwardMostMove)
4570           currentMove = forwardMostMove;
4571     } else {
4572         /* New part of history that is not contiguous with old part */
4573         if (pausing && gameMode == IcsExamining) {
4574             pauseExamInvalid = TRUE;
4575             forwardMostMove = pauseExamForwardMostMove;
4576             return;
4577         }
4578         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4579 #if ZIPPY
4580             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4581                 // [HGM] when we will receive the move list we now request, it will be
4582                 // fed to the engine from the first move on. So if the engine is not
4583                 // in the initial position now, bring it there.
4584                 InitChessProgram(&first, 0);
4585             }
4586 #endif
4587             ics_getting_history = H_REQUESTED;
4588             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4589             SendToICS(str);
4590         }
4591         forwardMostMove = backwardMostMove = currentMove = moveNum;
4592     }
4593
4594     /* Update the clocks */
4595     if (strchr(elapsed_time, '.')) {
4596       /* Time is in ms */
4597       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4598       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4599     } else {
4600       /* Time is in seconds */
4601       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4602       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4603     }
4604
4605
4606 #if ZIPPY
4607     if (appData.zippyPlay && newGame &&
4608         gameMode != IcsObserving && gameMode != IcsIdle &&
4609         gameMode != IcsExamining)
4610       ZippyFirstBoard(moveNum, basetime, increment);
4611 #endif
4612
4613     /* Put the move on the move list, first converting
4614        to canonical algebraic form. */
4615     if (moveNum > 0) {
4616   if (appData.debugMode) {
4617     if (appData.debugMode) { int f = forwardMostMove;
4618         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4619                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4620                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4621     }
4622     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4623     fprintf(debugFP, "moveNum = %d\n", moveNum);
4624     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4625     setbuf(debugFP, NULL);
4626   }
4627         if (moveNum <= backwardMostMove) {
4628             /* We don't know what the board looked like before
4629                this move.  Punt. */
4630           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4631             strcat(parseList[moveNum - 1], " ");
4632             strcat(parseList[moveNum - 1], elapsed_time);
4633             moveList[moveNum - 1][0] = NULLCHAR;
4634         } else if (strcmp(move_str, "none") == 0) {
4635             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4636             /* Again, we don't know what the board looked like;
4637                this is really the start of the game. */
4638             parseList[moveNum - 1][0] = NULLCHAR;
4639             moveList[moveNum - 1][0] = NULLCHAR;
4640             backwardMostMove = moveNum;
4641             startedFromSetupPosition = TRUE;
4642             fromX = fromY = toX = toY = -1;
4643         } else {
4644           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4645           //                 So we parse the long-algebraic move string in stead of the SAN move
4646           int valid; char buf[MSG_SIZ], *prom;
4647
4648           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4649                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4650           // str looks something like "Q/a1-a2"; kill the slash
4651           if(str[1] == '/')
4652             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4653           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4654           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4655                 strcat(buf, prom); // long move lacks promo specification!
4656           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4657                 if(appData.debugMode)
4658                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4659                 safeStrCpy(move_str, buf, MSG_SIZ);
4660           }
4661           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4662                                 &fromX, &fromY, &toX, &toY, &promoChar)
4663                || ParseOneMove(buf, moveNum - 1, &moveType,
4664                                 &fromX, &fromY, &toX, &toY, &promoChar);
4665           // end of long SAN patch
4666           if (valid) {
4667             (void) CoordsToAlgebraic(boards[moveNum - 1],
4668                                      PosFlags(moveNum - 1),
4669                                      fromY, fromX, toY, toX, promoChar,
4670                                      parseList[moveNum-1]);
4671             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4672               case MT_NONE:
4673               case MT_STALEMATE:
4674               default:
4675                 break;
4676               case MT_CHECK:
4677                 if(gameInfo.variant != VariantShogi)
4678                     strcat(parseList[moveNum - 1], "+");
4679                 break;
4680               case MT_CHECKMATE:
4681               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4682                 strcat(parseList[moveNum - 1], "#");
4683                 break;
4684             }
4685             strcat(parseList[moveNum - 1], " ");
4686             strcat(parseList[moveNum - 1], elapsed_time);
4687             /* currentMoveString is set as a side-effect of ParseOneMove */
4688             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4689             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4690             strcat(moveList[moveNum - 1], "\n");
4691
4692             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4693                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4694               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4695                 ChessSquare old, new = boards[moveNum][k][j];
4696                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4697                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4698                   if(old == new) continue;
4699                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4700                   else if(new == WhiteWazir || new == BlackWazir) {
4701                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4702                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4703                       else boards[moveNum][k][j] = old; // preserve type of Gold
4704                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4705                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4706               }
4707           } else {
4708             /* Move from ICS was illegal!?  Punt. */
4709             if (appData.debugMode) {
4710               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4711               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4712             }
4713             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4714             strcat(parseList[moveNum - 1], " ");
4715             strcat(parseList[moveNum - 1], elapsed_time);
4716             moveList[moveNum - 1][0] = NULLCHAR;
4717             fromX = fromY = toX = toY = -1;
4718           }
4719         }
4720   if (appData.debugMode) {
4721     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4722     setbuf(debugFP, NULL);
4723   }
4724
4725 #if ZIPPY
4726         /* Send move to chess program (BEFORE animating it). */
4727         if (appData.zippyPlay && !newGame && newMove &&
4728            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4729
4730             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4731                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4732                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4733                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4734                             move_str);
4735                     DisplayError(str, 0);
4736                 } else {
4737                     if (first.sendTime) {
4738                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4739                     }
4740                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4741                     if (firstMove && !bookHit) {
4742                         firstMove = FALSE;
4743                         if (first.useColors) {
4744                           SendToProgram(gameMode == IcsPlayingWhite ?
4745                                         "white\ngo\n" :
4746                                         "black\ngo\n", &first);
4747                         } else {
4748                           SendToProgram("go\n", &first);
4749                         }
4750                         first.maybeThinking = TRUE;
4751                     }
4752                 }
4753             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4754               if (moveList[moveNum - 1][0] == NULLCHAR) {
4755                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4756                 DisplayError(str, 0);
4757               } else {
4758                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4759                 SendMoveToProgram(moveNum - 1, &first);
4760               }
4761             }
4762         }
4763 #endif
4764     }
4765
4766     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4767         /* If move comes from a remote source, animate it.  If it
4768            isn't remote, it will have already been animated. */
4769         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4770             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4771         }
4772         if (!pausing && appData.highlightLastMove) {
4773             SetHighlights(fromX, fromY, toX, toY);
4774         }
4775     }
4776
4777     /* Start the clocks */
4778     whiteFlag = blackFlag = FALSE;
4779     appData.clockMode = !(basetime == 0 && increment == 0);
4780     if (ticking == 0) {
4781       ics_clock_paused = TRUE;
4782       StopClocks();
4783     } else if (ticking == 1) {
4784       ics_clock_paused = FALSE;
4785     }
4786     if (gameMode == IcsIdle ||
4787         relation == RELATION_OBSERVING_STATIC ||
4788         relation == RELATION_EXAMINING ||
4789         ics_clock_paused)
4790       DisplayBothClocks();
4791     else
4792       StartClocks();
4793
4794     /* Display opponents and material strengths */
4795     if (gameInfo.variant != VariantBughouse &&
4796         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4797         if (tinyLayout || smallLayout) {
4798             if(gameInfo.variant == VariantNormal)
4799               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4800                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4801                     basetime, increment);
4802             else
4803               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4804                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4805                     basetime, increment, (int) gameInfo.variant);
4806         } else {
4807             if(gameInfo.variant == VariantNormal)
4808               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4809                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4810                     basetime, increment);
4811             else
4812               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4813                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4814                     basetime, increment, VariantName(gameInfo.variant));
4815         }
4816         DisplayTitle(str);
4817   if (appData.debugMode) {
4818     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4819   }
4820     }
4821
4822
4823     /* Display the board */
4824     if (!pausing && !appData.noGUI) {
4825
4826       if (appData.premove)
4827           if (!gotPremove ||
4828              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4829              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4830               ClearPremoveHighlights();
4831
4832       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4833         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4834       DrawPosition(j, boards[currentMove]);
4835
4836       DisplayMove(moveNum - 1);
4837       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4838             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4839               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4840         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4841       }
4842     }
4843
4844     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4845 #if ZIPPY
4846     if(bookHit) { // [HGM] book: simulate book reply
4847         static char bookMove[MSG_SIZ]; // a bit generous?
4848
4849         programStats.nodes = programStats.depth = programStats.time =
4850         programStats.score = programStats.got_only_move = 0;
4851         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4852
4853         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4854         strcat(bookMove, bookHit);
4855         HandleMachineMove(bookMove, &first);
4856     }
4857 #endif
4858 }
4859
4860 void
4861 GetMoveListEvent ()
4862 {
4863     char buf[MSG_SIZ];
4864     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4865         ics_getting_history = H_REQUESTED;
4866         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4867         SendToICS(buf);
4868     }
4869 }
4870
4871 void
4872 AnalysisPeriodicEvent (int force)
4873 {
4874     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4875          && !force) || !appData.periodicUpdates)
4876       return;
4877
4878     /* Send . command to Crafty to collect stats */
4879     SendToProgram(".\n", &first);
4880
4881     /* Don't send another until we get a response (this makes
4882        us stop sending to old Crafty's which don't understand
4883        the "." command (sending illegal cmds resets node count & time,
4884        which looks bad)) */
4885     programStats.ok_to_send = 0;
4886 }
4887
4888 void
4889 ics_update_width (int new_width)
4890 {
4891         ics_printf("set width %d\n", new_width);
4892 }
4893
4894 void
4895 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4896 {
4897     char buf[MSG_SIZ];
4898
4899     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4900         // null move in variant where engine does not understand it (for analysis purposes)
4901         SendBoard(cps, moveNum + 1); // send position after move in stead.
4902         return;
4903     }
4904     if (cps->useUsermove) {
4905       SendToProgram("usermove ", cps);
4906     }
4907     if (cps->useSAN) {
4908       char *space;
4909       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4910         int len = space - parseList[moveNum];
4911         memcpy(buf, parseList[moveNum], len);
4912         buf[len++] = '\n';
4913         buf[len] = NULLCHAR;
4914       } else {
4915         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4916       }
4917       SendToProgram(buf, cps);
4918     } else {
4919       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4920         AlphaRank(moveList[moveNum], 4);
4921         SendToProgram(moveList[moveNum], cps);
4922         AlphaRank(moveList[moveNum], 4); // and back
4923       } else
4924       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4925        * the engine. It would be nice to have a better way to identify castle
4926        * moves here. */
4927       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4928                                                                          && cps->useOOCastle) {
4929         int fromX = moveList[moveNum][0] - AAA;
4930         int fromY = moveList[moveNum][1] - ONE;
4931         int toX = moveList[moveNum][2] - AAA;
4932         int toY = moveList[moveNum][3] - ONE;
4933         if((boards[moveNum][fromY][fromX] == WhiteKing
4934             && boards[moveNum][toY][toX] == WhiteRook)
4935            || (boards[moveNum][fromY][fromX] == BlackKing
4936                && boards[moveNum][toY][toX] == BlackRook)) {
4937           if(toX > fromX) SendToProgram("O-O\n", cps);
4938           else SendToProgram("O-O-O\n", cps);
4939         }
4940         else SendToProgram(moveList[moveNum], cps);
4941       } else
4942       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4943         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4944           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4945           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4946                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4947         } else
4948           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4949                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4950         SendToProgram(buf, cps);
4951       }
4952       else SendToProgram(moveList[moveNum], cps);
4953       /* End of additions by Tord */
4954     }
4955
4956     /* [HGM] setting up the opening has brought engine in force mode! */
4957     /*       Send 'go' if we are in a mode where machine should play. */
4958     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4959         (gameMode == TwoMachinesPlay   ||
4960 #if ZIPPY
4961          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4962 #endif
4963          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4964         SendToProgram("go\n", cps);
4965   if (appData.debugMode) {
4966     fprintf(debugFP, "(extra)\n");
4967   }
4968     }
4969     setboardSpoiledMachineBlack = 0;
4970 }
4971
4972 void
4973 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4974 {
4975     char user_move[MSG_SIZ];
4976     char suffix[4];
4977
4978     if(gameInfo.variant == VariantSChess && promoChar) {
4979         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4980         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4981     } else suffix[0] = NULLCHAR;
4982
4983     switch (moveType) {
4984       default:
4985         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4986                 (int)moveType, fromX, fromY, toX, toY);
4987         DisplayError(user_move + strlen("say "), 0);
4988         break;
4989       case WhiteKingSideCastle:
4990       case BlackKingSideCastle:
4991       case WhiteQueenSideCastleWild:
4992       case BlackQueenSideCastleWild:
4993       /* PUSH Fabien */
4994       case WhiteHSideCastleFR:
4995       case BlackHSideCastleFR:
4996       /* POP Fabien */
4997         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4998         break;
4999       case WhiteQueenSideCastle:
5000       case BlackQueenSideCastle:
5001       case WhiteKingSideCastleWild:
5002       case BlackKingSideCastleWild:
5003       /* PUSH Fabien */
5004       case WhiteASideCastleFR:
5005       case BlackASideCastleFR:
5006       /* POP Fabien */
5007         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5008         break;
5009       case WhiteNonPromotion:
5010       case BlackNonPromotion:
5011         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5012         break;
5013       case WhitePromotion:
5014       case BlackPromotion:
5015         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5016           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5017                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5018                 PieceToChar(WhiteFerz));
5019         else if(gameInfo.variant == VariantGreat)
5020           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5021                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5022                 PieceToChar(WhiteMan));
5023         else
5024           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5026                 promoChar);
5027         break;
5028       case WhiteDrop:
5029       case BlackDrop:
5030       drop:
5031         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5032                  ToUpper(PieceToChar((ChessSquare) fromX)),
5033                  AAA + toX, ONE + toY);
5034         break;
5035       case IllegalMove:  /* could be a variant we don't quite understand */
5036         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5037       case NormalMove:
5038       case WhiteCapturesEnPassant:
5039       case BlackCapturesEnPassant:
5040         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5041                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5042         break;
5043     }
5044     SendToICS(user_move);
5045     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5046         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5047 }
5048
5049 void
5050 UploadGameEvent ()
5051 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5052     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5053     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5054     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5055       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5056       return;
5057     }
5058     if(gameMode != IcsExamining) { // is this ever not the case?
5059         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5060
5061         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5062           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5063         } else { // on FICS we must first go to general examine mode
5064           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5065         }
5066         if(gameInfo.variant != VariantNormal) {
5067             // try figure out wild number, as xboard names are not always valid on ICS
5068             for(i=1; i<=36; i++) {
5069               snprintf(buf, MSG_SIZ, "wild/%d", i);
5070                 if(StringToVariant(buf) == gameInfo.variant) break;
5071             }
5072             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5073             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5074             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5075         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5076         SendToICS(ics_prefix);
5077         SendToICS(buf);
5078         if(startedFromSetupPosition || backwardMostMove != 0) {
5079           fen = PositionToFEN(backwardMostMove, NULL);
5080           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5081             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5082             SendToICS(buf);
5083           } else { // FICS: everything has to set by separate bsetup commands
5084             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5085             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5086             SendToICS(buf);
5087             if(!WhiteOnMove(backwardMostMove)) {
5088                 SendToICS("bsetup tomove black\n");
5089             }
5090             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5091             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5092             SendToICS(buf);
5093             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5094             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5095             SendToICS(buf);
5096             i = boards[backwardMostMove][EP_STATUS];
5097             if(i >= 0) { // set e.p.
5098               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5099                 SendToICS(buf);
5100             }
5101             bsetup++;
5102           }
5103         }
5104       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5105             SendToICS("bsetup done\n"); // switch to normal examining.
5106     }
5107     for(i = backwardMostMove; i<last; i++) {
5108         char buf[20];
5109         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5110         SendToICS(buf);
5111     }
5112     SendToICS(ics_prefix);
5113     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5114 }
5115
5116 void
5117 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5118 {
5119     if (rf == DROP_RANK) {
5120       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5121       sprintf(move, "%c@%c%c\n",
5122                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5123     } else {
5124         if (promoChar == 'x' || promoChar == NULLCHAR) {
5125           sprintf(move, "%c%c%c%c\n",
5126                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5127         } else {
5128             sprintf(move, "%c%c%c%c%c\n",
5129                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5130         }
5131     }
5132 }
5133
5134 void
5135 ProcessICSInitScript (FILE *f)
5136 {
5137     char buf[MSG_SIZ];
5138
5139     while (fgets(buf, MSG_SIZ, f)) {
5140         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5141     }
5142
5143     fclose(f);
5144 }
5145
5146
5147 static int lastX, lastY, selectFlag, dragging;
5148
5149 void
5150 Sweep (int step)
5151 {
5152     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5153     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5154     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5155     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5156     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5157     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5158     do {
5159         promoSweep -= step;
5160         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5161         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5162         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5163         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5164         if(!step) step = -1;
5165     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5166             appData.testLegality && (promoSweep == king ||
5167             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5168     ChangeDragPiece(promoSweep);
5169 }
5170
5171 int
5172 PromoScroll (int x, int y)
5173 {
5174   int step = 0;
5175
5176   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5177   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5178   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5179   if(!step) return FALSE;
5180   lastX = x; lastY = y;
5181   if((promoSweep < BlackPawn) == flipView) step = -step;
5182   if(step > 0) selectFlag = 1;
5183   if(!selectFlag) Sweep(step);
5184   return FALSE;
5185 }
5186
5187 void
5188 NextPiece (int step)
5189 {
5190     ChessSquare piece = boards[currentMove][toY][toX];
5191     do {
5192         pieceSweep -= step;
5193         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5194         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5195         if(!step) step = -1;
5196     } while(PieceToChar(pieceSweep) == '.');
5197     boards[currentMove][toY][toX] = pieceSweep;
5198     DrawPosition(FALSE, boards[currentMove]);
5199     boards[currentMove][toY][toX] = piece;
5200 }
5201 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5202 void
5203 AlphaRank (char *move, int n)
5204 {
5205 //    char *p = move, c; int x, y;
5206
5207     if (appData.debugMode) {
5208         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5209     }
5210
5211     if(move[1]=='*' &&
5212        move[2]>='0' && move[2]<='9' &&
5213        move[3]>='a' && move[3]<='x'    ) {
5214         move[1] = '@';
5215         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5216         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5217     } else
5218     if(move[0]>='0' && move[0]<='9' &&
5219        move[1]>='a' && move[1]<='x' &&
5220        move[2]>='0' && move[2]<='9' &&
5221        move[3]>='a' && move[3]<='x'    ) {
5222         /* input move, Shogi -> normal */
5223         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5224         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5225         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5226         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5227     } else
5228     if(move[1]=='@' &&
5229        move[3]>='0' && move[3]<='9' &&
5230        move[2]>='a' && move[2]<='x'    ) {
5231         move[1] = '*';
5232         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5233         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5234     } else
5235     if(
5236        move[0]>='a' && move[0]<='x' &&
5237        move[3]>='0' && move[3]<='9' &&
5238        move[2]>='a' && move[2]<='x'    ) {
5239          /* output move, normal -> Shogi */
5240         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5241         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5242         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5243         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5244         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5245     }
5246     if (appData.debugMode) {
5247         fprintf(debugFP, "   out = '%s'\n", move);
5248     }
5249 }
5250
5251 char yy_textstr[8000];
5252
5253 /* Parser for moves from gnuchess, ICS, or user typein box */
5254 Boolean
5255 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5256 {
5257     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5258
5259     switch (*moveType) {
5260       case WhitePromotion:
5261       case BlackPromotion:
5262       case WhiteNonPromotion:
5263       case BlackNonPromotion:
5264       case NormalMove:
5265       case WhiteCapturesEnPassant:
5266       case BlackCapturesEnPassant:
5267       case WhiteKingSideCastle:
5268       case WhiteQueenSideCastle:
5269       case BlackKingSideCastle:
5270       case BlackQueenSideCastle:
5271       case WhiteKingSideCastleWild:
5272       case WhiteQueenSideCastleWild:
5273       case BlackKingSideCastleWild:
5274       case BlackQueenSideCastleWild:
5275       /* Code added by Tord: */
5276       case WhiteHSideCastleFR:
5277       case WhiteASideCastleFR:
5278       case BlackHSideCastleFR:
5279       case BlackASideCastleFR:
5280       /* End of code added by Tord */
5281       case IllegalMove:         /* bug or odd chess variant */
5282         *fromX = currentMoveString[0] - AAA;
5283         *fromY = currentMoveString[1] - ONE;
5284         *toX = currentMoveString[2] - AAA;
5285         *toY = currentMoveString[3] - ONE;
5286         *promoChar = currentMoveString[4];
5287         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5288             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5289     if (appData.debugMode) {
5290         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5291     }
5292             *fromX = *fromY = *toX = *toY = 0;
5293             return FALSE;
5294         }
5295         if (appData.testLegality) {
5296           return (*moveType != IllegalMove);
5297         } else {
5298           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5299                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5300         }
5301
5302       case WhiteDrop:
5303       case BlackDrop:
5304         *fromX = *moveType == WhiteDrop ?
5305           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5306           (int) CharToPiece(ToLower(currentMoveString[0]));
5307         *fromY = DROP_RANK;
5308         *toX = currentMoveString[2] - AAA;
5309         *toY = currentMoveString[3] - ONE;
5310         *promoChar = NULLCHAR;
5311         return TRUE;
5312
5313       case AmbiguousMove:
5314       case ImpossibleMove:
5315       case EndOfFile:
5316       case ElapsedTime:
5317       case Comment:
5318       case PGNTag:
5319       case NAG:
5320       case WhiteWins:
5321       case BlackWins:
5322       case GameIsDrawn:
5323       default:
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5326     }
5327         /* bug? */
5328         *fromX = *fromY = *toX = *toY = 0;
5329         *promoChar = NULLCHAR;
5330         return FALSE;
5331     }
5332 }
5333
5334 Boolean pushed = FALSE;
5335 char *lastParseAttempt;
5336
5337 void
5338 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5339 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5340   int fromX, fromY, toX, toY; char promoChar;
5341   ChessMove moveType;
5342   Boolean valid;
5343   int nr = 0;
5344
5345   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5346     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5347     pushed = TRUE;
5348   }
5349   endPV = forwardMostMove;
5350   do {
5351     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5352     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5353     lastParseAttempt = pv;
5354     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5355     if(!valid && nr == 0 &&
5356        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5357         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5358         // Hande case where played move is different from leading PV move
5359         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5360         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5361         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5362         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5363           endPV += 2; // if position different, keep this
5364           moveList[endPV-1][0] = fromX + AAA;
5365           moveList[endPV-1][1] = fromY + ONE;
5366           moveList[endPV-1][2] = toX + AAA;
5367           moveList[endPV-1][3] = toY + ONE;
5368           parseList[endPV-1][0] = NULLCHAR;
5369           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5370         }
5371       }
5372     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5373     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5374     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5375     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5376         valid++; // allow comments in PV
5377         continue;
5378     }
5379     nr++;
5380     if(endPV+1 > framePtr) break; // no space, truncate
5381     if(!valid) break;
5382     endPV++;
5383     CopyBoard(boards[endPV], boards[endPV-1]);
5384     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5385     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5386     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5387     CoordsToAlgebraic(boards[endPV - 1],
5388                              PosFlags(endPV - 1),
5389                              fromY, fromX, toY, toX, promoChar,
5390                              parseList[endPV - 1]);
5391   } while(valid);
5392   if(atEnd == 2) return; // used hidden, for PV conversion
5393   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5394   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5395   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5396                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5397   DrawPosition(TRUE, boards[currentMove]);
5398 }
5399
5400 int
5401 MultiPV (ChessProgramState *cps)
5402 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5403         int i;
5404         for(i=0; i<cps->nrOptions; i++)
5405             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5406                 return i;
5407         return -1;
5408 }
5409
5410 Boolean
5411 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5412 {
5413         int startPV, multi, lineStart, origIndex = index;
5414         char *p, buf2[MSG_SIZ];
5415
5416         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5417         lastX = x; lastY = y;
5418         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5419         lineStart = startPV = index;
5420         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5421         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5422         index = startPV;
5423         do{ while(buf[index] && buf[index] != '\n') index++;
5424         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5425         buf[index] = 0;
5426         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5427                 int n = first.option[multi].value;
5428                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5429                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5430                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5431                 first.option[multi].value = n;
5432                 *start = *end = 0;
5433                 return FALSE;
5434         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5435                 ExcludeClick(origIndex - lineStart);
5436                 return FALSE;
5437         }
5438         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5439         *start = startPV; *end = index-1;
5440         return TRUE;
5441 }
5442
5443 char *
5444 PvToSAN (char *pv)
5445 {
5446         static char buf[10*MSG_SIZ];
5447         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5448         *buf = NULLCHAR;
5449         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5450         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5451         for(i = forwardMostMove; i<endPV; i++){
5452             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5453             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5454             k += strlen(buf+k);
5455         }
5456         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5457         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5458         endPV = savedEnd;
5459         return buf;
5460 }
5461
5462 Boolean
5463 LoadPV (int x, int y)
5464 { // called on right mouse click to load PV
5465   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5466   lastX = x; lastY = y;
5467   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5468   return TRUE;
5469 }
5470
5471 void
5472 UnLoadPV ()
5473 {
5474   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5475   if(endPV < 0) return;
5476   if(appData.autoCopyPV) CopyFENToClipboard();
5477   endPV = -1;
5478   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5479         Boolean saveAnimate = appData.animate;
5480         if(pushed) {
5481             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5482                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5483             } else storedGames--; // abandon shelved tail of original game
5484         }
5485         pushed = FALSE;
5486         forwardMostMove = currentMove;
5487         currentMove = oldFMM;
5488         appData.animate = FALSE;
5489         ToNrEvent(forwardMostMove);
5490         appData.animate = saveAnimate;
5491   }
5492   currentMove = forwardMostMove;
5493   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5494   ClearPremoveHighlights();
5495   DrawPosition(TRUE, boards[currentMove]);
5496 }
5497
5498 void
5499 MovePV (int x, int y, int h)
5500 { // step through PV based on mouse coordinates (called on mouse move)
5501   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5502
5503   // we must somehow check if right button is still down (might be released off board!)
5504   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5505   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5506   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5507   if(!step) return;
5508   lastX = x; lastY = y;
5509
5510   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5511   if(endPV < 0) return;
5512   if(y < margin) step = 1; else
5513   if(y > h - margin) step = -1;
5514   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5515   currentMove += step;
5516   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5517   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5518                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5519   DrawPosition(FALSE, boards[currentMove]);
5520 }
5521
5522
5523 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5524 // All positions will have equal probability, but the current method will not provide a unique
5525 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5526 #define DARK 1
5527 #define LITE 2
5528 #define ANY 3
5529
5530 int squaresLeft[4];
5531 int piecesLeft[(int)BlackPawn];
5532 int seed, nrOfShuffles;
5533
5534 void
5535 GetPositionNumber ()
5536 {       // sets global variable seed
5537         int i;
5538
5539         seed = appData.defaultFrcPosition;
5540         if(seed < 0) { // randomize based on time for negative FRC position numbers
5541                 for(i=0; i<50; i++) seed += random();
5542                 seed = random() ^ random() >> 8 ^ random() << 8;
5543                 if(seed<0) seed = -seed;
5544         }
5545 }
5546
5547 int
5548 put (Board board, int pieceType, int rank, int n, int shade)
5549 // put the piece on the (n-1)-th empty squares of the given shade
5550 {
5551         int i;
5552
5553         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5554                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5555                         board[rank][i] = (ChessSquare) pieceType;
5556                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5557                         squaresLeft[ANY]--;
5558                         piecesLeft[pieceType]--;
5559                         return i;
5560                 }
5561         }
5562         return -1;
5563 }
5564
5565
5566 void
5567 AddOnePiece (Board board, int pieceType, int rank, int shade)
5568 // calculate where the next piece goes, (any empty square), and put it there
5569 {
5570         int i;
5571
5572         i = seed % squaresLeft[shade];
5573         nrOfShuffles *= squaresLeft[shade];
5574         seed /= squaresLeft[shade];
5575         put(board, pieceType, rank, i, shade);
5576 }
5577
5578 void
5579 AddTwoPieces (Board board, int pieceType, int rank)
5580 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5581 {
5582         int i, n=squaresLeft[ANY], j=n-1, k;
5583
5584         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5585         i = seed % k;  // pick one
5586         nrOfShuffles *= k;
5587         seed /= k;
5588         while(i >= j) i -= j--;
5589         j = n - 1 - j; i += j;
5590         put(board, pieceType, rank, j, ANY);
5591         put(board, pieceType, rank, i, ANY);
5592 }
5593
5594 void
5595 SetUpShuffle (Board board, int number)
5596 {
5597         int i, p, first=1;
5598
5599         GetPositionNumber(); nrOfShuffles = 1;
5600
5601         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5602         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5603         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5604
5605         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5606
5607         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5608             p = (int) board[0][i];
5609             if(p < (int) BlackPawn) piecesLeft[p] ++;
5610             board[0][i] = EmptySquare;
5611         }
5612
5613         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5614             // shuffles restricted to allow normal castling put KRR first
5615             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5616                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5617             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5618                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5619             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5620                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5621             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5622                 put(board, WhiteRook, 0, 0, ANY);
5623             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5624         }
5625
5626         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5627             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5628             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5629                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5630                 while(piecesLeft[p] >= 2) {
5631                     AddOnePiece(board, p, 0, LITE);
5632                     AddOnePiece(board, p, 0, DARK);
5633                 }
5634                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5635             }
5636
5637         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5638             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5639             // but we leave King and Rooks for last, to possibly obey FRC restriction
5640             if(p == (int)WhiteRook) continue;
5641             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5642             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5643         }
5644
5645         // now everything is placed, except perhaps King (Unicorn) and Rooks
5646
5647         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5648             // Last King gets castling rights
5649             while(piecesLeft[(int)WhiteUnicorn]) {
5650                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5651                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5652             }
5653
5654             while(piecesLeft[(int)WhiteKing]) {
5655                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5657             }
5658
5659
5660         } else {
5661             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5662             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5663         }
5664
5665         // Only Rooks can be left; simply place them all
5666         while(piecesLeft[(int)WhiteRook]) {
5667                 i = put(board, WhiteRook, 0, 0, ANY);
5668                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5669                         if(first) {
5670                                 first=0;
5671                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5672                         }
5673                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5674                 }
5675         }
5676         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5677             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5678         }
5679
5680         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5681 }
5682
5683 int
5684 SetCharTable (char *table, const char * map)
5685 /* [HGM] moved here from winboard.c because of its general usefulness */
5686 /*       Basically a safe strcpy that uses the last character as King */
5687 {
5688     int result = FALSE; int NrPieces;
5689
5690     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5691                     && NrPieces >= 12 && !(NrPieces&1)) {
5692         int i; /* [HGM] Accept even length from 12 to 34 */
5693
5694         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5695         for( i=0; i<NrPieces/2-1; i++ ) {
5696             table[i] = map[i];
5697             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5698         }
5699         table[(int) WhiteKing]  = map[NrPieces/2-1];
5700         table[(int) BlackKing]  = map[NrPieces-1];
5701
5702         result = TRUE;
5703     }
5704
5705     return result;
5706 }
5707
5708 void
5709 Prelude (Board board)
5710 {       // [HGM] superchess: random selection of exo-pieces
5711         int i, j, k; ChessSquare p;
5712         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5713
5714         GetPositionNumber(); // use FRC position number
5715
5716         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5717             SetCharTable(pieceToChar, appData.pieceToCharTable);
5718             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5719                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5720         }
5721
5722         j = seed%4;                 seed /= 4;
5723         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5724         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5725         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5726         j = seed%3 + (seed%3 >= j); seed /= 3;
5727         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5728         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5729         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5730         j = seed%3;                 seed /= 3;
5731         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%2 + (seed%2 >= j); seed /= 2;
5735         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5736         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5737         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5738         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5739         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5740         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5741         put(board, exoPieces[0],    0, 0, ANY);
5742         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5743 }
5744
5745 void
5746 InitPosition (int redraw)
5747 {
5748     ChessSquare (* pieces)[BOARD_FILES];
5749     int i, j, pawnRow, overrule,
5750     oldx = gameInfo.boardWidth,
5751     oldy = gameInfo.boardHeight,
5752     oldh = gameInfo.holdingsWidth;
5753     static int oldv;
5754
5755     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5756
5757     /* [AS] Initialize pv info list [HGM] and game status */
5758     {
5759         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5760             pvInfoList[i].depth = 0;
5761             boards[i][EP_STATUS] = EP_NONE;
5762             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5763         }
5764
5765         initialRulePlies = 0; /* 50-move counter start */
5766
5767         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5768         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5769     }
5770
5771
5772     /* [HGM] logic here is completely changed. In stead of full positions */
5773     /* the initialized data only consist of the two backranks. The switch */
5774     /* selects which one we will use, which is than copied to the Board   */
5775     /* initialPosition, which for the rest is initialized by Pawns and    */
5776     /* empty squares. This initial position is then copied to boards[0],  */
5777     /* possibly after shuffling, so that it remains available.            */
5778
5779     gameInfo.holdingsWidth = 0; /* default board sizes */
5780     gameInfo.boardWidth    = 8;
5781     gameInfo.boardHeight   = 8;
5782     gameInfo.holdingsSize  = 0;
5783     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5784     for(i=0; i<BOARD_FILES-2; i++)
5785       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5786     initialPosition[EP_STATUS] = EP_NONE;
5787     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5788     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5789          SetCharTable(pieceNickName, appData.pieceNickNames);
5790     else SetCharTable(pieceNickName, "............");
5791     pieces = FIDEArray;
5792
5793     switch (gameInfo.variant) {
5794     case VariantFischeRandom:
5795       shuffleOpenings = TRUE;
5796     default:
5797       break;
5798     case VariantShatranj:
5799       pieces = ShatranjArray;
5800       nrCastlingRights = 0;
5801       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5802       break;
5803     case VariantMakruk:
5804       pieces = makrukArray;
5805       nrCastlingRights = 0;
5806       startedFromSetupPosition = TRUE;
5807       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5808       break;
5809     case VariantTwoKings:
5810       pieces = twoKingsArray;
5811       break;
5812     case VariantGrand:
5813       pieces = GrandArray;
5814       nrCastlingRights = 0;
5815       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5816       gameInfo.boardWidth = 10;
5817       gameInfo.boardHeight = 10;
5818       gameInfo.holdingsSize = 7;
5819       break;
5820     case VariantCapaRandom:
5821       shuffleOpenings = TRUE;
5822     case VariantCapablanca:
5823       pieces = CapablancaArray;
5824       gameInfo.boardWidth = 10;
5825       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5826       break;
5827     case VariantGothic:
5828       pieces = GothicArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5831       break;
5832     case VariantSChess:
5833       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5834       gameInfo.holdingsSize = 7;
5835       break;
5836     case VariantJanus:
5837       pieces = JanusArray;
5838       gameInfo.boardWidth = 10;
5839       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5840       nrCastlingRights = 6;
5841         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5842         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5843         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5844         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5845         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5846         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5847       break;
5848     case VariantFalcon:
5849       pieces = FalconArray;
5850       gameInfo.boardWidth = 10;
5851       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5852       break;
5853     case VariantXiangqi:
5854       pieces = XiangqiArray;
5855       gameInfo.boardWidth  = 9;
5856       gameInfo.boardHeight = 10;
5857       nrCastlingRights = 0;
5858       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5859       break;
5860     case VariantShogi:
5861       pieces = ShogiArray;
5862       gameInfo.boardWidth  = 9;
5863       gameInfo.boardHeight = 9;
5864       gameInfo.holdingsSize = 7;
5865       nrCastlingRights = 0;
5866       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5867       break;
5868     case VariantCourier:
5869       pieces = CourierArray;
5870       gameInfo.boardWidth  = 12;
5871       nrCastlingRights = 0;
5872       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5873       break;
5874     case VariantKnightmate:
5875       pieces = KnightmateArray;
5876       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5877       break;
5878     case VariantSpartan:
5879       pieces = SpartanArray;
5880       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5881       break;
5882     case VariantFairy:
5883       pieces = fairyArray;
5884       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5885       break;
5886     case VariantGreat:
5887       pieces = GreatArray;
5888       gameInfo.boardWidth = 10;
5889       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5890       gameInfo.holdingsSize = 8;
5891       break;
5892     case VariantSuper:
5893       pieces = FIDEArray;
5894       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5895       gameInfo.holdingsSize = 8;
5896       startedFromSetupPosition = TRUE;
5897       break;
5898     case VariantCrazyhouse:
5899     case VariantBughouse:
5900       pieces = FIDEArray;
5901       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5902       gameInfo.holdingsSize = 5;
5903       break;
5904     case VariantWildCastle:
5905       pieces = FIDEArray;
5906       /* !!?shuffle with kings guaranteed to be on d or e file */
5907       shuffleOpenings = 1;
5908       break;
5909     case VariantNoCastle:
5910       pieces = FIDEArray;
5911       nrCastlingRights = 0;
5912       /* !!?unconstrained back-rank shuffle */
5913       shuffleOpenings = 1;
5914       break;
5915     }
5916
5917     overrule = 0;
5918     if(appData.NrFiles >= 0) {
5919         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5920         gameInfo.boardWidth = appData.NrFiles;
5921     }
5922     if(appData.NrRanks >= 0) {
5923         gameInfo.boardHeight = appData.NrRanks;
5924     }
5925     if(appData.holdingsSize >= 0) {
5926         i = appData.holdingsSize;
5927         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5928         gameInfo.holdingsSize = i;
5929     }
5930     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5931     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5932         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5933
5934     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5935     if(pawnRow < 1) pawnRow = 1;
5936     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5937
5938     /* User pieceToChar list overrules defaults */
5939     if(appData.pieceToCharTable != NULL)
5940         SetCharTable(pieceToChar, appData.pieceToCharTable);
5941
5942     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5943
5944         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5945             s = (ChessSquare) 0; /* account holding counts in guard band */
5946         for( i=0; i<BOARD_HEIGHT; i++ )
5947             initialPosition[i][j] = s;
5948
5949         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5950         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5951         initialPosition[pawnRow][j] = WhitePawn;
5952         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5953         if(gameInfo.variant == VariantXiangqi) {
5954             if(j&1) {
5955                 initialPosition[pawnRow][j] =
5956                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5957                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5958                    initialPosition[2][j] = WhiteCannon;
5959                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5960                 }
5961             }
5962         }
5963         if(gameInfo.variant == VariantGrand) {
5964             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5965                initialPosition[0][j] = WhiteRook;
5966                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5967             }
5968         }
5969         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5970     }
5971     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5972
5973             j=BOARD_LEFT+1;
5974             initialPosition[1][j] = WhiteBishop;
5975             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5976             j=BOARD_RGHT-2;
5977             initialPosition[1][j] = WhiteRook;
5978             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5979     }
5980
5981     if( nrCastlingRights == -1) {
5982         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5983         /*       This sets default castling rights from none to normal corners   */
5984         /* Variants with other castling rights must set them themselves above    */
5985         nrCastlingRights = 6;
5986
5987         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5988         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5989         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5990         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5991         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5992         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5993      }
5994
5995      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5996      if(gameInfo.variant == VariantGreat) { // promotion commoners
5997         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5998         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5999         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6000         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6001      }
6002      if( gameInfo.variant == VariantSChess ) {
6003       initialPosition[1][0] = BlackMarshall;
6004       initialPosition[2][0] = BlackAngel;
6005       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6006       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6007       initialPosition[1][1] = initialPosition[2][1] = 
6008       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6009      }
6010   if (appData.debugMode) {
6011     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6012   }
6013     if(shuffleOpenings) {
6014         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6015         startedFromSetupPosition = TRUE;
6016     }
6017     if(startedFromPositionFile) {
6018       /* [HGM] loadPos: use PositionFile for every new game */
6019       CopyBoard(initialPosition, filePosition);
6020       for(i=0; i<nrCastlingRights; i++)
6021           initialRights[i] = filePosition[CASTLING][i];
6022       startedFromSetupPosition = TRUE;
6023     }
6024
6025     CopyBoard(boards[0], initialPosition);
6026
6027     if(oldx != gameInfo.boardWidth ||
6028        oldy != gameInfo.boardHeight ||
6029        oldv != gameInfo.variant ||
6030        oldh != gameInfo.holdingsWidth
6031                                          )
6032             InitDrawingSizes(-2 ,0);
6033
6034     oldv = gameInfo.variant;
6035     if (redraw)
6036       DrawPosition(TRUE, boards[currentMove]);
6037 }
6038
6039 void
6040 SendBoard (ChessProgramState *cps, int moveNum)
6041 {
6042     char message[MSG_SIZ];
6043
6044     if (cps->useSetboard) {
6045       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6046       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6047       SendToProgram(message, cps);
6048       free(fen);
6049
6050     } else {
6051       ChessSquare *bp;
6052       int i, j, left=0, right=BOARD_WIDTH;
6053       /* Kludge to set black to move, avoiding the troublesome and now
6054        * deprecated "black" command.
6055        */
6056       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6057         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6058
6059       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6060
6061       SendToProgram("edit\n", cps);
6062       SendToProgram("#\n", cps);
6063       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6064         bp = &boards[moveNum][i][left];
6065         for (j = left; j < right; j++, bp++) {
6066           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6067           if ((int) *bp < (int) BlackPawn) {
6068             if(j == BOARD_RGHT+1)
6069                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6070             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6071             if(message[0] == '+' || message[0] == '~') {
6072               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6074                         AAA + j, ONE + i);
6075             }
6076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6077                 message[1] = BOARD_RGHT   - 1 - j + '1';
6078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6079             }
6080             SendToProgram(message, cps);
6081           }
6082         }
6083       }
6084
6085       SendToProgram("c\n", cps);
6086       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6087         bp = &boards[moveNum][i][left];
6088         for (j = left; j < right; j++, bp++) {
6089           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6090           if (((int) *bp != (int) EmptySquare)
6091               && ((int) *bp >= (int) BlackPawn)) {
6092             if(j == BOARD_LEFT-2)
6093                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6094             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6095                     AAA + j, ONE + i);
6096             if(message[0] == '+' || message[0] == '~') {
6097               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6098                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6099                         AAA + j, ONE + i);
6100             }
6101             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6102                 message[1] = BOARD_RGHT   - 1 - j + '1';
6103                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6104             }
6105             SendToProgram(message, cps);
6106           }
6107         }
6108       }
6109
6110       SendToProgram(".\n", cps);
6111     }
6112     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6113 }
6114
6115 char exclusionHeader[MSG_SIZ];
6116 int exCnt, excludePtr;
6117 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6118 static Exclusion excluTab[200];
6119 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6120
6121 static void
6122 WriteMap (int s)
6123 {
6124     int j;
6125     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6126     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6127 }
6128
6129 static void
6130 ClearMap ()
6131 {
6132     int j;
6133     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6134     excludePtr = 24; exCnt = 0;
6135     WriteMap(0);
6136 }
6137
6138 static void
6139 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6140 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6141     char buf[2*MOVE_LEN], *p;
6142     Exclusion *e = excluTab;
6143     int i;
6144     for(i=0; i<exCnt; i++)
6145         if(e[i].ff == fromX && e[i].fr == fromY &&
6146            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6147     if(i == exCnt) { // was not in exclude list; add it
6148         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6149         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6150             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6151             return; // abort
6152         }
6153         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6154         excludePtr++; e[i].mark = excludePtr++;
6155         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6156         exCnt++;
6157     }
6158     exclusionHeader[e[i].mark] = state;
6159 }
6160
6161 static int
6162 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6163 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6164     char *p, buf[MSG_SIZ];
6165     int j, k;
6166     ChessMove moveType;
6167     if(promoChar == -1) { // kludge to indicate best move
6168         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6169             return 1; // if unparsable, abort
6170     }
6171     // update exclusion map (resolving toggle by consulting existing state)
6172     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6173     j = k%8; k >>= 3;
6174     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6175     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6176          excludeMap[k] |=   1<<j;
6177     else excludeMap[k] &= ~(1<<j);
6178     // update header
6179     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6180     // inform engine
6181     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6182     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6183     SendToProgram(buf, &first);
6184     return (state == '+');
6185 }
6186
6187 static void
6188 ExcludeClick (int index)
6189 {
6190     int i, j;
6191     char buf[MSG_SIZ];
6192     Exclusion *e = excluTab;
6193     if(index < 25) { // none, best or tail clicked
6194         if(index < 13) { // none: include all
6195             WriteMap(0); // clear map
6196             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6197             SendToProgram("include all\n", &first); // and inform engine
6198         } else if(index > 18) { // tail
6199             if(exclusionHeader[19] == '-') { // tail was excluded
6200                 SendToProgram("include all\n", &first);
6201                 WriteMap(0); // clear map completely
6202                 // now re-exclude selected moves
6203                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6204                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6205             } else { // tail was included or in mixed state
6206                 SendToProgram("exclude all\n", &first);
6207                 WriteMap(0xFF); // fill map completely
6208                 // now re-include selected moves
6209                 j = 0; // count them
6210                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6211                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6212                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6213             }
6214         } else { // best
6215             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6216         }
6217     } else {
6218         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6219             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6220             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6221             break;
6222         }
6223     }
6224 }
6225
6226 ChessSquare
6227 DefaultPromoChoice (int white)
6228 {
6229     ChessSquare result;
6230     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6231         result = WhiteFerz; // no choice
6232     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6233         result= WhiteKing; // in Suicide Q is the last thing we want
6234     else if(gameInfo.variant == VariantSpartan)
6235         result = white ? WhiteQueen : WhiteAngel;
6236     else result = WhiteQueen;
6237     if(!white) result = WHITE_TO_BLACK result;
6238     return result;
6239 }
6240
6241 static int autoQueen; // [HGM] oneclick
6242
6243 int
6244 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6245 {
6246     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6247     /* [HGM] add Shogi promotions */
6248     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6249     ChessSquare piece;
6250     ChessMove moveType;
6251     Boolean premove;
6252
6253     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6254     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6255
6256     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6257       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6258         return FALSE;
6259
6260     piece = boards[currentMove][fromY][fromX];
6261     if(gameInfo.variant == VariantShogi) {
6262         promotionZoneSize = BOARD_HEIGHT/3;
6263         highestPromotingPiece = (int)WhiteFerz;
6264     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6265         promotionZoneSize = 3;
6266     }
6267
6268     // Treat Lance as Pawn when it is not representing Amazon
6269     if(gameInfo.variant != VariantSuper) {
6270         if(piece == WhiteLance) piece = WhitePawn; else
6271         if(piece == BlackLance) piece = BlackPawn;
6272     }
6273
6274     // next weed out all moves that do not touch the promotion zone at all
6275     if((int)piece >= BlackPawn) {
6276         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6277              return FALSE;
6278         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6279     } else {
6280         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6281            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6282     }
6283
6284     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6285
6286     // weed out mandatory Shogi promotions
6287     if(gameInfo.variant == VariantShogi) {
6288         if(piece >= BlackPawn) {
6289             if(toY == 0 && piece == BlackPawn ||
6290                toY == 0 && piece == BlackQueen ||
6291                toY <= 1 && piece == BlackKnight) {
6292                 *promoChoice = '+';
6293                 return FALSE;
6294             }
6295         } else {
6296             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6297                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6298                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6299                 *promoChoice = '+';
6300                 return FALSE;
6301             }
6302         }
6303     }
6304
6305     // weed out obviously illegal Pawn moves
6306     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6307         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6308         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6309         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6310         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6311         // note we are not allowed to test for valid (non-)capture, due to premove
6312     }
6313
6314     // we either have a choice what to promote to, or (in Shogi) whether to promote
6315     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6316         *promoChoice = PieceToChar(BlackFerz);  // no choice
6317         return FALSE;
6318     }
6319     // no sense asking what we must promote to if it is going to explode...
6320     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6321         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6322         return FALSE;
6323     }
6324     // give caller the default choice even if we will not make it
6325     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6326     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6327     if(        sweepSelect && gameInfo.variant != VariantGreat
6328                            && gameInfo.variant != VariantGrand
6329                            && gameInfo.variant != VariantSuper) return FALSE;
6330     if(autoQueen) return FALSE; // predetermined
6331
6332     // suppress promotion popup on illegal moves that are not premoves
6333     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6334               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6335     if(appData.testLegality && !premove) {
6336         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6337                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6338         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6339             return FALSE;
6340     }
6341
6342     return TRUE;
6343 }
6344
6345 int
6346 InPalace (int row, int column)
6347 {   /* [HGM] for Xiangqi */
6348     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6349          column < (BOARD_WIDTH + 4)/2 &&
6350          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6351     return FALSE;
6352 }
6353
6354 int
6355 PieceForSquare (int x, int y)
6356 {
6357   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6358      return -1;
6359   else
6360      return boards[currentMove][y][x];
6361 }
6362
6363 int
6364 OKToStartUserMove (int x, int y)
6365 {
6366     ChessSquare from_piece;
6367     int white_piece;
6368
6369     if (matchMode) return FALSE;
6370     if (gameMode == EditPosition) return TRUE;
6371
6372     if (x >= 0 && y >= 0)
6373       from_piece = boards[currentMove][y][x];
6374     else
6375       from_piece = EmptySquare;
6376
6377     if (from_piece == EmptySquare) return FALSE;
6378
6379     white_piece = (int)from_piece >= (int)WhitePawn &&
6380       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6381
6382     switch (gameMode) {
6383       case AnalyzeFile:
6384       case TwoMachinesPlay:
6385       case EndOfGame:
6386         return FALSE;
6387
6388       case IcsObserving:
6389       case IcsIdle:
6390         return FALSE;
6391
6392       case MachinePlaysWhite:
6393       case IcsPlayingBlack:
6394         if (appData.zippyPlay) return FALSE;
6395         if (white_piece) {
6396             DisplayMoveError(_("You are playing Black"));
6397             return FALSE;
6398         }
6399         break;
6400
6401       case MachinePlaysBlack:
6402       case IcsPlayingWhite:
6403         if (appData.zippyPlay) return FALSE;
6404         if (!white_piece) {
6405             DisplayMoveError(_("You are playing White"));
6406             return FALSE;
6407         }
6408         break;
6409
6410       case PlayFromGameFile:
6411             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6412       case EditGame:
6413         if (!white_piece && WhiteOnMove(currentMove)) {
6414             DisplayMoveError(_("It is White's turn"));
6415             return FALSE;
6416         }
6417         if (white_piece && !WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is Black's turn"));
6419             return FALSE;
6420         }
6421         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6422             /* Editing correspondence game history */
6423             /* Could disallow this or prompt for confirmation */
6424             cmailOldMove = -1;
6425         }
6426         break;
6427
6428       case BeginningOfGame:
6429         if (appData.icsActive) return FALSE;
6430         if (!appData.noChessProgram) {
6431             if (!white_piece) {
6432                 DisplayMoveError(_("You are playing White"));
6433                 return FALSE;
6434             }
6435         }
6436         break;
6437
6438       case Training:
6439         if (!white_piece && WhiteOnMove(currentMove)) {
6440             DisplayMoveError(_("It is White's turn"));
6441             return FALSE;
6442         }
6443         if (white_piece && !WhiteOnMove(currentMove)) {
6444             DisplayMoveError(_("It is Black's turn"));
6445             return FALSE;
6446         }
6447         break;
6448
6449       default:
6450       case IcsExamining:
6451         break;
6452     }
6453     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6454         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6455         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6456         && gameMode != AnalyzeFile && gameMode != Training) {
6457         DisplayMoveError(_("Displayed position is not current"));
6458         return FALSE;
6459     }
6460     return TRUE;
6461 }
6462
6463 Boolean
6464 OnlyMove (int *x, int *y, Boolean captures) 
6465 {
6466     DisambiguateClosure cl;
6467     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6468     switch(gameMode) {
6469       case MachinePlaysBlack:
6470       case IcsPlayingWhite:
6471       case BeginningOfGame:
6472         if(!WhiteOnMove(currentMove)) return FALSE;
6473         break;
6474       case MachinePlaysWhite:
6475       case IcsPlayingBlack:
6476         if(WhiteOnMove(currentMove)) return FALSE;
6477         break;
6478       case EditGame:
6479         break;
6480       default:
6481         return FALSE;
6482     }
6483     cl.pieceIn = EmptySquare;
6484     cl.rfIn = *y;
6485     cl.ffIn = *x;
6486     cl.rtIn = -1;
6487     cl.ftIn = -1;
6488     cl.promoCharIn = NULLCHAR;
6489     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6490     if( cl.kind == NormalMove ||
6491         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6492         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6493         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6494       fromX = cl.ff;
6495       fromY = cl.rf;
6496       *x = cl.ft;
6497       *y = cl.rt;
6498       return TRUE;
6499     }
6500     if(cl.kind != ImpossibleMove) return FALSE;
6501     cl.pieceIn = EmptySquare;
6502     cl.rfIn = -1;
6503     cl.ffIn = -1;
6504     cl.rtIn = *y;
6505     cl.ftIn = *x;
6506     cl.promoCharIn = NULLCHAR;
6507     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6508     if( cl.kind == NormalMove ||
6509         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6510         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6511         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6512       fromX = cl.ff;
6513       fromY = cl.rf;
6514       *x = cl.ft;
6515       *y = cl.rt;
6516       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6517       return TRUE;
6518     }
6519     return FALSE;
6520 }
6521
6522 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6523 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6524 int lastLoadGameUseList = FALSE;
6525 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6526 ChessMove lastLoadGameStart = EndOfFile;
6527 int doubleClick;
6528
6529 void
6530 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6531 {
6532     ChessMove moveType;
6533     ChessSquare pdown, pup;
6534     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6535
6536
6537     /* Check if the user is playing in turn.  This is complicated because we
6538        let the user "pick up" a piece before it is his turn.  So the piece he
6539        tried to pick up may have been captured by the time he puts it down!
6540        Therefore we use the color the user is supposed to be playing in this
6541        test, not the color of the piece that is currently on the starting
6542        square---except in EditGame mode, where the user is playing both
6543        sides; fortunately there the capture race can't happen.  (It can
6544        now happen in IcsExamining mode, but that's just too bad.  The user
6545        will get a somewhat confusing message in that case.)
6546        */
6547
6548     switch (gameMode) {
6549       case AnalyzeFile:
6550       case TwoMachinesPlay:
6551       case EndOfGame:
6552       case IcsObserving:
6553       case IcsIdle:
6554         /* We switched into a game mode where moves are not accepted,
6555            perhaps while the mouse button was down. */
6556         return;
6557
6558       case MachinePlaysWhite:
6559         /* User is moving for Black */
6560         if (WhiteOnMove(currentMove)) {
6561             DisplayMoveError(_("It is White's turn"));
6562             return;
6563         }
6564         break;
6565
6566       case MachinePlaysBlack:
6567         /* User is moving for White */
6568         if (!WhiteOnMove(currentMove)) {
6569             DisplayMoveError(_("It is Black's turn"));
6570             return;
6571         }
6572         break;
6573
6574       case PlayFromGameFile:
6575             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6576       case EditGame:
6577       case IcsExamining:
6578       case BeginningOfGame:
6579       case AnalyzeMode:
6580       case Training:
6581         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6582         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6583             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6584             /* User is moving for Black */
6585             if (WhiteOnMove(currentMove)) {
6586                 DisplayMoveError(_("It is White's turn"));
6587                 return;
6588             }
6589         } else {
6590             /* User is moving for White */
6591             if (!WhiteOnMove(currentMove)) {
6592                 DisplayMoveError(_("It is Black's turn"));
6593                 return;
6594             }
6595         }
6596         break;
6597
6598       case IcsPlayingBlack:
6599         /* User is moving for Black */
6600         if (WhiteOnMove(currentMove)) {
6601             if (!appData.premove) {
6602                 DisplayMoveError(_("It is White's turn"));
6603             } else if (toX >= 0 && toY >= 0) {
6604                 premoveToX = toX;
6605                 premoveToY = toY;
6606                 premoveFromX = fromX;
6607                 premoveFromY = fromY;
6608                 premovePromoChar = promoChar;
6609                 gotPremove = 1;
6610                 if (appData.debugMode)
6611                     fprintf(debugFP, "Got premove: fromX %d,"
6612                             "fromY %d, toX %d, toY %d\n",
6613                             fromX, fromY, toX, toY);
6614             }
6615             return;
6616         }
6617         break;
6618
6619       case IcsPlayingWhite:
6620         /* User is moving for White */
6621         if (!WhiteOnMove(currentMove)) {
6622             if (!appData.premove) {
6623                 DisplayMoveError(_("It is Black's turn"));
6624             } else if (toX >= 0 && toY >= 0) {
6625                 premoveToX = toX;
6626                 premoveToY = toY;
6627                 premoveFromX = fromX;
6628                 premoveFromY = fromY;
6629                 premovePromoChar = promoChar;
6630                 gotPremove = 1;
6631                 if (appData.debugMode)
6632                     fprintf(debugFP, "Got premove: fromX %d,"
6633                             "fromY %d, toX %d, toY %d\n",
6634                             fromX, fromY, toX, toY);
6635             }
6636             return;
6637         }
6638         break;
6639
6640       default:
6641         break;
6642
6643       case EditPosition:
6644         /* EditPosition, empty square, or different color piece;
6645            click-click move is possible */
6646         if (toX == -2 || toY == -2) {
6647             boards[0][fromY][fromX] = EmptySquare;
6648             DrawPosition(FALSE, boards[currentMove]);
6649             return;
6650         } else if (toX >= 0 && toY >= 0) {
6651             boards[0][toY][toX] = boards[0][fromY][fromX];
6652             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6653                 if(boards[0][fromY][0] != EmptySquare) {
6654                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6655                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6656                 }
6657             } else
6658             if(fromX == BOARD_RGHT+1) {
6659                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6660                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6661                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6662                 }
6663             } else
6664             boards[0][fromY][fromX] = EmptySquare;
6665             DrawPosition(FALSE, boards[currentMove]);
6666             return;
6667         }
6668         return;
6669     }
6670
6671     if(toX < 0 || toY < 0) return;
6672     pdown = boards[currentMove][fromY][fromX];
6673     pup = boards[currentMove][toY][toX];
6674
6675     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6676     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6677          if( pup != EmptySquare ) return;
6678          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6679            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6680                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6681            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6682            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6683            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6684            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6685          fromY = DROP_RANK;
6686     }
6687
6688     /* [HGM] always test for legality, to get promotion info */
6689     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6690                                          fromY, fromX, toY, toX, promoChar);
6691
6692     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6693
6694     /* [HGM] but possibly ignore an IllegalMove result */
6695     if (appData.testLegality) {
6696         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6697             DisplayMoveError(_("Illegal move"));
6698             return;
6699         }
6700     }
6701
6702     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6703         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6704              ClearPremoveHighlights(); // was included
6705         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6706         return;
6707     }
6708
6709     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6710 }
6711
6712 /* Common tail of UserMoveEvent and DropMenuEvent */
6713 int
6714 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6715 {
6716     char *bookHit = 0;
6717
6718     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6719         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6720         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6721         if(WhiteOnMove(currentMove)) {
6722             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6723         } else {
6724             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6725         }
6726     }
6727
6728     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6729        move type in caller when we know the move is a legal promotion */
6730     if(moveType == NormalMove && promoChar)
6731         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6732
6733     /* [HGM] <popupFix> The following if has been moved here from
6734        UserMoveEvent(). Because it seemed to belong here (why not allow
6735        piece drops in training games?), and because it can only be
6736        performed after it is known to what we promote. */
6737     if (gameMode == Training) {
6738       /* compare the move played on the board to the next move in the
6739        * game. If they match, display the move and the opponent's response.
6740        * If they don't match, display an error message.
6741        */
6742       int saveAnimate;
6743       Board testBoard;
6744       CopyBoard(testBoard, boards[currentMove]);
6745       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6746
6747       if (CompareBoards(testBoard, boards[currentMove+1])) {
6748         ForwardInner(currentMove+1);
6749
6750         /* Autoplay the opponent's response.
6751          * if appData.animate was TRUE when Training mode was entered,
6752          * the response will be animated.
6753          */
6754         saveAnimate = appData.animate;
6755         appData.animate = animateTraining;
6756         ForwardInner(currentMove+1);
6757         appData.animate = saveAnimate;
6758
6759         /* check for the end of the game */
6760         if (currentMove >= forwardMostMove) {
6761           gameMode = PlayFromGameFile;
6762           ModeHighlight();
6763           SetTrainingModeOff();
6764           DisplayInformation(_("End of game"));
6765         }
6766       } else {
6767         DisplayError(_("Incorrect move"), 0);
6768       }
6769       return 1;
6770     }
6771
6772   /* Ok, now we know that the move is good, so we can kill
6773      the previous line in Analysis Mode */
6774   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6775                                 && currentMove < forwardMostMove) {
6776     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6777     else forwardMostMove = currentMove;
6778   }
6779
6780   ClearMap();
6781
6782   /* If we need the chess program but it's dead, restart it */
6783   ResurrectChessProgram();
6784
6785   /* A user move restarts a paused game*/
6786   if (pausing)
6787     PauseEvent();
6788
6789   thinkOutput[0] = NULLCHAR;
6790
6791   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6792
6793   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6794     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795     return 1;
6796   }
6797
6798   if (gameMode == BeginningOfGame) {
6799     if (appData.noChessProgram) {
6800       gameMode = EditGame;
6801       SetGameInfo();
6802     } else {
6803       char buf[MSG_SIZ];
6804       gameMode = MachinePlaysBlack;
6805       StartClocks();
6806       SetGameInfo();
6807       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6808       DisplayTitle(buf);
6809       if (first.sendName) {
6810         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6811         SendToProgram(buf, &first);
6812       }
6813       StartClocks();
6814     }
6815     ModeHighlight();
6816   }
6817
6818   /* Relay move to ICS or chess engine */
6819   if (appData.icsActive) {
6820     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6821         gameMode == IcsExamining) {
6822       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6823         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6824         SendToICS("draw ");
6825         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6826       }
6827       // also send plain move, in case ICS does not understand atomic claims
6828       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829       ics_user_moved = 1;
6830     }
6831   } else {
6832     if (first.sendTime && (gameMode == BeginningOfGame ||
6833                            gameMode == MachinePlaysWhite ||
6834                            gameMode == MachinePlaysBlack)) {
6835       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6836     }
6837     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6838          // [HGM] book: if program might be playing, let it use book
6839         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6840         first.maybeThinking = TRUE;
6841     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6842         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6843         SendBoard(&first, currentMove+1);
6844     } else SendMoveToProgram(forwardMostMove-1, &first);
6845     if (currentMove == cmailOldMove + 1) {
6846       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6847     }
6848   }
6849
6850   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6851
6852   switch (gameMode) {
6853   case EditGame:
6854     if(appData.testLegality)
6855     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6856     case MT_NONE:
6857     case MT_CHECK:
6858       break;
6859     case MT_CHECKMATE:
6860     case MT_STAINMATE:
6861       if (WhiteOnMove(currentMove)) {
6862         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6863       } else {
6864         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6865       }
6866       break;
6867     case MT_STALEMATE:
6868       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6869       break;
6870     }
6871     break;
6872
6873   case MachinePlaysBlack:
6874   case MachinePlaysWhite:
6875     /* disable certain menu options while machine is thinking */
6876     SetMachineThinkingEnables();
6877     break;
6878
6879   default:
6880     break;
6881   }
6882
6883   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6884   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6885
6886   if(bookHit) { // [HGM] book: simulate book reply
6887         static char bookMove[MSG_SIZ]; // a bit generous?
6888
6889         programStats.nodes = programStats.depth = programStats.time =
6890         programStats.score = programStats.got_only_move = 0;
6891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6892
6893         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6894         strcat(bookMove, bookHit);
6895         HandleMachineMove(bookMove, &first);
6896   }
6897   return 1;
6898 }
6899
6900 void
6901 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6902 {
6903     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6904     Markers *m = (Markers *) closure;
6905     if(rf == fromY && ff == fromX)
6906         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6907                          || kind == WhiteCapturesEnPassant
6908                          || kind == BlackCapturesEnPassant);
6909     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6910 }
6911
6912 void
6913 MarkTargetSquares (int clear)
6914 {
6915   int x, y;
6916   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6917      !appData.testLegality || gameMode == EditPosition) return;
6918   if(clear) {
6919     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6920   } else {
6921     int capt = 0;
6922     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6923     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6924       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6925       if(capt)
6926       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6927     }
6928   }
6929   DrawPosition(TRUE, NULL);
6930 }
6931
6932 int
6933 Explode (Board board, int fromX, int fromY, int toX, int toY)
6934 {
6935     if(gameInfo.variant == VariantAtomic &&
6936        (board[toY][toX] != EmptySquare ||                     // capture?
6937         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6938                          board[fromY][fromX] == BlackPawn   )
6939       )) {
6940         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6941         return TRUE;
6942     }
6943     return FALSE;
6944 }
6945
6946 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6947
6948 int
6949 CanPromote (ChessSquare piece, int y)
6950 {
6951         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6952         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6953         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6954            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6955            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6956                                                   gameInfo.variant == VariantMakruk) return FALSE;
6957         return (piece == BlackPawn && y == 1 ||
6958                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6959                 piece == BlackLance && y == 1 ||
6960                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6961 }
6962
6963 void
6964 LeftClick (ClickType clickType, int xPix, int yPix)
6965 {
6966     int x, y;
6967     Boolean saveAnimate;
6968     static int second = 0, promotionChoice = 0, clearFlag = 0;
6969     char promoChoice = NULLCHAR;
6970     ChessSquare piece;
6971     static TimeMark lastClickTime, prevClickTime;
6972
6973     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6974
6975     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6976
6977     if (clickType == Press) ErrorPopDown();
6978
6979     x = EventToSquare(xPix, BOARD_WIDTH);
6980     y = EventToSquare(yPix, BOARD_HEIGHT);
6981     if (!flipView && y >= 0) {
6982         y = BOARD_HEIGHT - 1 - y;
6983     }
6984     if (flipView && x >= 0) {
6985         x = BOARD_WIDTH - 1 - x;
6986     }
6987
6988     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6989         defaultPromoChoice = promoSweep;
6990         promoSweep = EmptySquare;   // terminate sweep
6991         promoDefaultAltered = TRUE;
6992         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6993     }
6994
6995     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6996         if(clickType == Release) return; // ignore upclick of click-click destination
6997         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6998         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6999         if(gameInfo.holdingsWidth &&
7000                 (WhiteOnMove(currentMove)
7001                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7002                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7003             // click in right holdings, for determining promotion piece
7004             ChessSquare p = boards[currentMove][y][x];
7005             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7006             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7007             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7008                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7009                 fromX = fromY = -1;
7010                 return;
7011             }
7012         }
7013         DrawPosition(FALSE, boards[currentMove]);
7014         return;
7015     }
7016
7017     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7018     if(clickType == Press
7019             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7020               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7021               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7022         return;
7023
7024     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7025         // could be static click on premove from-square: abort premove
7026         gotPremove = 0;
7027         ClearPremoveHighlights();
7028     }
7029
7030     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7031         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7032
7033     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7034         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7035                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7036         defaultPromoChoice = DefaultPromoChoice(side);
7037     }
7038
7039     autoQueen = appData.alwaysPromoteToQueen;
7040
7041     if (fromX == -1) {
7042       int originalY = y;
7043       gatingPiece = EmptySquare;
7044       if (clickType != Press) {
7045         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7046             DragPieceEnd(xPix, yPix); dragging = 0;
7047             DrawPosition(FALSE, NULL);
7048         }
7049         return;
7050       }
7051       doubleClick = FALSE;
7052       fromX = x; fromY = y; toX = toY = -1;
7053       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7054          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7055          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7056             /* First square */
7057             if (OKToStartUserMove(fromX, fromY)) {
7058                 second = 0;
7059                 MarkTargetSquares(0);
7060                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7061                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7062                     promoSweep = defaultPromoChoice;
7063                     selectFlag = 0; lastX = xPix; lastY = yPix;
7064                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7065                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7066                 }
7067                 if (appData.highlightDragging) {
7068                     SetHighlights(fromX, fromY, -1, -1);
7069                 }
7070             } else fromX = fromY = -1;
7071             return;
7072         }
7073     }
7074
7075     /* fromX != -1 */
7076     if (clickType == Press && gameMode != EditPosition) {
7077         ChessSquare fromP;
7078         ChessSquare toP;
7079         int frc;
7080
7081         // ignore off-board to clicks
7082         if(y < 0 || x < 0) return;
7083
7084         /* Check if clicking again on the same color piece */
7085         fromP = boards[currentMove][fromY][fromX];
7086         toP = boards[currentMove][y][x];
7087         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7088         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7089              WhitePawn <= toP && toP <= WhiteKing &&
7090              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7091              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7092             (BlackPawn <= fromP && fromP <= BlackKing &&
7093              BlackPawn <= toP && toP <= BlackKing &&
7094              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7095              !(fromP == BlackKing && toP == BlackRook && frc))) {
7096             /* Clicked again on same color piece -- changed his mind */
7097             second = (x == fromX && y == fromY);
7098             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7099                 second = FALSE; // first double-click rather than scond click
7100                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7101             }
7102             promoDefaultAltered = FALSE;
7103             MarkTargetSquares(1);
7104            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7105             if (appData.highlightDragging) {
7106                 SetHighlights(x, y, -1, -1);
7107             } else {
7108                 ClearHighlights();
7109             }
7110             if (OKToStartUserMove(x, y)) {
7111                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7112                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7113                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7114                  gatingPiece = boards[currentMove][fromY][fromX];
7115                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7116                 fromX = x;
7117                 fromY = y; dragging = 1;
7118                 MarkTargetSquares(0);
7119                 DragPieceBegin(xPix, yPix, FALSE);
7120                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7121                     promoSweep = defaultPromoChoice;
7122                     selectFlag = 0; lastX = xPix; lastY = yPix;
7123                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7124                 }
7125             }
7126            }
7127            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7128            second = FALSE; 
7129         }
7130         // ignore clicks on holdings
7131         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7132     }
7133
7134     if (clickType == Release && x == fromX && y == fromY) {
7135         DragPieceEnd(xPix, yPix); dragging = 0;
7136         if(clearFlag) {
7137             // a deferred attempt to click-click move an empty square on top of a piece
7138             boards[currentMove][y][x] = EmptySquare;
7139             ClearHighlights();
7140             DrawPosition(FALSE, boards[currentMove]);
7141             fromX = fromY = -1; clearFlag = 0;
7142             return;
7143         }
7144         if (appData.animateDragging) {
7145             /* Undo animation damage if any */
7146             DrawPosition(FALSE, NULL);
7147         }
7148         if (second) {
7149             /* Second up/down in same square; just abort move */
7150             second = 0;
7151             fromX = fromY = -1;
7152             gatingPiece = EmptySquare;
7153             ClearHighlights();
7154             gotPremove = 0;
7155             ClearPremoveHighlights();
7156         } else {
7157             /* First upclick in same square; start click-click mode */
7158             SetHighlights(x, y, -1, -1);
7159         }
7160         return;
7161     }
7162
7163     clearFlag = 0;
7164
7165     /* we now have a different from- and (possibly off-board) to-square */
7166     /* Completed move */
7167     toX = x;
7168     toY = y;
7169     saveAnimate = appData.animate;
7170     MarkTargetSquares(1);
7171     if (clickType == Press) {
7172         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7173             // must be Edit Position mode with empty-square selected
7174             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7175             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7176             return;
7177         }
7178         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7179             ChessSquare piece = boards[currentMove][fromY][fromX];
7180             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7181             promoSweep = defaultPromoChoice;
7182             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7183             selectFlag = 0; lastX = xPix; lastY = yPix;
7184             Sweep(0); // Pawn that is going to promote: preview promotion piece
7185             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7186             DrawPosition(FALSE, boards[currentMove]);
7187             return;
7188         }
7189         /* Finish clickclick move */
7190         if (appData.animate || appData.highlightLastMove) {
7191             SetHighlights(fromX, fromY, toX, toY);
7192         } else {
7193             ClearHighlights();
7194         }
7195     } else {
7196         /* Finish drag move */
7197         if (appData.highlightLastMove) {
7198             SetHighlights(fromX, fromY, toX, toY);
7199         } else {
7200             ClearHighlights();
7201         }
7202         DragPieceEnd(xPix, yPix); dragging = 0;
7203         /* Don't animate move and drag both */
7204         appData.animate = FALSE;
7205     }
7206
7207     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7208     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7209         ChessSquare piece = boards[currentMove][fromY][fromX];
7210         if(gameMode == EditPosition && piece != EmptySquare &&
7211            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7212             int n;
7213
7214             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7215                 n = PieceToNumber(piece - (int)BlackPawn);
7216                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7217                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7218                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7219             } else
7220             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7221                 n = PieceToNumber(piece);
7222                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7223                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7224                 boards[currentMove][n][BOARD_WIDTH-2]++;
7225             }
7226             boards[currentMove][fromY][fromX] = EmptySquare;
7227         }
7228         ClearHighlights();
7229         fromX = fromY = -1;
7230         DrawPosition(TRUE, boards[currentMove]);
7231         return;
7232     }
7233
7234     // off-board moves should not be highlighted
7235     if(x < 0 || y < 0) ClearHighlights();
7236
7237     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7238
7239     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7240         SetHighlights(fromX, fromY, toX, toY);
7241         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7242             // [HGM] super: promotion to captured piece selected from holdings
7243             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7244             promotionChoice = TRUE;
7245             // kludge follows to temporarily execute move on display, without promoting yet
7246             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7247             boards[currentMove][toY][toX] = p;
7248             DrawPosition(FALSE, boards[currentMove]);
7249             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7250             boards[currentMove][toY][toX] = q;
7251             DisplayMessage("Click in holdings to choose piece", "");
7252             return;
7253         }
7254         PromotionPopUp();
7255     } else {
7256         int oldMove = currentMove;
7257         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7258         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7259         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7260         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7261            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7262             DrawPosition(TRUE, boards[currentMove]);
7263         fromX = fromY = -1;
7264     }
7265     appData.animate = saveAnimate;
7266     if (appData.animate || appData.animateDragging) {
7267         /* Undo animation damage if needed */
7268         DrawPosition(FALSE, NULL);
7269     }
7270 }
7271
7272 int
7273 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7274 {   // front-end-free part taken out of PieceMenuPopup
7275     int whichMenu; int xSqr, ySqr;
7276
7277     if(seekGraphUp) { // [HGM] seekgraph
7278         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7279         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7280         return -2;
7281     }
7282
7283     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7284          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7285         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7286         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7287         if(action == Press)   {
7288             originalFlip = flipView;
7289             flipView = !flipView; // temporarily flip board to see game from partners perspective
7290             DrawPosition(TRUE, partnerBoard);
7291             DisplayMessage(partnerStatus, "");
7292             partnerUp = TRUE;
7293         } else if(action == Release) {
7294             flipView = originalFlip;
7295             DrawPosition(TRUE, boards[currentMove]);
7296             partnerUp = FALSE;
7297         }
7298         return -2;
7299     }
7300
7301     xSqr = EventToSquare(x, BOARD_WIDTH);
7302     ySqr = EventToSquare(y, BOARD_HEIGHT);
7303     if (action == Release) {
7304         if(pieceSweep != EmptySquare) {
7305             EditPositionMenuEvent(pieceSweep, toX, toY);
7306             pieceSweep = EmptySquare;
7307         } else UnLoadPV(); // [HGM] pv
7308     }
7309     if (action != Press) return -2; // return code to be ignored
7310     switch (gameMode) {
7311       case IcsExamining:
7312         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7313       case EditPosition:
7314         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7315         if (xSqr < 0 || ySqr < 0) return -1;
7316         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7317         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7318         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7319         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7320         NextPiece(0);
7321         return 2; // grab
7322       case IcsObserving:
7323         if(!appData.icsEngineAnalyze) return -1;
7324       case IcsPlayingWhite:
7325       case IcsPlayingBlack:
7326         if(!appData.zippyPlay) goto noZip;
7327       case AnalyzeMode:
7328       case AnalyzeFile:
7329       case MachinePlaysWhite:
7330       case MachinePlaysBlack:
7331       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7332         if (!appData.dropMenu) {
7333           LoadPV(x, y);
7334           return 2; // flag front-end to grab mouse events
7335         }
7336         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7337            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7338       case EditGame:
7339       noZip:
7340         if (xSqr < 0 || ySqr < 0) return -1;
7341         if (!appData.dropMenu || appData.testLegality &&
7342             gameInfo.variant != VariantBughouse &&
7343             gameInfo.variant != VariantCrazyhouse) return -1;
7344         whichMenu = 1; // drop menu
7345         break;
7346       default:
7347         return -1;
7348     }
7349
7350     if (((*fromX = xSqr) < 0) ||
7351         ((*fromY = ySqr) < 0)) {
7352         *fromX = *fromY = -1;
7353         return -1;
7354     }
7355     if (flipView)
7356       *fromX = BOARD_WIDTH - 1 - *fromX;
7357     else
7358       *fromY = BOARD_HEIGHT - 1 - *fromY;
7359
7360     return whichMenu;
7361 }
7362
7363 void
7364 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7365 {
7366 //    char * hint = lastHint;
7367     FrontEndProgramStats stats;
7368
7369     stats.which = cps == &first ? 0 : 1;
7370     stats.depth = cpstats->depth;
7371     stats.nodes = cpstats->nodes;
7372     stats.score = cpstats->score;
7373     stats.time = cpstats->time;
7374     stats.pv = cpstats->movelist;
7375     stats.hint = lastHint;
7376     stats.an_move_index = 0;
7377     stats.an_move_count = 0;
7378
7379     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7380         stats.hint = cpstats->move_name;
7381         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7382         stats.an_move_count = cpstats->nr_moves;
7383     }
7384
7385     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
7386
7387     SetProgramStats( &stats );
7388 }
7389
7390 void
7391 ClearEngineOutputPane (int which)
7392 {
7393     static FrontEndProgramStats dummyStats;
7394     dummyStats.which = which;
7395     dummyStats.pv = "#";
7396     SetProgramStats( &dummyStats );
7397 }
7398
7399 #define MAXPLAYERS 500
7400
7401 char *
7402 TourneyStandings (int display)
7403 {
7404     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7405     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7406     char result, *p, *names[MAXPLAYERS];
7407
7408     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7409         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7410     names[0] = p = strdup(appData.participants);
7411     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7412
7413     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7414
7415     while(result = appData.results[nr]) {
7416         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7417         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7418         wScore = bScore = 0;
7419         switch(result) {
7420           case '+': wScore = 2; break;
7421           case '-': bScore = 2; break;
7422           case '=': wScore = bScore = 1; break;
7423           case ' ':
7424           case '*': return strdup("busy"); // tourney not finished
7425         }
7426         score[w] += wScore;
7427         score[b] += bScore;
7428         games[w]++;
7429         games[b]++;
7430         nr++;
7431     }
7432     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7433     for(w=0; w<nPlayers; w++) {
7434         bScore = -1;
7435         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7436         ranking[w] = b; points[w] = bScore; score[b] = -2;
7437     }
7438     p = malloc(nPlayers*34+1);
7439     for(w=0; w<nPlayers && w<display; w++)
7440         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7441     free(names[0]);
7442     return p;
7443 }
7444
7445 void
7446 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7447 {       // count all piece types
7448         int p, f, r;
7449         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7450         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7451         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7452                 p = board[r][f];
7453                 pCnt[p]++;
7454                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7455                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7456                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7457                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7458                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7459                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7460         }
7461 }
7462
7463 int
7464 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7465 {
7466         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7467         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7468
7469         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7470         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7471         if(myPawns == 2 && nMine == 3) // KPP
7472             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7473         if(myPawns == 1 && nMine == 2) // KP
7474             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7475         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7476             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7477         if(myPawns) return FALSE;
7478         if(pCnt[WhiteRook+side])
7479             return pCnt[BlackRook-side] ||
7480                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7481                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7482                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7483         if(pCnt[WhiteCannon+side]) {
7484             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7485             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7486         }
7487         if(pCnt[WhiteKnight+side])
7488             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7489         return FALSE;
7490 }
7491
7492 int
7493 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7494 {
7495         VariantClass v = gameInfo.variant;
7496
7497         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7498         if(v == VariantShatranj) return TRUE; // always winnable through baring
7499         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7500         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7501
7502         if(v == VariantXiangqi) {
7503                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7504
7505                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7506                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7507                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7508                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7509                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7510                 if(stale) // we have at least one last-rank P plus perhaps C
7511                     return majors // KPKX
7512                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7513                 else // KCA*E*
7514                     return pCnt[WhiteFerz+side] // KCAK
7515                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7516                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7517                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7518
7519         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7520                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7521
7522                 if(nMine == 1) return FALSE; // bare King
7523                 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
7524                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7525                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7526                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7527                 if(pCnt[WhiteKnight+side])
7528                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7529                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7530                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7531                 if(nBishops)
7532                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7533                 if(pCnt[WhiteAlfil+side])
7534                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7535                 if(pCnt[WhiteWazir+side])
7536                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7537         }
7538
7539         return TRUE;
7540 }
7541
7542 int
7543 CompareWithRights (Board b1, Board b2)
7544 {
7545     int rights = 0;
7546     if(!CompareBoards(b1, b2)) return FALSE;
7547     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7548     /* compare castling rights */
7549     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7550            rights++; /* King lost rights, while rook still had them */
7551     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7552         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7553            rights++; /* but at least one rook lost them */
7554     }
7555     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7556            rights++;
7557     if( b1[CASTLING][5] != NoRights ) {
7558         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7559            rights++;
7560     }
7561     return rights == 0;
7562 }
7563
7564 int
7565 Adjudicate (ChessProgramState *cps)
7566 {       // [HGM] some adjudications useful with buggy engines
7567         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7568         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7569         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7570         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7571         int k, count = 0; static int bare = 1;
7572         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7573         Boolean canAdjudicate = !appData.icsActive;
7574
7575         // most tests only when we understand the game, i.e. legality-checking on
7576             if( appData.testLegality )
7577             {   /* [HGM] Some more adjudications for obstinate engines */
7578                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7579                 static int moveCount = 6;
7580                 ChessMove result;
7581                 char *reason = NULL;
7582
7583                 /* Count what is on board. */
7584                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7585
7586                 /* Some material-based adjudications that have to be made before stalemate test */
7587                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7588                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7589                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7590                      if(canAdjudicate && appData.checkMates) {
7591                          if(engineOpponent)
7592                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7593                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7594                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7595                          return 1;
7596                      }
7597                 }
7598
7599                 /* Bare King in Shatranj (loses) or Losers (wins) */
7600                 if( nrW == 1 || nrB == 1) {
7601                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7602                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7603                      if(canAdjudicate && appData.checkMates) {
7604                          if(engineOpponent)
7605                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7606                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7607                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7608                          return 1;
7609                      }
7610                   } else
7611                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7612                   {    /* bare King */
7613                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7614                         if(canAdjudicate && appData.checkMates) {
7615                             /* but only adjudicate if adjudication enabled */
7616                             if(engineOpponent)
7617                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7618                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7619                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7620                             return 1;
7621                         }
7622                   }
7623                 } else bare = 1;
7624
7625
7626             // don't wait for engine to announce game end if we can judge ourselves
7627             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7628               case MT_CHECK:
7629                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7630                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7631                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7632                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7633                             checkCnt++;
7634                         if(checkCnt >= 2) {
7635                             reason = "Xboard adjudication: 3rd check";
7636                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7637                             break;
7638                         }
7639                     }
7640                 }
7641               case MT_NONE:
7642               default:
7643                 break;
7644               case MT_STALEMATE:
7645               case MT_STAINMATE:
7646                 reason = "Xboard adjudication: Stalemate";
7647                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7648                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7649                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7650                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7651                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7652                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7653                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7654                                                                         EP_CHECKMATE : EP_WINS);
7655                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7656                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7657                 }
7658                 break;
7659               case MT_CHECKMATE:
7660                 reason = "Xboard adjudication: Checkmate";
7661                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7662                 break;
7663             }
7664
7665                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7666                     case EP_STALEMATE:
7667                         result = GameIsDrawn; break;
7668                     case EP_CHECKMATE:
7669                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7670                     case EP_WINS:
7671                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7672                     default:
7673                         result = EndOfFile;
7674                 }
7675                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7676                     if(engineOpponent)
7677                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                     GameEnds( result, reason, GE_XBOARD );
7679                     return 1;
7680                 }
7681
7682                 /* Next absolutely insufficient mating material. */
7683                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7684                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7685                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7686
7687                      /* always flag draws, for judging claims */
7688                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7689
7690                      if(canAdjudicate && appData.materialDraws) {
7691                          /* but only adjudicate them if adjudication enabled */
7692                          if(engineOpponent) {
7693                            SendToProgram("force\n", engineOpponent); // suppress reply
7694                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7695                          }
7696                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7697                          return 1;
7698                      }
7699                 }
7700
7701                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7702                 if(gameInfo.variant == VariantXiangqi ?
7703                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7704                  : nrW + nrB == 4 &&
7705                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7706                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7707                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7708                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7709                    ) ) {
7710                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7711                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7712                           if(engineOpponent) {
7713                             SendToProgram("force\n", engineOpponent); // suppress reply
7714                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7715                           }
7716                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7717                           return 1;
7718                      }
7719                 } else moveCount = 6;
7720             }
7721
7722         // Repetition draws and 50-move rule can be applied independently of legality testing
7723
7724                 /* Check for rep-draws */
7725                 count = 0;
7726                 for(k = forwardMostMove-2;
7727                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7728                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7729                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7730                     k-=2)
7731                 {   int rights=0;
7732                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7733                         /* compare castling rights */
7734                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7735                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7736                                 rights++; /* King lost rights, while rook still had them */
7737                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7738                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7739                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7740                                    rights++; /* but at least one rook lost them */
7741                         }
7742                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7743                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7744                                 rights++;
7745                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7746                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7747                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7748                                    rights++;
7749                         }
7750                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7751                             && appData.drawRepeats > 1) {
7752                              /* adjudicate after user-specified nr of repeats */
7753                              int result = GameIsDrawn;
7754                              char *details = "XBoard adjudication: repetition draw";
7755                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7756                                 // [HGM] xiangqi: check for forbidden perpetuals
7757                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7758                                 for(m=forwardMostMove; m>k; m-=2) {
7759                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7760                                         ourPerpetual = 0; // the current mover did not always check
7761                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7762                                         hisPerpetual = 0; // the opponent did not always check
7763                                 }
7764                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7765                                                                         ourPerpetual, hisPerpetual);
7766                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7767                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7768                                     details = "Xboard adjudication: perpetual checking";
7769                                 } else
7770                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7771                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7772                                 } else
7773                                 // Now check for perpetual chases
7774                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7775                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7776                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7777                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7778                                         static char resdet[MSG_SIZ];
7779                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7780                                         details = resdet;
7781                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7782                                     } else
7783                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7784                                         break; // Abort repetition-checking loop.
7785                                 }
7786                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7787                              }
7788                              if(engineOpponent) {
7789                                SendToProgram("force\n", engineOpponent); // suppress reply
7790                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7791                              }
7792                              GameEnds( result, details, GE_XBOARD );
7793                              return 1;
7794                         }
7795                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7796                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7797                     }
7798                 }
7799
7800                 /* Now we test for 50-move draws. Determine ply count */
7801                 count = forwardMostMove;
7802                 /* look for last irreversble move */
7803                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7804                     count--;
7805                 /* if we hit starting position, add initial plies */
7806                 if( count == backwardMostMove )
7807                     count -= initialRulePlies;
7808                 count = forwardMostMove - count;
7809                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7810                         // adjust reversible move counter for checks in Xiangqi
7811                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7812                         if(i < backwardMostMove) i = backwardMostMove;
7813                         while(i <= forwardMostMove) {
7814                                 lastCheck = inCheck; // check evasion does not count
7815                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7816                                 if(inCheck || lastCheck) count--; // check does not count
7817                                 i++;
7818                         }
7819                 }
7820                 if( count >= 100)
7821                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7822                          /* this is used to judge if draw claims are legal */
7823                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7824                          if(engineOpponent) {
7825                            SendToProgram("force\n", engineOpponent); // suppress reply
7826                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7827                          }
7828                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7829                          return 1;
7830                 }
7831
7832                 /* if draw offer is pending, treat it as a draw claim
7833                  * when draw condition present, to allow engines a way to
7834                  * claim draws before making their move to avoid a race
7835                  * condition occurring after their move
7836                  */
7837                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7838                          char *p = NULL;
7839                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7840                              p = "Draw claim: 50-move rule";
7841                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7842                              p = "Draw claim: 3-fold repetition";
7843                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7844                              p = "Draw claim: insufficient mating material";
7845                          if( p != NULL && canAdjudicate) {
7846                              if(engineOpponent) {
7847                                SendToProgram("force\n", engineOpponent); // suppress reply
7848                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7849                              }
7850                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7851                              return 1;
7852                          }
7853                 }
7854
7855                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7856                     if(engineOpponent) {
7857                       SendToProgram("force\n", engineOpponent); // suppress reply
7858                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7859                     }
7860                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7861                     return 1;
7862                 }
7863         return 0;
7864 }
7865
7866 char *
7867 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7868 {   // [HGM] book: this routine intercepts moves to simulate book replies
7869     char *bookHit = NULL;
7870
7871     //first determine if the incoming move brings opponent into his book
7872     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7873         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7874     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7875     if(bookHit != NULL && !cps->bookSuspend) {
7876         // make sure opponent is not going to reply after receiving move to book position
7877         SendToProgram("force\n", cps);
7878         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7879     }
7880     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7881     // now arrange restart after book miss
7882     if(bookHit) {
7883         // after a book hit we never send 'go', and the code after the call to this routine
7884         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7885         char buf[MSG_SIZ], *move = bookHit;
7886         if(cps->useSAN) {
7887             int fromX, fromY, toX, toY;
7888             char promoChar;
7889             ChessMove moveType;
7890             move = buf + 30;
7891             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7892                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7893                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7894                                     PosFlags(forwardMostMove),
7895                                     fromY, fromX, toY, toX, promoChar, move);
7896             } else {
7897                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7898                 bookHit = NULL;
7899             }
7900         }
7901         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7902         SendToProgram(buf, cps);
7903         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7904     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7905         SendToProgram("go\n", cps);
7906         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7907     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7908         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7909             SendToProgram("go\n", cps);
7910         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7911     }
7912     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7913 }
7914
7915 char *savedMessage;
7916 ChessProgramState *savedState;
7917 void
7918 DeferredBookMove (void)
7919 {
7920         if(savedState->lastPing != savedState->lastPong)
7921                     ScheduleDelayedEvent(DeferredBookMove, 10);
7922         else
7923         HandleMachineMove(savedMessage, savedState);
7924 }
7925
7926 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7927
7928 void
7929 HandleMachineMove (char *message, ChessProgramState *cps)
7930 {
7931     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7932     char realname[MSG_SIZ];
7933     int fromX, fromY, toX, toY;
7934     ChessMove moveType;
7935     char promoChar;
7936     char *p, *pv=buf1;
7937     int machineWhite;
7938     char *bookHit;
7939
7940     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7941         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7942         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7943             DisplayError(_("Invalid pairing from pairing engine"), 0);
7944             return;
7945         }
7946         pairingReceived = 1;
7947         NextMatchGame();
7948         return; // Skim the pairing messages here.
7949     }
7950
7951     cps->userError = 0;
7952
7953 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7954     /*
7955      * Kludge to ignore BEL characters
7956      */
7957     while (*message == '\007') message++;
7958
7959     /*
7960      * [HGM] engine debug message: ignore lines starting with '#' character
7961      */
7962     if(cps->debug && *message == '#') return;
7963
7964     /*
7965      * Look for book output
7966      */
7967     if (cps == &first && bookRequested) {
7968         if (message[0] == '\t' || message[0] == ' ') {
7969             /* Part of the book output is here; append it */
7970             strcat(bookOutput, message);
7971             strcat(bookOutput, "  \n");
7972             return;
7973         } else if (bookOutput[0] != NULLCHAR) {
7974             /* All of book output has arrived; display it */
7975             char *p = bookOutput;
7976             while (*p != NULLCHAR) {
7977                 if (*p == '\t') *p = ' ';
7978                 p++;
7979             }
7980             DisplayInformation(bookOutput);
7981             bookRequested = FALSE;
7982             /* Fall through to parse the current output */
7983         }
7984     }
7985
7986     /*
7987      * Look for machine move.
7988      */
7989     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7990         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7991     {
7992         /* This method is only useful on engines that support ping */
7993         if (cps->lastPing != cps->lastPong) {
7994           if (gameMode == BeginningOfGame) {
7995             /* Extra move from before last new; ignore */
7996             if (appData.debugMode) {
7997                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7998             }
7999           } else {
8000             if (appData.debugMode) {
8001                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8002                         cps->which, gameMode);
8003             }
8004
8005             SendToProgram("undo\n", cps);
8006           }
8007           return;
8008         }
8009
8010         switch (gameMode) {
8011           case BeginningOfGame:
8012             /* Extra move from before last reset; ignore */
8013             if (appData.debugMode) {
8014                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8015             }
8016             return;
8017
8018           case EndOfGame:
8019           case IcsIdle:
8020           default:
8021             /* Extra move after we tried to stop.  The mode test is
8022                not a reliable way of detecting this problem, but it's
8023                the best we can do on engines that don't support ping.
8024             */
8025             if (appData.debugMode) {
8026                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8027                         cps->which, gameMode);
8028             }
8029             SendToProgram("undo\n", cps);
8030             return;
8031
8032           case MachinePlaysWhite:
8033           case IcsPlayingWhite:
8034             machineWhite = TRUE;
8035             break;
8036
8037           case MachinePlaysBlack:
8038           case IcsPlayingBlack:
8039             machineWhite = FALSE;
8040             break;
8041
8042           case TwoMachinesPlay:
8043             machineWhite = (cps->twoMachinesColor[0] == 'w');
8044             break;
8045         }
8046         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8047             if (appData.debugMode) {
8048                 fprintf(debugFP,
8049                         "Ignoring move out of turn by %s, gameMode %d"
8050                         ", forwardMost %d\n",
8051                         cps->which, gameMode, forwardMostMove);
8052             }
8053             return;
8054         }
8055
8056         if(cps->alphaRank) AlphaRank(machineMove, 4);
8057         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8058                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8059             /* Machine move could not be parsed; ignore it. */
8060           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8061                     machineMove, _(cps->which));
8062             DisplayError(buf1, 0);
8063             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8064                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8065             if (gameMode == TwoMachinesPlay) {
8066               GameEnds(machineWhite ? BlackWins : WhiteWins,
8067                        buf1, GE_XBOARD);
8068             }
8069             return;
8070         }
8071
8072         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8073         /* So we have to redo legality test with true e.p. status here,  */
8074         /* to make sure an illegal e.p. capture does not slip through,   */
8075         /* to cause a forfeit on a justified illegal-move complaint      */
8076         /* of the opponent.                                              */
8077         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8078            ChessMove moveType;
8079            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8080                              fromY, fromX, toY, toX, promoChar);
8081             if(moveType == IllegalMove) {
8082               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8083                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8084                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8085                            buf1, GE_XBOARD);
8086                 return;
8087            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8088            /* [HGM] Kludge to handle engines that send FRC-style castling
8089               when they shouldn't (like TSCP-Gothic) */
8090            switch(moveType) {
8091              case WhiteASideCastleFR:
8092              case BlackASideCastleFR:
8093                toX+=2;
8094                currentMoveString[2]++;
8095                break;
8096              case WhiteHSideCastleFR:
8097              case BlackHSideCastleFR:
8098                toX--;
8099                currentMoveString[2]--;
8100                break;
8101              default: ; // nothing to do, but suppresses warning of pedantic compilers
8102            }
8103         }
8104         hintRequested = FALSE;
8105         lastHint[0] = NULLCHAR;
8106         bookRequested = FALSE;
8107         /* Program may be pondering now */
8108         cps->maybeThinking = TRUE;
8109         if (cps->sendTime == 2) cps->sendTime = 1;
8110         if (cps->offeredDraw) cps->offeredDraw--;
8111
8112         /* [AS] Save move info*/
8113         pvInfoList[ forwardMostMove ].score = programStats.score;
8114         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8115         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8116
8117         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8118
8119         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8120         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8121             int count = 0;
8122
8123             while( count < adjudicateLossPlies ) {
8124                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8125
8126                 if( count & 1 ) {
8127                     score = -score; /* Flip score for winning side */
8128                 }
8129
8130                 if( score > adjudicateLossThreshold ) {
8131                     break;
8132                 }
8133
8134                 count++;
8135             }
8136
8137             if( count >= adjudicateLossPlies ) {
8138                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8139
8140                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141                     "Xboard adjudication",
8142                     GE_XBOARD );
8143
8144                 return;
8145             }
8146         }
8147
8148         if(Adjudicate(cps)) {
8149             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8150             return; // [HGM] adjudicate: for all automatic game ends
8151         }
8152
8153 #if ZIPPY
8154         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8155             first.initDone) {
8156           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8157                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8158                 SendToICS("draw ");
8159                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8160           }
8161           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8162           ics_user_moved = 1;
8163           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8164                 char buf[3*MSG_SIZ];
8165
8166                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8167                         programStats.score / 100.,
8168                         programStats.depth,
8169                         programStats.time / 100.,
8170                         (unsigned int)programStats.nodes,
8171                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8172                         programStats.movelist);
8173                 SendToICS(buf);
8174 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8175           }
8176         }
8177 #endif
8178
8179         /* [AS] Clear stats for next move */
8180         ClearProgramStats();
8181         thinkOutput[0] = NULLCHAR;
8182         hiddenThinkOutputState = 0;
8183
8184         bookHit = NULL;
8185         if (gameMode == TwoMachinesPlay) {
8186             /* [HGM] relaying draw offers moved to after reception of move */
8187             /* and interpreting offer as claim if it brings draw condition */
8188             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8189                 SendToProgram("draw\n", cps->other);
8190             }
8191             if (cps->other->sendTime) {
8192                 SendTimeRemaining(cps->other,
8193                                   cps->other->twoMachinesColor[0] == 'w');
8194             }
8195             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8196             if (firstMove && !bookHit) {
8197                 firstMove = FALSE;
8198                 if (cps->other->useColors) {
8199                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8200                 }
8201                 SendToProgram("go\n", cps->other);
8202             }
8203             cps->other->maybeThinking = TRUE;
8204         }
8205
8206         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8207
8208         if (!pausing && appData.ringBellAfterMoves) {
8209             RingBell();
8210         }
8211
8212         /*
8213          * Reenable menu items that were disabled while
8214          * machine was thinking
8215          */
8216         if (gameMode != TwoMachinesPlay)
8217             SetUserThinkingEnables();
8218
8219         // [HGM] book: after book hit opponent has received move and is now in force mode
8220         // force the book reply into it, and then fake that it outputted this move by jumping
8221         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8222         if(bookHit) {
8223                 static char bookMove[MSG_SIZ]; // a bit generous?
8224
8225                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8226                 strcat(bookMove, bookHit);
8227                 message = bookMove;
8228                 cps = cps->other;
8229                 programStats.nodes = programStats.depth = programStats.time =
8230                 programStats.score = programStats.got_only_move = 0;
8231                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8232
8233                 if(cps->lastPing != cps->lastPong) {
8234                     savedMessage = message; // args for deferred call
8235                     savedState = cps;
8236                     ScheduleDelayedEvent(DeferredBookMove, 10);
8237                     return;
8238                 }
8239                 goto FakeBookMove;
8240         }
8241
8242         return;
8243     }
8244
8245     /* Set special modes for chess engines.  Later something general
8246      *  could be added here; for now there is just one kludge feature,
8247      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8248      *  when "xboard" is given as an interactive command.
8249      */
8250     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8251         cps->useSigint = FALSE;
8252         cps->useSigterm = FALSE;
8253     }
8254     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8255       ParseFeatures(message+8, cps);
8256       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8257     }
8258
8259     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8260                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8261       int dummy, s=6; char buf[MSG_SIZ];
8262       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8263       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8264       if(startedFromSetupPosition) return;
8265       ParseFEN(boards[0], &dummy, message+s);
8266       DrawPosition(TRUE, boards[0]);
8267       startedFromSetupPosition = TRUE;
8268       return;
8269     }
8270     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8271      * want this, I was asked to put it in, and obliged.
8272      */
8273     if (!strncmp(message, "setboard ", 9)) {
8274         Board initial_position;
8275
8276         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8277
8278         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8279             DisplayError(_("Bad FEN received from engine"), 0);
8280             return ;
8281         } else {
8282            Reset(TRUE, FALSE);
8283            CopyBoard(boards[0], initial_position);
8284            initialRulePlies = FENrulePlies;
8285            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8286            else gameMode = MachinePlaysBlack;
8287            DrawPosition(FALSE, boards[currentMove]);
8288         }
8289         return;
8290     }
8291
8292     /*
8293      * Look for communication commands
8294      */
8295     if (!strncmp(message, "telluser ", 9)) {
8296         if(message[9] == '\\' && message[10] == '\\')
8297             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8298         PlayTellSound();
8299         DisplayNote(message + 9);
8300         return;
8301     }
8302     if (!strncmp(message, "tellusererror ", 14)) {
8303         cps->userError = 1;
8304         if(message[14] == '\\' && message[15] == '\\')
8305             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8306         PlayTellSound();
8307         DisplayError(message + 14, 0);
8308         return;
8309     }
8310     if (!strncmp(message, "tellopponent ", 13)) {
8311       if (appData.icsActive) {
8312         if (loggedOn) {
8313           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8314           SendToICS(buf1);
8315         }
8316       } else {
8317         DisplayNote(message + 13);
8318       }
8319       return;
8320     }
8321     if (!strncmp(message, "tellothers ", 11)) {
8322       if (appData.icsActive) {
8323         if (loggedOn) {
8324           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8325           SendToICS(buf1);
8326         }
8327       }
8328       return;
8329     }
8330     if (!strncmp(message, "tellall ", 8)) {
8331       if (appData.icsActive) {
8332         if (loggedOn) {
8333           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8334           SendToICS(buf1);
8335         }
8336       } else {
8337         DisplayNote(message + 8);
8338       }
8339       return;
8340     }
8341     if (strncmp(message, "warning", 7) == 0) {
8342         /* Undocumented feature, use tellusererror in new code */
8343         DisplayError(message, 0);
8344         return;
8345     }
8346     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8347         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8348         strcat(realname, " query");
8349         AskQuestion(realname, buf2, buf1, cps->pr);
8350         return;
8351     }
8352     /* Commands from the engine directly to ICS.  We don't allow these to be
8353      *  sent until we are logged on. Crafty kibitzes have been known to
8354      *  interfere with the login process.
8355      */
8356     if (loggedOn) {
8357         if (!strncmp(message, "tellics ", 8)) {
8358             SendToICS(message + 8);
8359             SendToICS("\n");
8360             return;
8361         }
8362         if (!strncmp(message, "tellicsnoalias ", 15)) {
8363             SendToICS(ics_prefix);
8364             SendToICS(message + 15);
8365             SendToICS("\n");
8366             return;
8367         }
8368         /* The following are for backward compatibility only */
8369         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8370             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8371             SendToICS(ics_prefix);
8372             SendToICS(message);
8373             SendToICS("\n");
8374             return;
8375         }
8376     }
8377     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8378         return;
8379     }
8380     /*
8381      * If the move is illegal, cancel it and redraw the board.
8382      * Also deal with other error cases.  Matching is rather loose
8383      * here to accommodate engines written before the spec.
8384      */
8385     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8386         strncmp(message, "Error", 5) == 0) {
8387         if (StrStr(message, "name") ||
8388             StrStr(message, "rating") || StrStr(message, "?") ||
8389             StrStr(message, "result") || StrStr(message, "board") ||
8390             StrStr(message, "bk") || StrStr(message, "computer") ||
8391             StrStr(message, "variant") || StrStr(message, "hint") ||
8392             StrStr(message, "random") || StrStr(message, "depth") ||
8393             StrStr(message, "accepted")) {
8394             return;
8395         }
8396         if (StrStr(message, "protover")) {
8397           /* Program is responding to input, so it's apparently done
8398              initializing, and this error message indicates it is
8399              protocol version 1.  So we don't need to wait any longer
8400              for it to initialize and send feature commands. */
8401           FeatureDone(cps, 1);
8402           cps->protocolVersion = 1;
8403           return;
8404         }
8405         cps->maybeThinking = FALSE;
8406
8407         if (StrStr(message, "draw")) {
8408             /* Program doesn't have "draw" command */
8409             cps->sendDrawOffers = 0;
8410             return;
8411         }
8412         if (cps->sendTime != 1 &&
8413             (StrStr(message, "time") || StrStr(message, "otim"))) {
8414           /* Program apparently doesn't have "time" or "otim" command */
8415           cps->sendTime = 0;
8416           return;
8417         }
8418         if (StrStr(message, "analyze")) {
8419             cps->analysisSupport = FALSE;
8420             cps->analyzing = FALSE;
8421 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8422             EditGameEvent(); // [HGM] try to preserve loaded game
8423             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8424             DisplayError(buf2, 0);
8425             return;
8426         }
8427         if (StrStr(message, "(no matching move)st")) {
8428           /* Special kludge for GNU Chess 4 only */
8429           cps->stKludge = TRUE;
8430           SendTimeControl(cps, movesPerSession, timeControl,
8431                           timeIncrement, appData.searchDepth,
8432                           searchTime);
8433           return;
8434         }
8435         if (StrStr(message, "(no matching move)sd")) {
8436           /* Special kludge for GNU Chess 4 only */
8437           cps->sdKludge = TRUE;
8438           SendTimeControl(cps, movesPerSession, timeControl,
8439                           timeIncrement, appData.searchDepth,
8440                           searchTime);
8441           return;
8442         }
8443         if (!StrStr(message, "llegal")) {
8444             return;
8445         }
8446         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8447             gameMode == IcsIdle) return;
8448         if (forwardMostMove <= backwardMostMove) return;
8449         if (pausing) PauseEvent();
8450       if(appData.forceIllegal) {
8451             // [HGM] illegal: machine refused move; force position after move into it
8452           SendToProgram("force\n", cps);
8453           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8454                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8455                 // when black is to move, while there might be nothing on a2 or black
8456                 // might already have the move. So send the board as if white has the move.
8457                 // But first we must change the stm of the engine, as it refused the last move
8458                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8459                 if(WhiteOnMove(forwardMostMove)) {
8460                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8461                     SendBoard(cps, forwardMostMove); // kludgeless board
8462                 } else {
8463                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8464                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8465                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8466                 }
8467           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8468             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8469                  gameMode == TwoMachinesPlay)
8470               SendToProgram("go\n", cps);
8471             return;
8472       } else
8473         if (gameMode == PlayFromGameFile) {
8474             /* Stop reading this game file */
8475             gameMode = EditGame;
8476             ModeHighlight();
8477         }
8478         /* [HGM] illegal-move claim should forfeit game when Xboard */
8479         /* only passes fully legal moves                            */
8480         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8481             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8482                                 "False illegal-move claim", GE_XBOARD );
8483             return; // do not take back move we tested as valid
8484         }
8485         currentMove = forwardMostMove-1;
8486         DisplayMove(currentMove-1); /* before DisplayMoveError */
8487         SwitchClocks(forwardMostMove-1); // [HGM] race
8488         DisplayBothClocks();
8489         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8490                 parseList[currentMove], _(cps->which));
8491         DisplayMoveError(buf1);
8492         DrawPosition(FALSE, boards[currentMove]);
8493
8494         SetUserThinkingEnables();
8495         return;
8496     }
8497     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8498         /* Program has a broken "time" command that
8499            outputs a string not ending in newline.
8500            Don't use it. */
8501         cps->sendTime = 0;
8502     }
8503
8504     /*
8505      * If chess program startup fails, exit with an error message.
8506      * Attempts to recover here are futile. [HGM] Well, we try anyway
8507      */
8508     if ((StrStr(message, "unknown host") != NULL)
8509         || (StrStr(message, "No remote directory") != NULL)
8510         || (StrStr(message, "not found") != NULL)
8511         || (StrStr(message, "No such file") != NULL)
8512         || (StrStr(message, "can't alloc") != NULL)
8513         || (StrStr(message, "Permission denied") != NULL)) {
8514
8515         cps->maybeThinking = FALSE;
8516         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8517                 _(cps->which), cps->program, cps->host, message);
8518         RemoveInputSource(cps->isr);
8519         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8520             cps->isr = NULL;
8521             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8522             cps->pr = NoProc; 
8523             if(cps == &first) {
8524                 appData.noChessProgram = TRUE;
8525                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8526                 gameMode = BeginningOfGame; ModeHighlight();
8527                 SetNCPMode();
8528             }
8529             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8530             DisplayMessage("", ""); // erase waiting message
8531             DisplayError(buf1, 0);
8532         }
8533         return;
8534     }
8535
8536     /*
8537      * Look for hint output
8538      */
8539     if (sscanf(message, "Hint: %s", buf1) == 1) {
8540         if (cps == &first && hintRequested) {
8541             hintRequested = FALSE;
8542             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8543                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8544                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8545                                     PosFlags(forwardMostMove),
8546                                     fromY, fromX, toY, toX, promoChar, buf1);
8547                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8548                 DisplayInformation(buf2);
8549             } else {
8550                 /* Hint move could not be parsed!? */
8551               snprintf(buf2, sizeof(buf2),
8552                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8553                         buf1, _(cps->which));
8554                 DisplayError(buf2, 0);
8555             }
8556         } else {
8557           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8558         }
8559         return;
8560     }
8561
8562     /*
8563      * Ignore other messages if game is not in progress
8564      */
8565     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8566         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8567
8568     /*
8569      * look for win, lose, draw, or draw offer
8570      */
8571     if (strncmp(message, "1-0", 3) == 0) {
8572         char *p, *q, *r = "";
8573         p = strchr(message, '{');
8574         if (p) {
8575             q = strchr(p, '}');
8576             if (q) {
8577                 *q = NULLCHAR;
8578                 r = p + 1;
8579             }
8580         }
8581         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8582         return;
8583     } else if (strncmp(message, "0-1", 3) == 0) {
8584         char *p, *q, *r = "";
8585         p = strchr(message, '{');
8586         if (p) {
8587             q = strchr(p, '}');
8588             if (q) {
8589                 *q = NULLCHAR;
8590                 r = p + 1;
8591             }
8592         }
8593         /* Kludge for Arasan 4.1 bug */
8594         if (strcmp(r, "Black resigns") == 0) {
8595             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8596             return;
8597         }
8598         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8599         return;
8600     } else if (strncmp(message, "1/2", 3) == 0) {
8601         char *p, *q, *r = "";
8602         p = strchr(message, '{');
8603         if (p) {
8604             q = strchr(p, '}');
8605             if (q) {
8606                 *q = NULLCHAR;
8607                 r = p + 1;
8608             }
8609         }
8610
8611         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8612         return;
8613
8614     } else if (strncmp(message, "White resign", 12) == 0) {
8615         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8616         return;
8617     } else if (strncmp(message, "Black resign", 12) == 0) {
8618         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8619         return;
8620     } else if (strncmp(message, "White matches", 13) == 0 ||
8621                strncmp(message, "Black matches", 13) == 0   ) {
8622         /* [HGM] ignore GNUShogi noises */
8623         return;
8624     } else if (strncmp(message, "White", 5) == 0 &&
8625                message[5] != '(' &&
8626                StrStr(message, "Black") == NULL) {
8627         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8628         return;
8629     } else if (strncmp(message, "Black", 5) == 0 &&
8630                message[5] != '(') {
8631         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strcmp(message, "resign") == 0 ||
8634                strcmp(message, "computer resigns") == 0) {
8635         switch (gameMode) {
8636           case MachinePlaysBlack:
8637           case IcsPlayingBlack:
8638             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8639             break;
8640           case MachinePlaysWhite:
8641           case IcsPlayingWhite:
8642             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8643             break;
8644           case TwoMachinesPlay:
8645             if (cps->twoMachinesColor[0] == 'w')
8646               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8647             else
8648               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8649             break;
8650           default:
8651             /* can't happen */
8652             break;
8653         }
8654         return;
8655     } else if (strncmp(message, "opponent mates", 14) == 0) {
8656         switch (gameMode) {
8657           case MachinePlaysBlack:
8658           case IcsPlayingBlack:
8659             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8660             break;
8661           case MachinePlaysWhite:
8662           case IcsPlayingWhite:
8663             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8664             break;
8665           case TwoMachinesPlay:
8666             if (cps->twoMachinesColor[0] == 'w')
8667               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8668             else
8669               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8670             break;
8671           default:
8672             /* can't happen */
8673             break;
8674         }
8675         return;
8676     } else if (strncmp(message, "computer mates", 14) == 0) {
8677         switch (gameMode) {
8678           case MachinePlaysBlack:
8679           case IcsPlayingBlack:
8680             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8681             break;
8682           case MachinePlaysWhite:
8683           case IcsPlayingWhite:
8684             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8685             break;
8686           case TwoMachinesPlay:
8687             if (cps->twoMachinesColor[0] == 'w')
8688               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8689             else
8690               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8691             break;
8692           default:
8693             /* can't happen */
8694             break;
8695         }
8696         return;
8697     } else if (strncmp(message, "checkmate", 9) == 0) {
8698         if (WhiteOnMove(forwardMostMove)) {
8699             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8700         } else {
8701             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8702         }
8703         return;
8704     } else if (strstr(message, "Draw") != NULL ||
8705                strstr(message, "game is a draw") != NULL) {
8706         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8707         return;
8708     } else if (strstr(message, "offer") != NULL &&
8709                strstr(message, "draw") != NULL) {
8710 #if ZIPPY
8711         if (appData.zippyPlay && first.initDone) {
8712             /* Relay offer to ICS */
8713             SendToICS(ics_prefix);
8714             SendToICS("draw\n");
8715         }
8716 #endif
8717         cps->offeredDraw = 2; /* valid until this engine moves twice */
8718         if (gameMode == TwoMachinesPlay) {
8719             if (cps->other->offeredDraw) {
8720                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8721             /* [HGM] in two-machine mode we delay relaying draw offer      */
8722             /* until after we also have move, to see if it is really claim */
8723             }
8724         } else if (gameMode == MachinePlaysWhite ||
8725                    gameMode == MachinePlaysBlack) {
8726           if (userOfferedDraw) {
8727             DisplayInformation(_("Machine accepts your draw offer"));
8728             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8729           } else {
8730             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8731           }
8732         }
8733     }
8734
8735
8736     /*
8737      * Look for thinking output
8738      */
8739     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8740           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8741                                 ) {
8742         int plylev, mvleft, mvtot, curscore, time;
8743         char mvname[MOVE_LEN];
8744         u64 nodes; // [DM]
8745         char plyext;
8746         int ignore = FALSE;
8747         int prefixHint = FALSE;
8748         mvname[0] = NULLCHAR;
8749
8750         switch (gameMode) {
8751           case MachinePlaysBlack:
8752           case IcsPlayingBlack:
8753             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8754             break;
8755           case MachinePlaysWhite:
8756           case IcsPlayingWhite:
8757             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8758             break;
8759           case AnalyzeMode:
8760           case AnalyzeFile:
8761             break;
8762           case IcsObserving: /* [DM] icsEngineAnalyze */
8763             if (!appData.icsEngineAnalyze) ignore = TRUE;
8764             break;
8765           case TwoMachinesPlay:
8766             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8767                 ignore = TRUE;
8768             }
8769             break;
8770           default:
8771             ignore = TRUE;
8772             break;
8773         }
8774
8775         if (!ignore) {
8776             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8777             buf1[0] = NULLCHAR;
8778             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8779                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8780
8781                 if (plyext != ' ' && plyext != '\t') {
8782                     time *= 100;
8783                 }
8784
8785                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8786                 if( cps->scoreIsAbsolute &&
8787                     ( gameMode == MachinePlaysBlack ||
8788                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8789                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8790                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8791                      !WhiteOnMove(currentMove)
8792                     ) )
8793                 {
8794                     curscore = -curscore;
8795                 }
8796
8797                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8798
8799                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8800                         char buf[MSG_SIZ];
8801                         FILE *f;
8802                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8803                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8804                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8805                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8806                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8807                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8808                                 fclose(f);
8809                         } else DisplayError(_("failed writing PV"), 0);
8810                 }
8811
8812                 tempStats.depth = plylev;
8813                 tempStats.nodes = nodes;
8814                 tempStats.time = time;
8815                 tempStats.score = curscore;
8816                 tempStats.got_only_move = 0;
8817
8818                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8819                         int ticklen;
8820
8821                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8822                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8823                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8824                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8825                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8826                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8827                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8828                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8829                 }
8830
8831                 /* Buffer overflow protection */
8832                 if (pv[0] != NULLCHAR) {
8833                     if (strlen(pv) >= sizeof(tempStats.movelist)
8834                         && appData.debugMode) {
8835                         fprintf(debugFP,
8836                                 "PV is too long; using the first %u bytes.\n",
8837                                 (unsigned) sizeof(tempStats.movelist) - 1);
8838                     }
8839
8840                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8841                 } else {
8842                     sprintf(tempStats.movelist, " no PV\n");
8843                 }
8844
8845                 if (tempStats.seen_stat) {
8846                     tempStats.ok_to_send = 1;
8847                 }
8848
8849                 if (strchr(tempStats.movelist, '(') != NULL) {
8850                     tempStats.line_is_book = 1;
8851                     tempStats.nr_moves = 0;
8852                     tempStats.moves_left = 0;
8853                 } else {
8854                     tempStats.line_is_book = 0;
8855                 }
8856
8857                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8858                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8859
8860                 SendProgramStatsToFrontend( cps, &tempStats );
8861
8862                 /*
8863                     [AS] Protect the thinkOutput buffer from overflow... this
8864                     is only useful if buf1 hasn't overflowed first!
8865                 */
8866                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8867                          plylev,
8868                          (gameMode == TwoMachinesPlay ?
8869                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8870                          ((double) curscore) / 100.0,
8871                          prefixHint ? lastHint : "",
8872                          prefixHint ? " " : "" );
8873
8874                 if( buf1[0] != NULLCHAR ) {
8875                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8876
8877                     if( strlen(pv) > max_len ) {
8878                         if( appData.debugMode) {
8879                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8880                         }
8881                         pv[max_len+1] = '\0';
8882                     }
8883
8884                     strcat( thinkOutput, pv);
8885                 }
8886
8887                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8888                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8889                     DisplayMove(currentMove - 1);
8890                 }
8891                 return;
8892
8893             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8894                 /* crafty (9.25+) says "(only move) <move>"
8895                  * if there is only 1 legal move
8896                  */
8897                 sscanf(p, "(only move) %s", buf1);
8898                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8899                 sprintf(programStats.movelist, "%s (only move)", buf1);
8900                 programStats.depth = 1;
8901                 programStats.nr_moves = 1;
8902                 programStats.moves_left = 1;
8903                 programStats.nodes = 1;
8904                 programStats.time = 1;
8905                 programStats.got_only_move = 1;
8906
8907                 /* Not really, but we also use this member to
8908                    mean "line isn't going to change" (Crafty
8909                    isn't searching, so stats won't change) */
8910                 programStats.line_is_book = 1;
8911
8912                 SendProgramStatsToFrontend( cps, &programStats );
8913
8914                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8915                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8916                     DisplayMove(currentMove - 1);
8917                 }
8918                 return;
8919             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8920                               &time, &nodes, &plylev, &mvleft,
8921                               &mvtot, mvname) >= 5) {
8922                 /* The stat01: line is from Crafty (9.29+) in response
8923                    to the "." command */
8924                 programStats.seen_stat = 1;
8925                 cps->maybeThinking = TRUE;
8926
8927                 if (programStats.got_only_move || !appData.periodicUpdates)
8928                   return;
8929
8930                 programStats.depth = plylev;
8931                 programStats.time = time;
8932                 programStats.nodes = nodes;
8933                 programStats.moves_left = mvleft;
8934                 programStats.nr_moves = mvtot;
8935                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8936                 programStats.ok_to_send = 1;
8937                 programStats.movelist[0] = '\0';
8938
8939                 SendProgramStatsToFrontend( cps, &programStats );
8940
8941                 return;
8942
8943             } else if (strncmp(message,"++",2) == 0) {
8944                 /* Crafty 9.29+ outputs this */
8945                 programStats.got_fail = 2;
8946                 return;
8947
8948             } else if (strncmp(message,"--",2) == 0) {
8949                 /* Crafty 9.29+ outputs this */
8950                 programStats.got_fail = 1;
8951                 return;
8952
8953             } else if (thinkOutput[0] != NULLCHAR &&
8954                        strncmp(message, "    ", 4) == 0) {
8955                 unsigned message_len;
8956
8957                 p = message;
8958                 while (*p && *p == ' ') p++;
8959
8960                 message_len = strlen( p );
8961
8962                 /* [AS] Avoid buffer overflow */
8963                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8964                     strcat(thinkOutput, " ");
8965                     strcat(thinkOutput, p);
8966                 }
8967
8968                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8969                     strcat(programStats.movelist, " ");
8970                     strcat(programStats.movelist, p);
8971                 }
8972
8973                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8974                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8975                     DisplayMove(currentMove - 1);
8976                 }
8977                 return;
8978             }
8979         }
8980         else {
8981             buf1[0] = NULLCHAR;
8982
8983             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8984                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8985             {
8986                 ChessProgramStats cpstats;
8987
8988                 if (plyext != ' ' && plyext != '\t') {
8989                     time *= 100;
8990                 }
8991
8992                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8993                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8994                     curscore = -curscore;
8995                 }
8996
8997                 cpstats.depth = plylev;
8998                 cpstats.nodes = nodes;
8999                 cpstats.time = time;
9000                 cpstats.score = curscore;
9001                 cpstats.got_only_move = 0;
9002                 cpstats.movelist[0] = '\0';
9003
9004                 if (buf1[0] != NULLCHAR) {
9005                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9006                 }
9007
9008                 cpstats.ok_to_send = 0;
9009                 cpstats.line_is_book = 0;
9010                 cpstats.nr_moves = 0;
9011                 cpstats.moves_left = 0;
9012
9013                 SendProgramStatsToFrontend( cps, &cpstats );
9014             }
9015         }
9016     }
9017 }
9018
9019
9020 /* Parse a game score from the character string "game", and
9021    record it as the history of the current game.  The game
9022    score is NOT assumed to start from the standard position.
9023    The display is not updated in any way.
9024    */
9025 void
9026 ParseGameHistory (char *game)
9027 {
9028     ChessMove moveType;
9029     int fromX, fromY, toX, toY, boardIndex;
9030     char promoChar;
9031     char *p, *q;
9032     char buf[MSG_SIZ];
9033
9034     if (appData.debugMode)
9035       fprintf(debugFP, "Parsing game history: %s\n", game);
9036
9037     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9038     gameInfo.site = StrSave(appData.icsHost);
9039     gameInfo.date = PGNDate();
9040     gameInfo.round = StrSave("-");
9041
9042     /* Parse out names of players */
9043     while (*game == ' ') game++;
9044     p = buf;
9045     while (*game != ' ') *p++ = *game++;
9046     *p = NULLCHAR;
9047     gameInfo.white = StrSave(buf);
9048     while (*game == ' ') game++;
9049     p = buf;
9050     while (*game != ' ' && *game != '\n') *p++ = *game++;
9051     *p = NULLCHAR;
9052     gameInfo.black = StrSave(buf);
9053
9054     /* Parse moves */
9055     boardIndex = blackPlaysFirst ? 1 : 0;
9056     yynewstr(game);
9057     for (;;) {
9058         yyboardindex = boardIndex;
9059         moveType = (ChessMove) Myylex();
9060         switch (moveType) {
9061           case IllegalMove:             /* maybe suicide chess, etc. */
9062   if (appData.debugMode) {
9063     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9064     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9065     setbuf(debugFP, NULL);
9066   }
9067           case WhitePromotion:
9068           case BlackPromotion:
9069           case WhiteNonPromotion:
9070           case BlackNonPromotion:
9071           case NormalMove:
9072           case WhiteCapturesEnPassant:
9073           case BlackCapturesEnPassant:
9074           case WhiteKingSideCastle:
9075           case WhiteQueenSideCastle:
9076           case BlackKingSideCastle:
9077           case BlackQueenSideCastle:
9078           case WhiteKingSideCastleWild:
9079           case WhiteQueenSideCastleWild:
9080           case BlackKingSideCastleWild:
9081           case BlackQueenSideCastleWild:
9082           /* PUSH Fabien */
9083           case WhiteHSideCastleFR:
9084           case WhiteASideCastleFR:
9085           case BlackHSideCastleFR:
9086           case BlackASideCastleFR:
9087           /* POP Fabien */
9088             fromX = currentMoveString[0] - AAA;
9089             fromY = currentMoveString[1] - ONE;
9090             toX = currentMoveString[2] - AAA;
9091             toY = currentMoveString[3] - ONE;
9092             promoChar = currentMoveString[4];
9093             break;
9094           case WhiteDrop:
9095           case BlackDrop:
9096             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9097             fromX = moveType == WhiteDrop ?
9098               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9099             (int) CharToPiece(ToLower(currentMoveString[0]));
9100             fromY = DROP_RANK;
9101             toX = currentMoveString[2] - AAA;
9102             toY = currentMoveString[3] - ONE;
9103             promoChar = NULLCHAR;
9104             break;
9105           case AmbiguousMove:
9106             /* bug? */
9107             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9108   if (appData.debugMode) {
9109     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9110     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9111     setbuf(debugFP, NULL);
9112   }
9113             DisplayError(buf, 0);
9114             return;
9115           case ImpossibleMove:
9116             /* bug? */
9117             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9118   if (appData.debugMode) {
9119     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9120     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9121     setbuf(debugFP, NULL);
9122   }
9123             DisplayError(buf, 0);
9124             return;
9125           case EndOfFile:
9126             if (boardIndex < backwardMostMove) {
9127                 /* Oops, gap.  How did that happen? */
9128                 DisplayError(_("Gap in move list"), 0);
9129                 return;
9130             }
9131             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9132             if (boardIndex > forwardMostMove) {
9133                 forwardMostMove = boardIndex;
9134             }
9135             return;
9136           case ElapsedTime:
9137             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9138                 strcat(parseList[boardIndex-1], " ");
9139                 strcat(parseList[boardIndex-1], yy_text);
9140             }
9141             continue;
9142           case Comment:
9143           case PGNTag:
9144           case NAG:
9145           default:
9146             /* ignore */
9147             continue;
9148           case WhiteWins:
9149           case BlackWins:
9150           case GameIsDrawn:
9151           case GameUnfinished:
9152             if (gameMode == IcsExamining) {
9153                 if (boardIndex < backwardMostMove) {
9154                     /* Oops, gap.  How did that happen? */
9155                     return;
9156                 }
9157                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9158                 return;
9159             }
9160             gameInfo.result = moveType;
9161             p = strchr(yy_text, '{');
9162             if (p == NULL) p = strchr(yy_text, '(');
9163             if (p == NULL) {
9164                 p = yy_text;
9165                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9166             } else {
9167                 q = strchr(p, *p == '{' ? '}' : ')');
9168                 if (q != NULL) *q = NULLCHAR;
9169                 p++;
9170             }
9171             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9172             gameInfo.resultDetails = StrSave(p);
9173             continue;
9174         }
9175         if (boardIndex >= forwardMostMove &&
9176             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9177             backwardMostMove = blackPlaysFirst ? 1 : 0;
9178             return;
9179         }
9180         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9181                                  fromY, fromX, toY, toX, promoChar,
9182                                  parseList[boardIndex]);
9183         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9184         /* currentMoveString is set as a side-effect of yylex */
9185         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9186         strcat(moveList[boardIndex], "\n");
9187         boardIndex++;
9188         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9189         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9190           case MT_NONE:
9191           case MT_STALEMATE:
9192           default:
9193             break;
9194           case MT_CHECK:
9195             if(gameInfo.variant != VariantShogi)
9196                 strcat(parseList[boardIndex - 1], "+");
9197             break;
9198           case MT_CHECKMATE:
9199           case MT_STAINMATE:
9200             strcat(parseList[boardIndex - 1], "#");
9201             break;
9202         }
9203     }
9204 }
9205
9206
9207 /* Apply a move to the given board  */
9208 void
9209 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9210 {
9211   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9212   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9213
9214     /* [HGM] compute & store e.p. status and castling rights for new position */
9215     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9216
9217       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9218       oldEP = (signed char)board[EP_STATUS];
9219       board[EP_STATUS] = EP_NONE;
9220
9221   if (fromY == DROP_RANK) {
9222         /* must be first */
9223         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9224             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9225             return;
9226         }
9227         piece = board[toY][toX] = (ChessSquare) fromX;
9228   } else {
9229       int i;
9230
9231       if( board[toY][toX] != EmptySquare )
9232            board[EP_STATUS] = EP_CAPTURE;
9233
9234       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9235            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9236                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9237       } else
9238       if( board[fromY][fromX] == WhitePawn ) {
9239            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9240                board[EP_STATUS] = EP_PAWN_MOVE;
9241            if( toY-fromY==2) {
9242                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9243                         gameInfo.variant != VariantBerolina || toX < fromX)
9244                       board[EP_STATUS] = toX | berolina;
9245                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9246                         gameInfo.variant != VariantBerolina || toX > fromX)
9247                       board[EP_STATUS] = toX;
9248            }
9249       } else
9250       if( board[fromY][fromX] == BlackPawn ) {
9251            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9252                board[EP_STATUS] = EP_PAWN_MOVE;
9253            if( toY-fromY== -2) {
9254                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9255                         gameInfo.variant != VariantBerolina || toX < fromX)
9256                       board[EP_STATUS] = toX | berolina;
9257                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9258                         gameInfo.variant != VariantBerolina || toX > fromX)
9259                       board[EP_STATUS] = toX;
9260            }
9261        }
9262
9263        for(i=0; i<nrCastlingRights; i++) {
9264            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9265               board[CASTLING][i] == toX   && castlingRank[i] == toY
9266              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9267        }
9268
9269      if (fromX == toX && fromY == toY) return;
9270
9271      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9272      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9273      if(gameInfo.variant == VariantKnightmate)
9274          king += (int) WhiteUnicorn - (int) WhiteKing;
9275
9276     /* Code added by Tord: */
9277     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9278     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9279         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9280       board[fromY][fromX] = EmptySquare;
9281       board[toY][toX] = EmptySquare;
9282       if((toX > fromX) != (piece == WhiteRook)) {
9283         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9284       } else {
9285         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9286       }
9287     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9288                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9289       board[fromY][fromX] = EmptySquare;
9290       board[toY][toX] = EmptySquare;
9291       if((toX > fromX) != (piece == BlackRook)) {
9292         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9293       } else {
9294         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9295       }
9296     /* End of code added by Tord */
9297
9298     } else if (board[fromY][fromX] == king
9299         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9300         && toY == fromY && toX > fromX+1) {
9301         board[fromY][fromX] = EmptySquare;
9302         board[toY][toX] = king;
9303         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9304         board[fromY][BOARD_RGHT-1] = EmptySquare;
9305     } else if (board[fromY][fromX] == king
9306         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9307                && toY == fromY && toX < fromX-1) {
9308         board[fromY][fromX] = EmptySquare;
9309         board[toY][toX] = king;
9310         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9311         board[fromY][BOARD_LEFT] = EmptySquare;
9312     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9313                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9314                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9315                ) {
9316         /* white pawn promotion */
9317         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9318         if(gameInfo.variant==VariantBughouse ||
9319            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9320             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9321         board[fromY][fromX] = EmptySquare;
9322     } else if ((fromY >= BOARD_HEIGHT>>1)
9323                && (toX != fromX)
9324                && gameInfo.variant != VariantXiangqi
9325                && gameInfo.variant != VariantBerolina
9326                && (board[fromY][fromX] == WhitePawn)
9327                && (board[toY][toX] == EmptySquare)) {
9328         board[fromY][fromX] = EmptySquare;
9329         board[toY][toX] = WhitePawn;
9330         captured = board[toY - 1][toX];
9331         board[toY - 1][toX] = EmptySquare;
9332     } else if ((fromY == BOARD_HEIGHT-4)
9333                && (toX == fromX)
9334                && gameInfo.variant == VariantBerolina
9335                && (board[fromY][fromX] == WhitePawn)
9336                && (board[toY][toX] == EmptySquare)) {
9337         board[fromY][fromX] = EmptySquare;
9338         board[toY][toX] = WhitePawn;
9339         if(oldEP & EP_BEROLIN_A) {
9340                 captured = board[fromY][fromX-1];
9341                 board[fromY][fromX-1] = EmptySquare;
9342         }else{  captured = board[fromY][fromX+1];
9343                 board[fromY][fromX+1] = EmptySquare;
9344         }
9345     } else if (board[fromY][fromX] == king
9346         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9347                && toY == fromY && toX > fromX+1) {
9348         board[fromY][fromX] = EmptySquare;
9349         board[toY][toX] = king;
9350         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9351         board[fromY][BOARD_RGHT-1] = EmptySquare;
9352     } else if (board[fromY][fromX] == king
9353         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9354                && toY == fromY && toX < fromX-1) {
9355         board[fromY][fromX] = EmptySquare;
9356         board[toY][toX] = king;
9357         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9358         board[fromY][BOARD_LEFT] = EmptySquare;
9359     } else if (fromY == 7 && fromX == 3
9360                && board[fromY][fromX] == BlackKing
9361                && toY == 7 && toX == 5) {
9362         board[fromY][fromX] = EmptySquare;
9363         board[toY][toX] = BlackKing;
9364         board[fromY][7] = EmptySquare;
9365         board[toY][4] = BlackRook;
9366     } else if (fromY == 7 && fromX == 3
9367                && board[fromY][fromX] == BlackKing
9368                && toY == 7 && toX == 1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = BlackKing;
9371         board[fromY][0] = EmptySquare;
9372         board[toY][2] = BlackRook;
9373     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9374                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375                && toY < promoRank && promoChar
9376                ) {
9377         /* black pawn promotion */
9378         board[toY][toX] = CharToPiece(ToLower(promoChar));
9379         if(gameInfo.variant==VariantBughouse ||
9380            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9381             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9382         board[fromY][fromX] = EmptySquare;
9383     } else if ((fromY < BOARD_HEIGHT>>1)
9384                && (toX != fromX)
9385                && gameInfo.variant != VariantXiangqi
9386                && gameInfo.variant != VariantBerolina
9387                && (board[fromY][fromX] == BlackPawn)
9388                && (board[toY][toX] == EmptySquare)) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = BlackPawn;
9391         captured = board[toY + 1][toX];
9392         board[toY + 1][toX] = EmptySquare;
9393     } else if ((fromY == 3)
9394                && (toX == fromX)
9395                && gameInfo.variant == VariantBerolina
9396                && (board[fromY][fromX] == BlackPawn)
9397                && (board[toY][toX] == EmptySquare)) {
9398         board[fromY][fromX] = EmptySquare;
9399         board[toY][toX] = BlackPawn;
9400         if(oldEP & EP_BEROLIN_A) {
9401                 captured = board[fromY][fromX-1];
9402                 board[fromY][fromX-1] = EmptySquare;
9403         }else{  captured = board[fromY][fromX+1];
9404                 board[fromY][fromX+1] = EmptySquare;
9405         }
9406     } else {
9407         board[toY][toX] = board[fromY][fromX];
9408         board[fromY][fromX] = EmptySquare;
9409     }
9410   }
9411
9412     if (gameInfo.holdingsWidth != 0) {
9413
9414       /* !!A lot more code needs to be written to support holdings  */
9415       /* [HGM] OK, so I have written it. Holdings are stored in the */
9416       /* penultimate board files, so they are automaticlly stored   */
9417       /* in the game history.                                       */
9418       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9419                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9420         /* Delete from holdings, by decreasing count */
9421         /* and erasing image if necessary            */
9422         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9423         if(p < (int) BlackPawn) { /* white drop */
9424              p -= (int)WhitePawn;
9425                  p = PieceToNumber((ChessSquare)p);
9426              if(p >= gameInfo.holdingsSize) p = 0;
9427              if(--board[p][BOARD_WIDTH-2] <= 0)
9428                   board[p][BOARD_WIDTH-1] = EmptySquare;
9429              if((int)board[p][BOARD_WIDTH-2] < 0)
9430                         board[p][BOARD_WIDTH-2] = 0;
9431         } else {                  /* black drop */
9432              p -= (int)BlackPawn;
9433                  p = PieceToNumber((ChessSquare)p);
9434              if(p >= gameInfo.holdingsSize) p = 0;
9435              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9436                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9437              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9438                         board[BOARD_HEIGHT-1-p][1] = 0;
9439         }
9440       }
9441       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9442           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9443         /* [HGM] holdings: Add to holdings, if holdings exist */
9444         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9445                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9446                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9447         }
9448         p = (int) captured;
9449         if (p >= (int) BlackPawn) {
9450           p -= (int)BlackPawn;
9451           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9452                   /* in Shogi restore piece to its original  first */
9453                   captured = (ChessSquare) (DEMOTED captured);
9454                   p = DEMOTED p;
9455           }
9456           p = PieceToNumber((ChessSquare)p);
9457           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9458           board[p][BOARD_WIDTH-2]++;
9459           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9460         } else {
9461           p -= (int)WhitePawn;
9462           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9463                   captured = (ChessSquare) (DEMOTED captured);
9464                   p = DEMOTED p;
9465           }
9466           p = PieceToNumber((ChessSquare)p);
9467           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9468           board[BOARD_HEIGHT-1-p][1]++;
9469           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9470         }
9471       }
9472     } else if (gameInfo.variant == VariantAtomic) {
9473       if (captured != EmptySquare) {
9474         int y, x;
9475         for (y = toY-1; y <= toY+1; y++) {
9476           for (x = toX-1; x <= toX+1; x++) {
9477             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9478                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9479               board[y][x] = EmptySquare;
9480             }
9481           }
9482         }
9483         board[toY][toX] = EmptySquare;
9484       }
9485     }
9486     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9487         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9488     } else
9489     if(promoChar == '+') {
9490         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9491         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9492     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9493         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9494         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9495            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9496         board[toY][toX] = newPiece;
9497     }
9498     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9499                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9500         // [HGM] superchess: take promotion piece out of holdings
9501         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9502         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9503             if(!--board[k][BOARD_WIDTH-2])
9504                 board[k][BOARD_WIDTH-1] = EmptySquare;
9505         } else {
9506             if(!--board[BOARD_HEIGHT-1-k][1])
9507                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9508         }
9509     }
9510
9511 }
9512
9513 /* Updates forwardMostMove */
9514 void
9515 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9516 {
9517 //    forwardMostMove++; // [HGM] bare: moved downstream
9518
9519     (void) CoordsToAlgebraic(boards[forwardMostMove],
9520                              PosFlags(forwardMostMove),
9521                              fromY, fromX, toY, toX, promoChar,
9522                              parseList[forwardMostMove]);
9523
9524     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9525         int timeLeft; static int lastLoadFlag=0; int king, piece;
9526         piece = boards[forwardMostMove][fromY][fromX];
9527         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9528         if(gameInfo.variant == VariantKnightmate)
9529             king += (int) WhiteUnicorn - (int) WhiteKing;
9530         if(forwardMostMove == 0) {
9531             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9532                 fprintf(serverMoves, "%s;", UserName());
9533             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9534                 fprintf(serverMoves, "%s;", second.tidy);
9535             fprintf(serverMoves, "%s;", first.tidy);
9536             if(gameMode == MachinePlaysWhite)
9537                 fprintf(serverMoves, "%s;", UserName());
9538             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9539                 fprintf(serverMoves, "%s;", second.tidy);
9540         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9541         lastLoadFlag = loadFlag;
9542         // print base move
9543         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9544         // print castling suffix
9545         if( toY == fromY && piece == king ) {
9546             if(toX-fromX > 1)
9547                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9548             if(fromX-toX >1)
9549                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9550         }
9551         // e.p. suffix
9552         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9553              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9554              boards[forwardMostMove][toY][toX] == EmptySquare
9555              && fromX != toX && fromY != toY)
9556                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9557         // promotion suffix
9558         if(promoChar != NULLCHAR)
9559                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9560         if(!loadFlag) {
9561                 char buf[MOVE_LEN*2], *p; int len;
9562             fprintf(serverMoves, "/%d/%d",
9563                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9564             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9565             else                      timeLeft = blackTimeRemaining/1000;
9566             fprintf(serverMoves, "/%d", timeLeft);
9567                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9568                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9569                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9570             fprintf(serverMoves, "/%s", buf);
9571         }
9572         fflush(serverMoves);
9573     }
9574
9575     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9576         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9577       return;
9578     }
9579     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9580     if (commentList[forwardMostMove+1] != NULL) {
9581         free(commentList[forwardMostMove+1]);
9582         commentList[forwardMostMove+1] = NULL;
9583     }
9584     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9585     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9586     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9587     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9588     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9589     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9590     adjustedClock = FALSE;
9591     gameInfo.result = GameUnfinished;
9592     if (gameInfo.resultDetails != NULL) {
9593         free(gameInfo.resultDetails);
9594         gameInfo.resultDetails = NULL;
9595     }
9596     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9597                               moveList[forwardMostMove - 1]);
9598     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9599       case MT_NONE:
9600       case MT_STALEMATE:
9601       default:
9602         break;
9603       case MT_CHECK:
9604         if(gameInfo.variant != VariantShogi)
9605             strcat(parseList[forwardMostMove - 1], "+");
9606         break;
9607       case MT_CHECKMATE:
9608       case MT_STAINMATE:
9609         strcat(parseList[forwardMostMove - 1], "#");
9610         break;
9611     }
9612
9613 }
9614
9615 /* Updates currentMove if not pausing */
9616 void
9617 ShowMove (int fromX, int fromY, int toX, int toY)
9618 {
9619     int instant = (gameMode == PlayFromGameFile) ?
9620         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9621     if(appData.noGUI) return;
9622     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9623         if (!instant) {
9624             if (forwardMostMove == currentMove + 1) {
9625                 AnimateMove(boards[forwardMostMove - 1],
9626                             fromX, fromY, toX, toY);
9627             }
9628             if (appData.highlightLastMove) {
9629                 SetHighlights(fromX, fromY, toX, toY);
9630             }
9631         }
9632         currentMove = forwardMostMove;
9633     }
9634
9635     if (instant) return;
9636
9637     DisplayMove(currentMove - 1);
9638     DrawPosition(FALSE, boards[currentMove]);
9639     DisplayBothClocks();
9640     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9641 }
9642
9643 void
9644 SendEgtPath (ChessProgramState *cps)
9645 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9646         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9647
9648         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9649
9650         while(*p) {
9651             char c, *q = name+1, *r, *s;
9652
9653             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9654             while(*p && *p != ',') *q++ = *p++;
9655             *q++ = ':'; *q = 0;
9656             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9657                 strcmp(name, ",nalimov:") == 0 ) {
9658                 // take nalimov path from the menu-changeable option first, if it is defined
9659               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9660                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9661             } else
9662             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9663                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9664                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9665                 s = r = StrStr(s, ":") + 1; // beginning of path info
9666                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9667                 c = *r; *r = 0;             // temporarily null-terminate path info
9668                     *--q = 0;               // strip of trailig ':' from name
9669                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9670                 *r = c;
9671                 SendToProgram(buf,cps);     // send egtbpath command for this format
9672             }
9673             if(*p == ',') p++; // read away comma to position for next format name
9674         }
9675 }
9676
9677 void
9678 InitChessProgram (ChessProgramState *cps, int setup)
9679 /* setup needed to setup FRC opening position */
9680 {
9681     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9682     if (appData.noChessProgram) return;
9683     hintRequested = FALSE;
9684     bookRequested = FALSE;
9685
9686     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9687     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9688     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9689     if(cps->memSize) { /* [HGM] memory */
9690       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9691         SendToProgram(buf, cps);
9692     }
9693     SendEgtPath(cps); /* [HGM] EGT */
9694     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9695       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9696         SendToProgram(buf, cps);
9697     }
9698
9699     SendToProgram(cps->initString, cps);
9700     if (gameInfo.variant != VariantNormal &&
9701         gameInfo.variant != VariantLoadable
9702         /* [HGM] also send variant if board size non-standard */
9703         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9704                                             ) {
9705       char *v = VariantName(gameInfo.variant);
9706       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9707         /* [HGM] in protocol 1 we have to assume all variants valid */
9708         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9709         DisplayFatalError(buf, 0, 1);
9710         return;
9711       }
9712
9713       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9714       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9715       if( gameInfo.variant == VariantXiangqi )
9716            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9717       if( gameInfo.variant == VariantShogi )
9718            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9719       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9720            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9721       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9722           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9723            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9724       if( gameInfo.variant == VariantCourier )
9725            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9726       if( gameInfo.variant == VariantSuper )
9727            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9728       if( gameInfo.variant == VariantGreat )
9729            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9730       if( gameInfo.variant == VariantSChess )
9731            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9732       if( gameInfo.variant == VariantGrand )
9733            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9734
9735       if(overruled) {
9736         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9737                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9738            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9739            if(StrStr(cps->variants, b) == NULL) {
9740                // specific sized variant not known, check if general sizing allowed
9741                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9742                    if(StrStr(cps->variants, "boardsize") == NULL) {
9743                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9744                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9745                        DisplayFatalError(buf, 0, 1);
9746                        return;
9747                    }
9748                    /* [HGM] here we really should compare with the maximum supported board size */
9749                }
9750            }
9751       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9752       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9753       SendToProgram(buf, cps);
9754     }
9755     currentlyInitializedVariant = gameInfo.variant;
9756
9757     /* [HGM] send opening position in FRC to first engine */
9758     if(setup) {
9759           SendToProgram("force\n", cps);
9760           SendBoard(cps, 0);
9761           /* engine is now in force mode! Set flag to wake it up after first move. */
9762           setboardSpoiledMachineBlack = 1;
9763     }
9764
9765     if (cps->sendICS) {
9766       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9767       SendToProgram(buf, cps);
9768     }
9769     cps->maybeThinking = FALSE;
9770     cps->offeredDraw = 0;
9771     if (!appData.icsActive) {
9772         SendTimeControl(cps, movesPerSession, timeControl,
9773                         timeIncrement, appData.searchDepth,
9774                         searchTime);
9775     }
9776     if (appData.showThinking
9777         // [HGM] thinking: four options require thinking output to be sent
9778         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9779                                 ) {
9780         SendToProgram("post\n", cps);
9781     }
9782     SendToProgram("hard\n", cps);
9783     if (!appData.ponderNextMove) {
9784         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9785            it without being sure what state we are in first.  "hard"
9786            is not a toggle, so that one is OK.
9787          */
9788         SendToProgram("easy\n", cps);
9789     }
9790     if (cps->usePing) {
9791       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9792       SendToProgram(buf, cps);
9793     }
9794     cps->initDone = TRUE;
9795     ClearEngineOutputPane(cps == &second);
9796 }
9797
9798
9799 void
9800 StartChessProgram (ChessProgramState *cps)
9801 {
9802     char buf[MSG_SIZ];
9803     int err;
9804
9805     if (appData.noChessProgram) return;
9806     cps->initDone = FALSE;
9807
9808     if (strcmp(cps->host, "localhost") == 0) {
9809         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9810     } else if (*appData.remoteShell == NULLCHAR) {
9811         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9812     } else {
9813         if (*appData.remoteUser == NULLCHAR) {
9814           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9815                     cps->program);
9816         } else {
9817           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9818                     cps->host, appData.remoteUser, cps->program);
9819         }
9820         err = StartChildProcess(buf, "", &cps->pr);
9821     }
9822
9823     if (err != 0) {
9824       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9825         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9826         if(cps != &first) return;
9827         appData.noChessProgram = TRUE;
9828         ThawUI();
9829         SetNCPMode();
9830 //      DisplayFatalError(buf, err, 1);
9831 //      cps->pr = NoProc;
9832 //      cps->isr = NULL;
9833         return;
9834     }
9835
9836     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9837     if (cps->protocolVersion > 1) {
9838       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9839       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9840       cps->comboCnt = 0;  //                and values of combo boxes
9841       SendToProgram(buf, cps);
9842     } else {
9843       SendToProgram("xboard\n", cps);
9844     }
9845 }
9846
9847 void
9848 TwoMachinesEventIfReady P((void))
9849 {
9850   static int curMess = 0;
9851   if (first.lastPing != first.lastPong) {
9852     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9853     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9854     return;
9855   }
9856   if (second.lastPing != second.lastPong) {
9857     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9858     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9859     return;
9860   }
9861   DisplayMessage("", ""); curMess = 0;
9862   ThawUI();
9863   TwoMachinesEvent();
9864 }
9865
9866 char *
9867 MakeName (char *template)
9868 {
9869     time_t clock;
9870     struct tm *tm;
9871     static char buf[MSG_SIZ];
9872     char *p = buf;
9873     int i;
9874
9875     clock = time((time_t *)NULL);
9876     tm = localtime(&clock);
9877
9878     while(*p++ = *template++) if(p[-1] == '%') {
9879         switch(*template++) {
9880           case 0:   *p = 0; return buf;
9881           case 'Y': i = tm->tm_year+1900; break;
9882           case 'y': i = tm->tm_year-100; break;
9883           case 'M': i = tm->tm_mon+1; break;
9884           case 'd': i = tm->tm_mday; break;
9885           case 'h': i = tm->tm_hour; break;
9886           case 'm': i = tm->tm_min; break;
9887           case 's': i = tm->tm_sec; break;
9888           default:  i = 0;
9889         }
9890         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9891     }
9892     return buf;
9893 }
9894
9895 int
9896 CountPlayers (char *p)
9897 {
9898     int n = 0;
9899     while(p = strchr(p, '\n')) p++, n++; // count participants
9900     return n;
9901 }
9902
9903 FILE *
9904 WriteTourneyFile (char *results, FILE *f)
9905 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9906     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9907     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9908         // create a file with tournament description
9909         fprintf(f, "-participants {%s}\n", appData.participants);
9910         fprintf(f, "-seedBase %d\n", appData.seedBase);
9911         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9912         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9913         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9914         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9915         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9916         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9917         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9918         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9919         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9920         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9921         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9922         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9923         if(searchTime > 0)
9924                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9925         else {
9926                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9927                 fprintf(f, "-tc %s\n", appData.timeControl);
9928                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9929         }
9930         fprintf(f, "-results \"%s\"\n", results);
9931     }
9932     return f;
9933 }
9934
9935 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9936
9937 void
9938 Substitute (char *participants, int expunge)
9939 {
9940     int i, changed, changes=0, nPlayers=0;
9941     char *p, *q, *r, buf[MSG_SIZ];
9942     if(participants == NULL) return;
9943     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9944     r = p = participants; q = appData.participants;
9945     while(*p && *p == *q) {
9946         if(*p == '\n') r = p+1, nPlayers++;
9947         p++; q++;
9948     }
9949     if(*p) { // difference
9950         while(*p && *p++ != '\n');
9951         while(*q && *q++ != '\n');
9952       changed = nPlayers;
9953         changes = 1 + (strcmp(p, q) != 0);
9954     }
9955     if(changes == 1) { // a single engine mnemonic was changed
9956         q = r; while(*q) nPlayers += (*q++ == '\n');
9957         p = buf; while(*r && (*p = *r++) != '\n') p++;
9958         *p = NULLCHAR;
9959         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9960         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9961         if(mnemonic[i]) { // The substitute is valid
9962             FILE *f;
9963             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9964                 flock(fileno(f), LOCK_EX);
9965                 ParseArgsFromFile(f);
9966                 fseek(f, 0, SEEK_SET);
9967                 FREE(appData.participants); appData.participants = participants;
9968                 if(expunge) { // erase results of replaced engine
9969                     int len = strlen(appData.results), w, b, dummy;
9970                     for(i=0; i<len; i++) {
9971                         Pairing(i, nPlayers, &w, &b, &dummy);
9972                         if((w == changed || b == changed) && appData.results[i] == '*') {
9973                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9974                             fclose(f);
9975                             return;
9976                         }
9977                     }
9978                     for(i=0; i<len; i++) {
9979                         Pairing(i, nPlayers, &w, &b, &dummy);
9980                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9981                     }
9982                 }
9983                 WriteTourneyFile(appData.results, f);
9984                 fclose(f); // release lock
9985                 return;
9986             }
9987         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9988     }
9989     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9990     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9991     free(participants);
9992     return;
9993 }
9994
9995 int
9996 CreateTourney (char *name)
9997 {
9998         FILE *f;
9999         if(matchMode && strcmp(name, appData.tourneyFile)) {
10000              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10001         }
10002         if(name[0] == NULLCHAR) {
10003             if(appData.participants[0])
10004                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10005             return 0;
10006         }
10007         f = fopen(name, "r");
10008         if(f) { // file exists
10009             ASSIGN(appData.tourneyFile, name);
10010             ParseArgsFromFile(f); // parse it
10011         } else {
10012             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10013             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10014                 DisplayError(_("Not enough participants"), 0);
10015                 return 0;
10016             }
10017             ASSIGN(appData.tourneyFile, name);
10018             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10019             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10020         }
10021         fclose(f);
10022         appData.noChessProgram = FALSE;
10023         appData.clockMode = TRUE;
10024         SetGNUMode();
10025         return 1;
10026 }
10027
10028 int
10029 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10030 {
10031     char buf[MSG_SIZ], *p, *q;
10032     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10033     skip = !all && group[0]; // if group requested, we start in skip mode
10034     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10035         p = names; q = buf; header = 0;
10036         while(*p && *p != '\n') *q++ = *p++;
10037         *q = 0;
10038         if(*p == '\n') p++;
10039         if(buf[0] == '#') {
10040             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10041             depth++; // we must be entering a new group
10042             if(all) continue; // suppress printing group headers when complete list requested
10043             header = 1;
10044             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10045         }
10046         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10047         if(engineList[i]) free(engineList[i]);
10048         engineList[i] = strdup(buf);
10049         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10050         if(engineMnemonic[i]) free(engineMnemonic[i]);
10051         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10052             strcat(buf, " (");
10053             sscanf(q + 8, "%s", buf + strlen(buf));
10054             strcat(buf, ")");
10055         }
10056         engineMnemonic[i] = strdup(buf);
10057         i++;
10058     }
10059     engineList[i] = engineMnemonic[i] = NULL;
10060     return i;
10061 }
10062
10063 // following implemented as macro to avoid type limitations
10064 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10065
10066 void
10067 SwapEngines (int n)
10068 {   // swap settings for first engine and other engine (so far only some selected options)
10069     int h;
10070     char *p;
10071     if(n == 0) return;
10072     SWAP(directory, p)
10073     SWAP(chessProgram, p)
10074     SWAP(isUCI, h)
10075     SWAP(hasOwnBookUCI, h)
10076     SWAP(protocolVersion, h)
10077     SWAP(reuse, h)
10078     SWAP(scoreIsAbsolute, h)
10079     SWAP(timeOdds, h)
10080     SWAP(logo, p)
10081     SWAP(pgnName, p)
10082     SWAP(pvSAN, h)
10083     SWAP(engOptions, p)
10084     SWAP(engInitString, p)
10085     SWAP(computerString, p)
10086     SWAP(features, p)
10087     SWAP(fenOverride, p)
10088     SWAP(NPS, h)
10089     SWAP(accumulateTC, h)
10090     SWAP(host, p)
10091 }
10092
10093 int
10094 SetPlayer (int player, char *p)
10095 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10096     int i;
10097     char buf[MSG_SIZ], *engineName;
10098     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10099     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10100     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10101     if(mnemonic[i]) {
10102         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10103         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10104         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10105         ParseArgsFromString(buf);
10106     }
10107     free(engineName);
10108     return i;
10109 }
10110
10111 char *recentEngines;
10112
10113 void
10114 RecentEngineEvent (int nr)
10115 {
10116     int n;
10117 //    SwapEngines(1); // bump first to second
10118 //    ReplaceEngine(&second, 1); // and load it there
10119     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10120     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10121     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10122         ReplaceEngine(&first, 0);
10123         FloatToFront(&appData.recentEngineList, command[n]);
10124     }
10125 }
10126
10127 int
10128 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10129 {   // determine players from game number
10130     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10131
10132     if(appData.tourneyType == 0) {
10133         roundsPerCycle = (nPlayers - 1) | 1;
10134         pairingsPerRound = nPlayers / 2;
10135     } else if(appData.tourneyType > 0) {
10136         roundsPerCycle = nPlayers - appData.tourneyType;
10137         pairingsPerRound = appData.tourneyType;
10138     }
10139     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10140     gamesPerCycle = gamesPerRound * roundsPerCycle;
10141     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10142     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10143     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10144     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10145     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10146     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10147
10148     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10149     if(appData.roundSync) *syncInterval = gamesPerRound;
10150
10151     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10152
10153     if(appData.tourneyType == 0) {
10154         if(curPairing == (nPlayers-1)/2 ) {
10155             *whitePlayer = curRound;
10156             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10157         } else {
10158             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10159             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10160             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10161             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10162         }
10163     } else if(appData.tourneyType > 1) {
10164         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10165         *whitePlayer = curRound + appData.tourneyType;
10166     } else if(appData.tourneyType > 0) {
10167         *whitePlayer = curPairing;
10168         *blackPlayer = curRound + appData.tourneyType;
10169     }
10170
10171     // take care of white/black alternation per round. 
10172     // For cycles and games this is already taken care of by default, derived from matchGame!
10173     return curRound & 1;
10174 }
10175
10176 int
10177 NextTourneyGame (int nr, int *swapColors)
10178 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10179     char *p, *q;
10180     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10181     FILE *tf;
10182     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10183     tf = fopen(appData.tourneyFile, "r");
10184     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10185     ParseArgsFromFile(tf); fclose(tf);
10186     InitTimeControls(); // TC might be altered from tourney file
10187
10188     nPlayers = CountPlayers(appData.participants); // count participants
10189     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10190     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10191
10192     if(syncInterval) {
10193         p = q = appData.results;
10194         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10195         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10196             DisplayMessage(_("Waiting for other game(s)"),"");
10197             waitingForGame = TRUE;
10198             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10199             return 0;
10200         }
10201         waitingForGame = FALSE;
10202     }
10203
10204     if(appData.tourneyType < 0) {
10205         if(nr>=0 && !pairingReceived) {
10206             char buf[1<<16];
10207             if(pairing.pr == NoProc) {
10208                 if(!appData.pairingEngine[0]) {
10209                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10210                     return 0;
10211                 }
10212                 StartChessProgram(&pairing); // starts the pairing engine
10213             }
10214             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10215             SendToProgram(buf, &pairing);
10216             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10217             SendToProgram(buf, &pairing);
10218             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10219         }
10220         pairingReceived = 0;                              // ... so we continue here 
10221         *swapColors = 0;
10222         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10223         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10224         matchGame = 1; roundNr = nr / syncInterval + 1;
10225     }
10226
10227     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10228
10229     // redefine engines, engine dir, etc.
10230     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10231     if(first.pr == NoProc) {
10232       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10233       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10234     }
10235     if(second.pr == NoProc) {
10236       SwapEngines(1);
10237       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10238       SwapEngines(1);         // and make that valid for second engine by swapping
10239       InitEngine(&second, 1);
10240     }
10241     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10242     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10243     return 1;
10244 }
10245
10246 void
10247 NextMatchGame ()
10248 {   // performs game initialization that does not invoke engines, and then tries to start the game
10249     int res, firstWhite, swapColors = 0;
10250     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10251     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
10252         char buf[MSG_SIZ];
10253         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10254         if(strcmp(buf, currentDebugFile)) { // name has changed
10255             FILE *f = fopen(buf, "w");
10256             if(f) { // if opening the new file failed, just keep using the old one
10257                 ASSIGN(currentDebugFile, buf);
10258                 fclose(debugFP);
10259                 debugFP = f;
10260             }
10261             if(appData.serverFileName) {
10262                 if(serverFP) fclose(serverFP);
10263                 serverFP = fopen(appData.serverFileName, "w");
10264                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10265                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10266             }
10267         }
10268     }
10269     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10270     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10271     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10272     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10273     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10274     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10275     Reset(FALSE, first.pr != NoProc);
10276     res = LoadGameOrPosition(matchGame); // setup game
10277     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10278     if(!res) return; // abort when bad game/pos file
10279     TwoMachinesEvent();
10280 }
10281
10282 void
10283 UserAdjudicationEvent (int result)
10284 {
10285     ChessMove gameResult = GameIsDrawn;
10286
10287     if( result > 0 ) {
10288         gameResult = WhiteWins;
10289     }
10290     else if( result < 0 ) {
10291         gameResult = BlackWins;
10292     }
10293
10294     if( gameMode == TwoMachinesPlay ) {
10295         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10296     }
10297 }
10298
10299
10300 // [HGM] save: calculate checksum of game to make games easily identifiable
10301 int
10302 StringCheckSum (char *s)
10303 {
10304         int i = 0;
10305         if(s==NULL) return 0;
10306         while(*s) i = i*259 + *s++;
10307         return i;
10308 }
10309
10310 int
10311 GameCheckSum ()
10312 {
10313         int i, sum=0;
10314         for(i=backwardMostMove; i<forwardMostMove; i++) {
10315                 sum += pvInfoList[i].depth;
10316                 sum += StringCheckSum(parseList[i]);
10317                 sum += StringCheckSum(commentList[i]);
10318                 sum *= 261;
10319         }
10320         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10321         return sum + StringCheckSum(commentList[i]);
10322 } // end of save patch
10323
10324 void
10325 GameEnds (ChessMove result, char *resultDetails, int whosays)
10326 {
10327     GameMode nextGameMode;
10328     int isIcsGame;
10329     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10330
10331     if(endingGame) return; /* [HGM] crash: forbid recursion */
10332     endingGame = 1;
10333     if(twoBoards) { // [HGM] dual: switch back to one board
10334         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10335         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10336     }
10337     if (appData.debugMode) {
10338       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10339               result, resultDetails ? resultDetails : "(null)", whosays);
10340     }
10341
10342     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10343
10344     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10345         /* If we are playing on ICS, the server decides when the
10346            game is over, but the engine can offer to draw, claim
10347            a draw, or resign.
10348          */
10349 #if ZIPPY
10350         if (appData.zippyPlay && first.initDone) {
10351             if (result == GameIsDrawn) {
10352                 /* In case draw still needs to be claimed */
10353                 SendToICS(ics_prefix);
10354                 SendToICS("draw\n");
10355             } else if (StrCaseStr(resultDetails, "resign")) {
10356                 SendToICS(ics_prefix);
10357                 SendToICS("resign\n");
10358             }
10359         }
10360 #endif
10361         endingGame = 0; /* [HGM] crash */
10362         return;
10363     }
10364
10365     /* If we're loading the game from a file, stop */
10366     if (whosays == GE_FILE) {
10367       (void) StopLoadGameTimer();
10368       gameFileFP = NULL;
10369     }
10370
10371     /* Cancel draw offers */
10372     first.offeredDraw = second.offeredDraw = 0;
10373
10374     /* If this is an ICS game, only ICS can really say it's done;
10375        if not, anyone can. */
10376     isIcsGame = (gameMode == IcsPlayingWhite ||
10377                  gameMode == IcsPlayingBlack ||
10378                  gameMode == IcsObserving    ||
10379                  gameMode == IcsExamining);
10380
10381     if (!isIcsGame || whosays == GE_ICS) {
10382         /* OK -- not an ICS game, or ICS said it was done */
10383         StopClocks();
10384         if (!isIcsGame && !appData.noChessProgram)
10385           SetUserThinkingEnables();
10386
10387         /* [HGM] if a machine claims the game end we verify this claim */
10388         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10389             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10390                 char claimer;
10391                 ChessMove trueResult = (ChessMove) -1;
10392
10393                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10394                                             first.twoMachinesColor[0] :
10395                                             second.twoMachinesColor[0] ;
10396
10397                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10398                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10399                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10400                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10401                 } else
10402                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10403                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10404                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10405                 } else
10406                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10407                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10408                 }
10409
10410                 // now verify win claims, but not in drop games, as we don't understand those yet
10411                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10412                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10413                     (result == WhiteWins && claimer == 'w' ||
10414                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10415                       if (appData.debugMode) {
10416                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10417                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10418                       }
10419                       if(result != trueResult) {
10420                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10421                               result = claimer == 'w' ? BlackWins : WhiteWins;
10422                               resultDetails = buf;
10423                       }
10424                 } else
10425                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10426                     && (forwardMostMove <= backwardMostMove ||
10427                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10428                         (claimer=='b')==(forwardMostMove&1))
10429                                                                                   ) {
10430                       /* [HGM] verify: draws that were not flagged are false claims */
10431                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10432                       result = claimer == 'w' ? BlackWins : WhiteWins;
10433                       resultDetails = buf;
10434                 }
10435                 /* (Claiming a loss is accepted no questions asked!) */
10436             }
10437             /* [HGM] bare: don't allow bare King to win */
10438             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10439                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10440                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10441                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10442                && result != GameIsDrawn)
10443             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10444                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10445                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10446                         if(p >= 0 && p <= (int)WhiteKing) k++;
10447                 }
10448                 if (appData.debugMode) {
10449                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10450                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10451                 }
10452                 if(k <= 1) {
10453                         result = GameIsDrawn;
10454                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10455                         resultDetails = buf;
10456                 }
10457             }
10458         }
10459
10460
10461         if(serverMoves != NULL && !loadFlag) { char c = '=';
10462             if(result==WhiteWins) c = '+';
10463             if(result==BlackWins) c = '-';
10464             if(resultDetails != NULL)
10465                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10466         }
10467         if (resultDetails != NULL) {
10468             gameInfo.result = result;
10469             gameInfo.resultDetails = StrSave(resultDetails);
10470
10471             /* display last move only if game was not loaded from file */
10472             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10473                 DisplayMove(currentMove - 1);
10474
10475             if (forwardMostMove != 0) {
10476                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10477                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10478                                                                 ) {
10479                     if (*appData.saveGameFile != NULLCHAR) {
10480                         SaveGameToFile(appData.saveGameFile, TRUE);
10481                     } else if (appData.autoSaveGames) {
10482                         AutoSaveGame();
10483                     }
10484                     if (*appData.savePositionFile != NULLCHAR) {
10485                         SavePositionToFile(appData.savePositionFile);
10486                     }
10487                 }
10488             }
10489
10490             /* Tell program how game ended in case it is learning */
10491             /* [HGM] Moved this to after saving the PGN, just in case */
10492             /* engine died and we got here through time loss. In that */
10493             /* case we will get a fatal error writing the pipe, which */
10494             /* would otherwise lose us the PGN.                       */
10495             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10496             /* output during GameEnds should never be fatal anymore   */
10497             if (gameMode == MachinePlaysWhite ||
10498                 gameMode == MachinePlaysBlack ||
10499                 gameMode == TwoMachinesPlay ||
10500                 gameMode == IcsPlayingWhite ||
10501                 gameMode == IcsPlayingBlack ||
10502                 gameMode == BeginningOfGame) {
10503                 char buf[MSG_SIZ];
10504                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10505                         resultDetails);
10506                 if (first.pr != NoProc) {
10507                     SendToProgram(buf, &first);
10508                 }
10509                 if (second.pr != NoProc &&
10510                     gameMode == TwoMachinesPlay) {
10511                     SendToProgram(buf, &second);
10512                 }
10513             }
10514         }
10515
10516         if (appData.icsActive) {
10517             if (appData.quietPlay &&
10518                 (gameMode == IcsPlayingWhite ||
10519                  gameMode == IcsPlayingBlack)) {
10520                 SendToICS(ics_prefix);
10521                 SendToICS("set shout 1\n");
10522             }
10523             nextGameMode = IcsIdle;
10524             ics_user_moved = FALSE;
10525             /* clean up premove.  It's ugly when the game has ended and the
10526              * premove highlights are still on the board.
10527              */
10528             if (gotPremove) {
10529               gotPremove = FALSE;
10530               ClearPremoveHighlights();
10531               DrawPosition(FALSE, boards[currentMove]);
10532             }
10533             if (whosays == GE_ICS) {
10534                 switch (result) {
10535                 case WhiteWins:
10536                     if (gameMode == IcsPlayingWhite)
10537                         PlayIcsWinSound();
10538                     else if(gameMode == IcsPlayingBlack)
10539                         PlayIcsLossSound();
10540                     break;
10541                 case BlackWins:
10542                     if (gameMode == IcsPlayingBlack)
10543                         PlayIcsWinSound();
10544                     else if(gameMode == IcsPlayingWhite)
10545                         PlayIcsLossSound();
10546                     break;
10547                 case GameIsDrawn:
10548                     PlayIcsDrawSound();
10549                     break;
10550                 default:
10551                     PlayIcsUnfinishedSound();
10552                 }
10553             }
10554         } else if (gameMode == EditGame ||
10555                    gameMode == PlayFromGameFile ||
10556                    gameMode == AnalyzeMode ||
10557                    gameMode == AnalyzeFile) {
10558             nextGameMode = gameMode;
10559         } else {
10560             nextGameMode = EndOfGame;
10561         }
10562         pausing = FALSE;
10563         ModeHighlight();
10564     } else {
10565         nextGameMode = gameMode;
10566     }
10567
10568     if (appData.noChessProgram) {
10569         gameMode = nextGameMode;
10570         ModeHighlight();
10571         endingGame = 0; /* [HGM] crash */
10572         return;
10573     }
10574
10575     if (first.reuse) {
10576         /* Put first chess program into idle state */
10577         if (first.pr != NoProc &&
10578             (gameMode == MachinePlaysWhite ||
10579              gameMode == MachinePlaysBlack ||
10580              gameMode == TwoMachinesPlay ||
10581              gameMode == IcsPlayingWhite ||
10582              gameMode == IcsPlayingBlack ||
10583              gameMode == BeginningOfGame)) {
10584             SendToProgram("force\n", &first);
10585             if (first.usePing) {
10586               char buf[MSG_SIZ];
10587               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10588               SendToProgram(buf, &first);
10589             }
10590         }
10591     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10592         /* Kill off first chess program */
10593         if (first.isr != NULL)
10594           RemoveInputSource(first.isr);
10595         first.isr = NULL;
10596
10597         if (first.pr != NoProc) {
10598             ExitAnalyzeMode();
10599             DoSleep( appData.delayBeforeQuit );
10600             SendToProgram("quit\n", &first);
10601             DoSleep( appData.delayAfterQuit );
10602             DestroyChildProcess(first.pr, first.useSigterm);
10603         }
10604         first.pr = NoProc;
10605     }
10606     if (second.reuse) {
10607         /* Put second chess program into idle state */
10608         if (second.pr != NoProc &&
10609             gameMode == TwoMachinesPlay) {
10610             SendToProgram("force\n", &second);
10611             if (second.usePing) {
10612               char buf[MSG_SIZ];
10613               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10614               SendToProgram(buf, &second);
10615             }
10616         }
10617     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10618         /* Kill off second chess program */
10619         if (second.isr != NULL)
10620           RemoveInputSource(second.isr);
10621         second.isr = NULL;
10622
10623         if (second.pr != NoProc) {
10624             DoSleep( appData.delayBeforeQuit );
10625             SendToProgram("quit\n", &second);
10626             DoSleep( appData.delayAfterQuit );
10627             DestroyChildProcess(second.pr, second.useSigterm);
10628         }
10629         second.pr = NoProc;
10630     }
10631
10632     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10633         char resChar = '=';
10634         switch (result) {
10635         case WhiteWins:
10636           resChar = '+';
10637           if (first.twoMachinesColor[0] == 'w') {
10638             first.matchWins++;
10639           } else {
10640             second.matchWins++;
10641           }
10642           break;
10643         case BlackWins:
10644           resChar = '-';
10645           if (first.twoMachinesColor[0] == 'b') {
10646             first.matchWins++;
10647           } else {
10648             second.matchWins++;
10649           }
10650           break;
10651         case GameUnfinished:
10652           resChar = ' ';
10653         default:
10654           break;
10655         }
10656
10657         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10658         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10659             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10660             ReserveGame(nextGame, resChar); // sets nextGame
10661             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10662             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10663         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10664
10665         if (nextGame <= appData.matchGames && !abortMatch) {
10666             gameMode = nextGameMode;
10667             matchGame = nextGame; // this will be overruled in tourney mode!
10668             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10669             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10670             endingGame = 0; /* [HGM] crash */
10671             return;
10672         } else {
10673             gameMode = nextGameMode;
10674             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10675                      first.tidy, second.tidy,
10676                      first.matchWins, second.matchWins,
10677                      appData.matchGames - (first.matchWins + second.matchWins));
10678             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10679             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10680             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10681             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10682                 first.twoMachinesColor = "black\n";
10683                 second.twoMachinesColor = "white\n";
10684             } else {
10685                 first.twoMachinesColor = "white\n";
10686                 second.twoMachinesColor = "black\n";
10687             }
10688         }
10689     }
10690     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10691         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10692       ExitAnalyzeMode();
10693     gameMode = nextGameMode;
10694     ModeHighlight();
10695     endingGame = 0;  /* [HGM] crash */
10696     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10697         if(matchMode == TRUE) { // match through command line: exit with or without popup
10698             if(ranking) {
10699                 ToNrEvent(forwardMostMove);
10700                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10701                 else ExitEvent(0);
10702             } else DisplayFatalError(buf, 0, 0);
10703         } else { // match through menu; just stop, with or without popup
10704             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10705             ModeHighlight();
10706             if(ranking){
10707                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10708             } else DisplayNote(buf);
10709       }
10710       if(ranking) free(ranking);
10711     }
10712 }
10713
10714 /* Assumes program was just initialized (initString sent).
10715    Leaves program in force mode. */
10716 void
10717 FeedMovesToProgram (ChessProgramState *cps, int upto)
10718 {
10719     int i;
10720
10721     if (appData.debugMode)
10722       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10723               startedFromSetupPosition ? "position and " : "",
10724               backwardMostMove, upto, cps->which);
10725     if(currentlyInitializedVariant != gameInfo.variant) {
10726       char buf[MSG_SIZ];
10727         // [HGM] variantswitch: make engine aware of new variant
10728         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10729                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10730         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10731         SendToProgram(buf, cps);
10732         currentlyInitializedVariant = gameInfo.variant;
10733     }
10734     SendToProgram("force\n", cps);
10735     if (startedFromSetupPosition) {
10736         SendBoard(cps, backwardMostMove);
10737     if (appData.debugMode) {
10738         fprintf(debugFP, "feedMoves\n");
10739     }
10740     }
10741     for (i = backwardMostMove; i < upto; i++) {
10742         SendMoveToProgram(i, cps);
10743     }
10744 }
10745
10746
10747 int
10748 ResurrectChessProgram ()
10749 {
10750      /* The chess program may have exited.
10751         If so, restart it and feed it all the moves made so far. */
10752     static int doInit = 0;
10753
10754     if (appData.noChessProgram) return 1;
10755
10756     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10757         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10758         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10759         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10760     } else {
10761         if (first.pr != NoProc) return 1;
10762         StartChessProgram(&first);
10763     }
10764     InitChessProgram(&first, FALSE);
10765     FeedMovesToProgram(&first, currentMove);
10766
10767     if (!first.sendTime) {
10768         /* can't tell gnuchess what its clock should read,
10769            so we bow to its notion. */
10770         ResetClocks();
10771         timeRemaining[0][currentMove] = whiteTimeRemaining;
10772         timeRemaining[1][currentMove] = blackTimeRemaining;
10773     }
10774
10775     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10776                 appData.icsEngineAnalyze) && first.analysisSupport) {
10777       SendToProgram("analyze\n", &first);
10778       first.analyzing = TRUE;
10779     }
10780     return 1;
10781 }
10782
10783 /*
10784  * Button procedures
10785  */
10786 void
10787 Reset (int redraw, int init)
10788 {
10789     int i;
10790
10791     if (appData.debugMode) {
10792         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10793                 redraw, init, gameMode);
10794     }
10795     CleanupTail(); // [HGM] vari: delete any stored variations
10796     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10797     pausing = pauseExamInvalid = FALSE;
10798     startedFromSetupPosition = blackPlaysFirst = FALSE;
10799     firstMove = TRUE;
10800     whiteFlag = blackFlag = FALSE;
10801     userOfferedDraw = FALSE;
10802     hintRequested = bookRequested = FALSE;
10803     first.maybeThinking = FALSE;
10804     second.maybeThinking = FALSE;
10805     first.bookSuspend = FALSE; // [HGM] book
10806     second.bookSuspend = FALSE;
10807     thinkOutput[0] = NULLCHAR;
10808     lastHint[0] = NULLCHAR;
10809     ClearGameInfo(&gameInfo);
10810     gameInfo.variant = StringToVariant(appData.variant);
10811     ics_user_moved = ics_clock_paused = FALSE;
10812     ics_getting_history = H_FALSE;
10813     ics_gamenum = -1;
10814     white_holding[0] = black_holding[0] = NULLCHAR;
10815     ClearProgramStats();
10816     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10817
10818     ResetFrontEnd();
10819     ClearHighlights();
10820     flipView = appData.flipView;
10821     ClearPremoveHighlights();
10822     gotPremove = FALSE;
10823     alarmSounded = FALSE;
10824
10825     GameEnds(EndOfFile, NULL, GE_PLAYER);
10826     if(appData.serverMovesName != NULL) {
10827         /* [HGM] prepare to make moves file for broadcasting */
10828         clock_t t = clock();
10829         if(serverMoves != NULL) fclose(serverMoves);
10830         serverMoves = fopen(appData.serverMovesName, "r");
10831         if(serverMoves != NULL) {
10832             fclose(serverMoves);
10833             /* delay 15 sec before overwriting, so all clients can see end */
10834             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10835         }
10836         serverMoves = fopen(appData.serverMovesName, "w");
10837     }
10838
10839     ExitAnalyzeMode();
10840     gameMode = BeginningOfGame;
10841     ModeHighlight();
10842     if(appData.icsActive) gameInfo.variant = VariantNormal;
10843     currentMove = forwardMostMove = backwardMostMove = 0;
10844     MarkTargetSquares(1);
10845     InitPosition(redraw);
10846     for (i = 0; i < MAX_MOVES; i++) {
10847         if (commentList[i] != NULL) {
10848             free(commentList[i]);
10849             commentList[i] = NULL;
10850         }
10851     }
10852     ResetClocks();
10853     timeRemaining[0][0] = whiteTimeRemaining;
10854     timeRemaining[1][0] = blackTimeRemaining;
10855
10856     if (first.pr == NoProc) {
10857         StartChessProgram(&first);
10858     }
10859     if (init) {
10860             InitChessProgram(&first, startedFromSetupPosition);
10861     }
10862     DisplayTitle("");
10863     DisplayMessage("", "");
10864     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10865     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10866     ClearMap();        // [HGM] exclude: invalidate map
10867 }
10868
10869 void
10870 AutoPlayGameLoop ()
10871 {
10872     for (;;) {
10873         if (!AutoPlayOneMove())
10874           return;
10875         if (matchMode || appData.timeDelay == 0)
10876           continue;
10877         if (appData.timeDelay < 0)
10878           return;
10879         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10880         break;
10881     }
10882 }
10883
10884
10885 int
10886 AutoPlayOneMove ()
10887 {
10888     int fromX, fromY, toX, toY;
10889
10890     if (appData.debugMode) {
10891       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10892     }
10893
10894     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10895       return FALSE;
10896
10897     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10898       pvInfoList[currentMove].depth = programStats.depth;
10899       pvInfoList[currentMove].score = programStats.score;
10900       pvInfoList[currentMove].time  = 0;
10901       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10902     }
10903
10904     if (currentMove >= forwardMostMove) {
10905       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10906 //      gameMode = EndOfGame;
10907 //      ModeHighlight();
10908
10909       /* [AS] Clear current move marker at the end of a game */
10910       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10911
10912       return FALSE;
10913     }
10914
10915     toX = moveList[currentMove][2] - AAA;
10916     toY = moveList[currentMove][3] - ONE;
10917
10918     if (moveList[currentMove][1] == '@') {
10919         if (appData.highlightLastMove) {
10920             SetHighlights(-1, -1, toX, toY);
10921         }
10922     } else {
10923         fromX = moveList[currentMove][0] - AAA;
10924         fromY = moveList[currentMove][1] - ONE;
10925
10926         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10927
10928         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10929
10930         if (appData.highlightLastMove) {
10931             SetHighlights(fromX, fromY, toX, toY);
10932         }
10933     }
10934     DisplayMove(currentMove);
10935     SendMoveToProgram(currentMove++, &first);
10936     DisplayBothClocks();
10937     DrawPosition(FALSE, boards[currentMove]);
10938     // [HGM] PV info: always display, routine tests if empty
10939     DisplayComment(currentMove - 1, commentList[currentMove]);
10940     return TRUE;
10941 }
10942
10943
10944 int
10945 LoadGameOneMove (ChessMove readAhead)
10946 {
10947     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10948     char promoChar = NULLCHAR;
10949     ChessMove moveType;
10950     char move[MSG_SIZ];
10951     char *p, *q;
10952
10953     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10954         gameMode != AnalyzeMode && gameMode != Training) {
10955         gameFileFP = NULL;
10956         return FALSE;
10957     }
10958
10959     yyboardindex = forwardMostMove;
10960     if (readAhead != EndOfFile) {
10961       moveType = readAhead;
10962     } else {
10963       if (gameFileFP == NULL)
10964           return FALSE;
10965       moveType = (ChessMove) Myylex();
10966     }
10967
10968     done = FALSE;
10969     switch (moveType) {
10970       case Comment:
10971         if (appData.debugMode)
10972           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10973         p = yy_text;
10974
10975         /* append the comment but don't display it */
10976         AppendComment(currentMove, p, FALSE);
10977         return TRUE;
10978
10979       case WhiteCapturesEnPassant:
10980       case BlackCapturesEnPassant:
10981       case WhitePromotion:
10982       case BlackPromotion:
10983       case WhiteNonPromotion:
10984       case BlackNonPromotion:
10985       case NormalMove:
10986       case WhiteKingSideCastle:
10987       case WhiteQueenSideCastle:
10988       case BlackKingSideCastle:
10989       case BlackQueenSideCastle:
10990       case WhiteKingSideCastleWild:
10991       case WhiteQueenSideCastleWild:
10992       case BlackKingSideCastleWild:
10993       case BlackQueenSideCastleWild:
10994       /* PUSH Fabien */
10995       case WhiteHSideCastleFR:
10996       case WhiteASideCastleFR:
10997       case BlackHSideCastleFR:
10998       case BlackASideCastleFR:
10999       /* POP Fabien */
11000         if (appData.debugMode)
11001           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11002         fromX = currentMoveString[0] - AAA;
11003         fromY = currentMoveString[1] - ONE;
11004         toX = currentMoveString[2] - AAA;
11005         toY = currentMoveString[3] - ONE;
11006         promoChar = currentMoveString[4];
11007         break;
11008
11009       case WhiteDrop:
11010       case BlackDrop:
11011         if (appData.debugMode)
11012           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11013         fromX = moveType == WhiteDrop ?
11014           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11015         (int) CharToPiece(ToLower(currentMoveString[0]));
11016         fromY = DROP_RANK;
11017         toX = currentMoveString[2] - AAA;
11018         toY = currentMoveString[3] - ONE;
11019         break;
11020
11021       case WhiteWins:
11022       case BlackWins:
11023       case GameIsDrawn:
11024       case GameUnfinished:
11025         if (appData.debugMode)
11026           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11027         p = strchr(yy_text, '{');
11028         if (p == NULL) p = strchr(yy_text, '(');
11029         if (p == NULL) {
11030             p = yy_text;
11031             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11032         } else {
11033             q = strchr(p, *p == '{' ? '}' : ')');
11034             if (q != NULL) *q = NULLCHAR;
11035             p++;
11036         }
11037         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11038         GameEnds(moveType, p, GE_FILE);
11039         done = TRUE;
11040         if (cmailMsgLoaded) {
11041             ClearHighlights();
11042             flipView = WhiteOnMove(currentMove);
11043             if (moveType == GameUnfinished) flipView = !flipView;
11044             if (appData.debugMode)
11045               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11046         }
11047         break;
11048
11049       case EndOfFile:
11050         if (appData.debugMode)
11051           fprintf(debugFP, "Parser hit end of file\n");
11052         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11053           case MT_NONE:
11054           case MT_CHECK:
11055             break;
11056           case MT_CHECKMATE:
11057           case MT_STAINMATE:
11058             if (WhiteOnMove(currentMove)) {
11059                 GameEnds(BlackWins, "Black mates", GE_FILE);
11060             } else {
11061                 GameEnds(WhiteWins, "White mates", GE_FILE);
11062             }
11063             break;
11064           case MT_STALEMATE:
11065             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11066             break;
11067         }
11068         done = TRUE;
11069         break;
11070
11071       case MoveNumberOne:
11072         if (lastLoadGameStart == GNUChessGame) {
11073             /* GNUChessGames have numbers, but they aren't move numbers */
11074             if (appData.debugMode)
11075               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11076                       yy_text, (int) moveType);
11077             return LoadGameOneMove(EndOfFile); /* tail recursion */
11078         }
11079         /* else fall thru */
11080
11081       case XBoardGame:
11082       case GNUChessGame:
11083       case PGNTag:
11084         /* Reached start of next game in file */
11085         if (appData.debugMode)
11086           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11087         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11088           case MT_NONE:
11089           case MT_CHECK:
11090             break;
11091           case MT_CHECKMATE:
11092           case MT_STAINMATE:
11093             if (WhiteOnMove(currentMove)) {
11094                 GameEnds(BlackWins, "Black mates", GE_FILE);
11095             } else {
11096                 GameEnds(WhiteWins, "White mates", GE_FILE);
11097             }
11098             break;
11099           case MT_STALEMATE:
11100             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11101             break;
11102         }
11103         done = TRUE;
11104         break;
11105
11106       case PositionDiagram:     /* should not happen; ignore */
11107       case ElapsedTime:         /* ignore */
11108       case NAG:                 /* ignore */
11109         if (appData.debugMode)
11110           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11111                   yy_text, (int) moveType);
11112         return LoadGameOneMove(EndOfFile); /* tail recursion */
11113
11114       case IllegalMove:
11115         if (appData.testLegality) {
11116             if (appData.debugMode)
11117               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11118             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11119                     (forwardMostMove / 2) + 1,
11120                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11121             DisplayError(move, 0);
11122             done = TRUE;
11123         } else {
11124             if (appData.debugMode)
11125               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11126                       yy_text, currentMoveString);
11127             fromX = currentMoveString[0] - AAA;
11128             fromY = currentMoveString[1] - ONE;
11129             toX = currentMoveString[2] - AAA;
11130             toY = currentMoveString[3] - ONE;
11131             promoChar = currentMoveString[4];
11132         }
11133         break;
11134
11135       case AmbiguousMove:
11136         if (appData.debugMode)
11137           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11138         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11139                 (forwardMostMove / 2) + 1,
11140                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11141         DisplayError(move, 0);
11142         done = TRUE;
11143         break;
11144
11145       default:
11146       case ImpossibleMove:
11147         if (appData.debugMode)
11148           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11149         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11150                 (forwardMostMove / 2) + 1,
11151                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11152         DisplayError(move, 0);
11153         done = TRUE;
11154         break;
11155     }
11156
11157     if (done) {
11158         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11159             DrawPosition(FALSE, boards[currentMove]);
11160             DisplayBothClocks();
11161             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11162               DisplayComment(currentMove - 1, commentList[currentMove]);
11163         }
11164         (void) StopLoadGameTimer();
11165         gameFileFP = NULL;
11166         cmailOldMove = forwardMostMove;
11167         return FALSE;
11168     } else {
11169         /* currentMoveString is set as a side-effect of yylex */
11170
11171         thinkOutput[0] = NULLCHAR;
11172         MakeMove(fromX, fromY, toX, toY, promoChar);
11173         currentMove = forwardMostMove;
11174         return TRUE;
11175     }
11176 }
11177
11178 /* Load the nth game from the given file */
11179 int
11180 LoadGameFromFile (char *filename, int n, char *title, int useList)
11181 {
11182     FILE *f;
11183     char buf[MSG_SIZ];
11184
11185     if (strcmp(filename, "-") == 0) {
11186         f = stdin;
11187         title = "stdin";
11188     } else {
11189         f = fopen(filename, "rb");
11190         if (f == NULL) {
11191           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11192             DisplayError(buf, errno);
11193             return FALSE;
11194         }
11195     }
11196     if (fseek(f, 0, 0) == -1) {
11197         /* f is not seekable; probably a pipe */
11198         useList = FALSE;
11199     }
11200     if (useList && n == 0) {
11201         int error = GameListBuild(f);
11202         if (error) {
11203             DisplayError(_("Cannot build game list"), error);
11204         } else if (!ListEmpty(&gameList) &&
11205                    ((ListGame *) gameList.tailPred)->number > 1) {
11206             GameListPopUp(f, title);
11207             return TRUE;
11208         }
11209         GameListDestroy();
11210         n = 1;
11211     }
11212     if (n == 0) n = 1;
11213     return LoadGame(f, n, title, FALSE);
11214 }
11215
11216
11217 void
11218 MakeRegisteredMove ()
11219 {
11220     int fromX, fromY, toX, toY;
11221     char promoChar;
11222     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11223         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11224           case CMAIL_MOVE:
11225           case CMAIL_DRAW:
11226             if (appData.debugMode)
11227               fprintf(debugFP, "Restoring %s for game %d\n",
11228                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11229
11230             thinkOutput[0] = NULLCHAR;
11231             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11232             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11233             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11234             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11235             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11236             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11237             MakeMove(fromX, fromY, toX, toY, promoChar);
11238             ShowMove(fromX, fromY, toX, toY);
11239
11240             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11241               case MT_NONE:
11242               case MT_CHECK:
11243                 break;
11244
11245               case MT_CHECKMATE:
11246               case MT_STAINMATE:
11247                 if (WhiteOnMove(currentMove)) {
11248                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11249                 } else {
11250                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11251                 }
11252                 break;
11253
11254               case MT_STALEMATE:
11255                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11256                 break;
11257             }
11258
11259             break;
11260
11261           case CMAIL_RESIGN:
11262             if (WhiteOnMove(currentMove)) {
11263                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11264             } else {
11265                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11266             }
11267             break;
11268
11269           case CMAIL_ACCEPT:
11270             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11271             break;
11272
11273           default:
11274             break;
11275         }
11276     }
11277
11278     return;
11279 }
11280
11281 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11282 int
11283 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11284 {
11285     int retVal;
11286
11287     if (gameNumber > nCmailGames) {
11288         DisplayError(_("No more games in this message"), 0);
11289         return FALSE;
11290     }
11291     if (f == lastLoadGameFP) {
11292         int offset = gameNumber - lastLoadGameNumber;
11293         if (offset == 0) {
11294             cmailMsg[0] = NULLCHAR;
11295             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11296                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11297                 nCmailMovesRegistered--;
11298             }
11299             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11300             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11301                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11302             }
11303         } else {
11304             if (! RegisterMove()) return FALSE;
11305         }
11306     }
11307
11308     retVal = LoadGame(f, gameNumber, title, useList);
11309
11310     /* Make move registered during previous look at this game, if any */
11311     MakeRegisteredMove();
11312
11313     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11314         commentList[currentMove]
11315           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11316         DisplayComment(currentMove - 1, commentList[currentMove]);
11317     }
11318
11319     return retVal;
11320 }
11321
11322 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11323 int
11324 ReloadGame (int offset)
11325 {
11326     int gameNumber = lastLoadGameNumber + offset;
11327     if (lastLoadGameFP == NULL) {
11328         DisplayError(_("No game has been loaded yet"), 0);
11329         return FALSE;
11330     }
11331     if (gameNumber <= 0) {
11332         DisplayError(_("Can't back up any further"), 0);
11333         return FALSE;
11334     }
11335     if (cmailMsgLoaded) {
11336         return CmailLoadGame(lastLoadGameFP, gameNumber,
11337                              lastLoadGameTitle, lastLoadGameUseList);
11338     } else {
11339         return LoadGame(lastLoadGameFP, gameNumber,
11340                         lastLoadGameTitle, lastLoadGameUseList);
11341     }
11342 }
11343
11344 int keys[EmptySquare+1];
11345
11346 int
11347 PositionMatches (Board b1, Board b2)
11348 {
11349     int r, f, sum=0;
11350     switch(appData.searchMode) {
11351         case 1: return CompareWithRights(b1, b2);
11352         case 2:
11353             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11354                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11355             }
11356             return TRUE;
11357         case 3:
11358             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11359               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11360                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11361             }
11362             return sum==0;
11363         case 4:
11364             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11365                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11366             }
11367             return sum==0;
11368     }
11369     return TRUE;
11370 }
11371
11372 #define Q_PROMO  4
11373 #define Q_EP     3
11374 #define Q_BCASTL 2
11375 #define Q_WCASTL 1
11376
11377 int pieceList[256], quickBoard[256];
11378 ChessSquare pieceType[256] = { EmptySquare };
11379 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11380 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11381 int soughtTotal, turn;
11382 Boolean epOK, flipSearch;
11383
11384 typedef struct {
11385     unsigned char piece, to;
11386 } Move;
11387
11388 #define DSIZE (250000)
11389
11390 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11391 Move *moveDatabase = initialSpace;
11392 unsigned int movePtr, dataSize = DSIZE;
11393
11394 int
11395 MakePieceList (Board board, int *counts)
11396 {
11397     int r, f, n=Q_PROMO, total=0;
11398     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11399     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11400         int sq = f + (r<<4);
11401         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11402             quickBoard[sq] = ++n;
11403             pieceList[n] = sq;
11404             pieceType[n] = board[r][f];
11405             counts[board[r][f]]++;
11406             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11407             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11408             total++;
11409         }
11410     }
11411     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11412     return total;
11413 }
11414
11415 void
11416 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11417 {
11418     int sq = fromX + (fromY<<4);
11419     int piece = quickBoard[sq];
11420     quickBoard[sq] = 0;
11421     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11422     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11423         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11424         moveDatabase[movePtr++].piece = Q_WCASTL;
11425         quickBoard[sq] = piece;
11426         piece = quickBoard[from]; quickBoard[from] = 0;
11427         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11428     } else
11429     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11430         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11431         moveDatabase[movePtr++].piece = Q_BCASTL;
11432         quickBoard[sq] = piece;
11433         piece = quickBoard[from]; quickBoard[from] = 0;
11434         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11435     } else
11436     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11437         quickBoard[(fromY<<4)+toX] = 0;
11438         moveDatabase[movePtr].piece = Q_EP;
11439         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11440         moveDatabase[movePtr].to = sq;
11441     } else
11442     if(promoPiece != pieceType[piece]) {
11443         moveDatabase[movePtr++].piece = Q_PROMO;
11444         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11445     }
11446     moveDatabase[movePtr].piece = piece;
11447     quickBoard[sq] = piece;
11448     movePtr++;
11449 }
11450
11451 int
11452 PackGame (Board board)
11453 {
11454     Move *newSpace = NULL;
11455     moveDatabase[movePtr].piece = 0; // terminate previous game
11456     if(movePtr > dataSize) {
11457         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11458         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11459         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11460         if(newSpace) {
11461             int i;
11462             Move *p = moveDatabase, *q = newSpace;
11463             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11464             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11465             moveDatabase = newSpace;
11466         } else { // calloc failed, we must be out of memory. Too bad...
11467             dataSize = 0; // prevent calloc events for all subsequent games
11468             return 0;     // and signal this one isn't cached
11469         }
11470     }
11471     movePtr++;
11472     MakePieceList(board, counts);
11473     return movePtr;
11474 }
11475
11476 int
11477 QuickCompare (Board board, int *minCounts, int *maxCounts)
11478 {   // compare according to search mode
11479     int r, f;
11480     switch(appData.searchMode)
11481     {
11482       case 1: // exact position match
11483         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11484         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11485             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11486         }
11487         break;
11488       case 2: // can have extra material on empty squares
11489         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11490             if(board[r][f] == EmptySquare) continue;
11491             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11492         }
11493         break;
11494       case 3: // material with exact Pawn structure
11495         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11496             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11497             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11498         } // fall through to material comparison
11499       case 4: // exact material
11500         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11501         break;
11502       case 6: // material range with given imbalance
11503         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11504         // fall through to range comparison
11505       case 5: // material range
11506         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11507     }
11508     return TRUE;
11509 }
11510
11511 int
11512 QuickScan (Board board, Move *move)
11513 {   // reconstruct game,and compare all positions in it
11514     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11515     do {
11516         int piece = move->piece;
11517         int to = move->to, from = pieceList[piece];
11518         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11519           if(!piece) return -1;
11520           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11521             piece = (++move)->piece;
11522             from = pieceList[piece];
11523             counts[pieceType[piece]]--;
11524             pieceType[piece] = (ChessSquare) move->to;
11525             counts[move->to]++;
11526           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11527             counts[pieceType[quickBoard[to]]]--;
11528             quickBoard[to] = 0; total--;
11529             move++;
11530             continue;
11531           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11532             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11533             from  = pieceList[piece]; // so this must be King
11534             quickBoard[from] = 0;
11535             quickBoard[to] = piece;
11536             pieceList[piece] = to;
11537             move++;
11538             continue;
11539           }
11540         }
11541         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11542         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11543         quickBoard[from] = 0;
11544         quickBoard[to] = piece;
11545         pieceList[piece] = to;
11546         cnt++; turn ^= 3;
11547         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11548            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11549            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11550                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11551           ) {
11552             static int lastCounts[EmptySquare+1];
11553             int i;
11554             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11555             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11556         } else stretch = 0;
11557         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11558         move++;
11559     } while(1);
11560 }
11561
11562 void
11563 InitSearch ()
11564 {
11565     int r, f;
11566     flipSearch = FALSE;
11567     CopyBoard(soughtBoard, boards[currentMove]);
11568     soughtTotal = MakePieceList(soughtBoard, maxSought);
11569     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11570     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11571     CopyBoard(reverseBoard, boards[currentMove]);
11572     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11573         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11574         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11575         reverseBoard[r][f] = piece;
11576     }
11577     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11578     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11579     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11580                  || (boards[currentMove][CASTLING][2] == NoRights || 
11581                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11582                  && (boards[currentMove][CASTLING][5] == NoRights || 
11583                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11584       ) {
11585         flipSearch = TRUE;
11586         CopyBoard(flipBoard, soughtBoard);
11587         CopyBoard(rotateBoard, reverseBoard);
11588         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11589             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11590             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11591         }
11592     }
11593     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11594     if(appData.searchMode >= 5) {
11595         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11596         MakePieceList(soughtBoard, minSought);
11597         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11598     }
11599     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11600         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11601 }
11602
11603 GameInfo dummyInfo;
11604
11605 int
11606 GameContainsPosition (FILE *f, ListGame *lg)
11607 {
11608     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11609     int fromX, fromY, toX, toY;
11610     char promoChar;
11611     static int initDone=FALSE;
11612
11613     // weed out games based on numerical tag comparison
11614     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11615     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11616     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11617     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11618     if(!initDone) {
11619         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11620         initDone = TRUE;
11621     }
11622     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11623     else CopyBoard(boards[scratch], initialPosition); // default start position
11624     if(lg->moves) {
11625         turn = btm + 1;
11626         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11627         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11628     }
11629     if(btm) plyNr++;
11630     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11631     fseek(f, lg->offset, 0);
11632     yynewfile(f);
11633     while(1) {
11634         yyboardindex = scratch;
11635         quickFlag = plyNr+1;
11636         next = Myylex();
11637         quickFlag = 0;
11638         switch(next) {
11639             case PGNTag:
11640                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11641             default:
11642                 continue;
11643
11644             case XBoardGame:
11645             case GNUChessGame:
11646                 if(plyNr) return -1; // after we have seen moves, this is for new game
11647               continue;
11648
11649             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11650             case ImpossibleMove:
11651             case WhiteWins: // game ends here with these four
11652             case BlackWins:
11653             case GameIsDrawn:
11654             case GameUnfinished:
11655                 return -1;
11656
11657             case IllegalMove:
11658                 if(appData.testLegality) return -1;
11659             case WhiteCapturesEnPassant:
11660             case BlackCapturesEnPassant:
11661             case WhitePromotion:
11662             case BlackPromotion:
11663             case WhiteNonPromotion:
11664             case BlackNonPromotion:
11665             case NormalMove:
11666             case WhiteKingSideCastle:
11667             case WhiteQueenSideCastle:
11668             case BlackKingSideCastle:
11669             case BlackQueenSideCastle:
11670             case WhiteKingSideCastleWild:
11671             case WhiteQueenSideCastleWild:
11672             case BlackKingSideCastleWild:
11673             case BlackQueenSideCastleWild:
11674             case WhiteHSideCastleFR:
11675             case WhiteASideCastleFR:
11676             case BlackHSideCastleFR:
11677             case BlackASideCastleFR:
11678                 fromX = currentMoveString[0] - AAA;
11679                 fromY = currentMoveString[1] - ONE;
11680                 toX = currentMoveString[2] - AAA;
11681                 toY = currentMoveString[3] - ONE;
11682                 promoChar = currentMoveString[4];
11683                 break;
11684             case WhiteDrop:
11685             case BlackDrop:
11686                 fromX = next == WhiteDrop ?
11687                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11688                   (int) CharToPiece(ToLower(currentMoveString[0]));
11689                 fromY = DROP_RANK;
11690                 toX = currentMoveString[2] - AAA;
11691                 toY = currentMoveString[3] - ONE;
11692                 promoChar = 0;
11693                 break;
11694         }
11695         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11696         plyNr++;
11697         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11698         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11699         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11700         if(appData.findMirror) {
11701             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11702             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11703         }
11704     }
11705 }
11706
11707 /* Load the nth game from open file f */
11708 int
11709 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11710 {
11711     ChessMove cm;
11712     char buf[MSG_SIZ];
11713     int gn = gameNumber;
11714     ListGame *lg = NULL;
11715     int numPGNTags = 0;
11716     int err, pos = -1;
11717     GameMode oldGameMode;
11718     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11719
11720     if (appData.debugMode)
11721         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11722
11723     if (gameMode == Training )
11724         SetTrainingModeOff();
11725
11726     oldGameMode = gameMode;
11727     if (gameMode != BeginningOfGame) {
11728       Reset(FALSE, TRUE);
11729     }
11730
11731     gameFileFP = f;
11732     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11733         fclose(lastLoadGameFP);
11734     }
11735
11736     if (useList) {
11737         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11738
11739         if (lg) {
11740             fseek(f, lg->offset, 0);
11741             GameListHighlight(gameNumber);
11742             pos = lg->position;
11743             gn = 1;
11744         }
11745         else {
11746             DisplayError(_("Game number out of range"), 0);
11747             return FALSE;
11748         }
11749     } else {
11750         GameListDestroy();
11751         if (fseek(f, 0, 0) == -1) {
11752             if (f == lastLoadGameFP ?
11753                 gameNumber == lastLoadGameNumber + 1 :
11754                 gameNumber == 1) {
11755                 gn = 1;
11756             } else {
11757                 DisplayError(_("Can't seek on game file"), 0);
11758                 return FALSE;
11759             }
11760         }
11761     }
11762     lastLoadGameFP = f;
11763     lastLoadGameNumber = gameNumber;
11764     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11765     lastLoadGameUseList = useList;
11766
11767     yynewfile(f);
11768
11769     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11770       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11771                 lg->gameInfo.black);
11772             DisplayTitle(buf);
11773     } else if (*title != NULLCHAR) {
11774         if (gameNumber > 1) {
11775           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11776             DisplayTitle(buf);
11777         } else {
11778             DisplayTitle(title);
11779         }
11780     }
11781
11782     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11783         gameMode = PlayFromGameFile;
11784         ModeHighlight();
11785     }
11786
11787     currentMove = forwardMostMove = backwardMostMove = 0;
11788     CopyBoard(boards[0], initialPosition);
11789     StopClocks();
11790
11791     /*
11792      * Skip the first gn-1 games in the file.
11793      * Also skip over anything that precedes an identifiable
11794      * start of game marker, to avoid being confused by
11795      * garbage at the start of the file.  Currently
11796      * recognized start of game markers are the move number "1",
11797      * the pattern "gnuchess .* game", the pattern
11798      * "^[#;%] [^ ]* game file", and a PGN tag block.
11799      * A game that starts with one of the latter two patterns
11800      * will also have a move number 1, possibly
11801      * following a position diagram.
11802      * 5-4-02: Let's try being more lenient and allowing a game to
11803      * start with an unnumbered move.  Does that break anything?
11804      */
11805     cm = lastLoadGameStart = EndOfFile;
11806     while (gn > 0) {
11807         yyboardindex = forwardMostMove;
11808         cm = (ChessMove) Myylex();
11809         switch (cm) {
11810           case EndOfFile:
11811             if (cmailMsgLoaded) {
11812                 nCmailGames = CMAIL_MAX_GAMES - gn;
11813             } else {
11814                 Reset(TRUE, TRUE);
11815                 DisplayError(_("Game not found in file"), 0);
11816             }
11817             return FALSE;
11818
11819           case GNUChessGame:
11820           case XBoardGame:
11821             gn--;
11822             lastLoadGameStart = cm;
11823             break;
11824
11825           case MoveNumberOne:
11826             switch (lastLoadGameStart) {
11827               case GNUChessGame:
11828               case XBoardGame:
11829               case PGNTag:
11830                 break;
11831               case MoveNumberOne:
11832               case EndOfFile:
11833                 gn--;           /* count this game */
11834                 lastLoadGameStart = cm;
11835                 break;
11836               default:
11837                 /* impossible */
11838                 break;
11839             }
11840             break;
11841
11842           case PGNTag:
11843             switch (lastLoadGameStart) {
11844               case GNUChessGame:
11845               case PGNTag:
11846               case MoveNumberOne:
11847               case EndOfFile:
11848                 gn--;           /* count this game */
11849                 lastLoadGameStart = cm;
11850                 break;
11851               case XBoardGame:
11852                 lastLoadGameStart = cm; /* game counted already */
11853                 break;
11854               default:
11855                 /* impossible */
11856                 break;
11857             }
11858             if (gn > 0) {
11859                 do {
11860                     yyboardindex = forwardMostMove;
11861                     cm = (ChessMove) Myylex();
11862                 } while (cm == PGNTag || cm == Comment);
11863             }
11864             break;
11865
11866           case WhiteWins:
11867           case BlackWins:
11868           case GameIsDrawn:
11869             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11870                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11871                     != CMAIL_OLD_RESULT) {
11872                     nCmailResults ++ ;
11873                     cmailResult[  CMAIL_MAX_GAMES
11874                                 - gn - 1] = CMAIL_OLD_RESULT;
11875                 }
11876             }
11877             break;
11878
11879           case NormalMove:
11880             /* Only a NormalMove can be at the start of a game
11881              * without a position diagram. */
11882             if (lastLoadGameStart == EndOfFile ) {
11883               gn--;
11884               lastLoadGameStart = MoveNumberOne;
11885             }
11886             break;
11887
11888           default:
11889             break;
11890         }
11891     }
11892
11893     if (appData.debugMode)
11894       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11895
11896     if (cm == XBoardGame) {
11897         /* Skip any header junk before position diagram and/or move 1 */
11898         for (;;) {
11899             yyboardindex = forwardMostMove;
11900             cm = (ChessMove) Myylex();
11901
11902             if (cm == EndOfFile ||
11903                 cm == GNUChessGame || cm == XBoardGame) {
11904                 /* Empty game; pretend end-of-file and handle later */
11905                 cm = EndOfFile;
11906                 break;
11907             }
11908
11909             if (cm == MoveNumberOne || cm == PositionDiagram ||
11910                 cm == PGNTag || cm == Comment)
11911               break;
11912         }
11913     } else if (cm == GNUChessGame) {
11914         if (gameInfo.event != NULL) {
11915             free(gameInfo.event);
11916         }
11917         gameInfo.event = StrSave(yy_text);
11918     }
11919
11920     startedFromSetupPosition = FALSE;
11921     while (cm == PGNTag) {
11922         if (appData.debugMode)
11923           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11924         err = ParsePGNTag(yy_text, &gameInfo);
11925         if (!err) numPGNTags++;
11926
11927         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11928         if(gameInfo.variant != oldVariant) {
11929             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11930             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11931             InitPosition(TRUE);
11932             oldVariant = gameInfo.variant;
11933             if (appData.debugMode)
11934               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11935         }
11936
11937
11938         if (gameInfo.fen != NULL) {
11939           Board initial_position;
11940           startedFromSetupPosition = TRUE;
11941           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11942             Reset(TRUE, TRUE);
11943             DisplayError(_("Bad FEN position in file"), 0);
11944             return FALSE;
11945           }
11946           CopyBoard(boards[0], initial_position);
11947           if (blackPlaysFirst) {
11948             currentMove = forwardMostMove = backwardMostMove = 1;
11949             CopyBoard(boards[1], initial_position);
11950             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11951             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11952             timeRemaining[0][1] = whiteTimeRemaining;
11953             timeRemaining[1][1] = blackTimeRemaining;
11954             if (commentList[0] != NULL) {
11955               commentList[1] = commentList[0];
11956               commentList[0] = NULL;
11957             }
11958           } else {
11959             currentMove = forwardMostMove = backwardMostMove = 0;
11960           }
11961           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11962           {   int i;
11963               initialRulePlies = FENrulePlies;
11964               for( i=0; i< nrCastlingRights; i++ )
11965                   initialRights[i] = initial_position[CASTLING][i];
11966           }
11967           yyboardindex = forwardMostMove;
11968           free(gameInfo.fen);
11969           gameInfo.fen = NULL;
11970         }
11971
11972         yyboardindex = forwardMostMove;
11973         cm = (ChessMove) Myylex();
11974
11975         /* Handle comments interspersed among the tags */
11976         while (cm == Comment) {
11977             char *p;
11978             if (appData.debugMode)
11979               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11980             p = yy_text;
11981             AppendComment(currentMove, p, FALSE);
11982             yyboardindex = forwardMostMove;
11983             cm = (ChessMove) Myylex();
11984         }
11985     }
11986
11987     /* don't rely on existence of Event tag since if game was
11988      * pasted from clipboard the Event tag may not exist
11989      */
11990     if (numPGNTags > 0){
11991         char *tags;
11992         if (gameInfo.variant == VariantNormal) {
11993           VariantClass v = StringToVariant(gameInfo.event);
11994           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11995           if(v < VariantShogi) gameInfo.variant = v;
11996         }
11997         if (!matchMode) {
11998           if( appData.autoDisplayTags ) {
11999             tags = PGNTags(&gameInfo);
12000             TagsPopUp(tags, CmailMsg());
12001             free(tags);
12002           }
12003         }
12004     } else {
12005         /* Make something up, but don't display it now */
12006         SetGameInfo();
12007         TagsPopDown();
12008     }
12009
12010     if (cm == PositionDiagram) {
12011         int i, j;
12012         char *p;
12013         Board initial_position;
12014
12015         if (appData.debugMode)
12016           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12017
12018         if (!startedFromSetupPosition) {
12019             p = yy_text;
12020             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12021               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12022                 switch (*p) {
12023                   case '{':
12024                   case '[':
12025                   case '-':
12026                   case ' ':
12027                   case '\t':
12028                   case '\n':
12029                   case '\r':
12030                     break;
12031                   default:
12032                     initial_position[i][j++] = CharToPiece(*p);
12033                     break;
12034                 }
12035             while (*p == ' ' || *p == '\t' ||
12036                    *p == '\n' || *p == '\r') p++;
12037
12038             if (strncmp(p, "black", strlen("black"))==0)
12039               blackPlaysFirst = TRUE;
12040             else
12041               blackPlaysFirst = FALSE;
12042             startedFromSetupPosition = TRUE;
12043
12044             CopyBoard(boards[0], initial_position);
12045             if (blackPlaysFirst) {
12046                 currentMove = forwardMostMove = backwardMostMove = 1;
12047                 CopyBoard(boards[1], initial_position);
12048                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12049                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12050                 timeRemaining[0][1] = whiteTimeRemaining;
12051                 timeRemaining[1][1] = blackTimeRemaining;
12052                 if (commentList[0] != NULL) {
12053                     commentList[1] = commentList[0];
12054                     commentList[0] = NULL;
12055                 }
12056             } else {
12057                 currentMove = forwardMostMove = backwardMostMove = 0;
12058             }
12059         }
12060         yyboardindex = forwardMostMove;
12061         cm = (ChessMove) Myylex();
12062     }
12063
12064     if (first.pr == NoProc) {
12065         StartChessProgram(&first);
12066     }
12067     InitChessProgram(&first, FALSE);
12068     SendToProgram("force\n", &first);
12069     if (startedFromSetupPosition) {
12070         SendBoard(&first, forwardMostMove);
12071     if (appData.debugMode) {
12072         fprintf(debugFP, "Load Game\n");
12073     }
12074         DisplayBothClocks();
12075     }
12076
12077     /* [HGM] server: flag to write setup moves in broadcast file as one */
12078     loadFlag = appData.suppressLoadMoves;
12079
12080     while (cm == Comment) {
12081         char *p;
12082         if (appData.debugMode)
12083           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12084         p = yy_text;
12085         AppendComment(currentMove, p, FALSE);
12086         yyboardindex = forwardMostMove;
12087         cm = (ChessMove) Myylex();
12088     }
12089
12090     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12091         cm == WhiteWins || cm == BlackWins ||
12092         cm == GameIsDrawn || cm == GameUnfinished) {
12093         DisplayMessage("", _("No moves in game"));
12094         if (cmailMsgLoaded) {
12095             if (appData.debugMode)
12096               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12097             ClearHighlights();
12098             flipView = FALSE;
12099         }
12100         DrawPosition(FALSE, boards[currentMove]);
12101         DisplayBothClocks();
12102         gameMode = EditGame;
12103         ModeHighlight();
12104         gameFileFP = NULL;
12105         cmailOldMove = 0;
12106         return TRUE;
12107     }
12108
12109     // [HGM] PV info: routine tests if comment empty
12110     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12111         DisplayComment(currentMove - 1, commentList[currentMove]);
12112     }
12113     if (!matchMode && appData.timeDelay != 0)
12114       DrawPosition(FALSE, boards[currentMove]);
12115
12116     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12117       programStats.ok_to_send = 1;
12118     }
12119
12120     /* if the first token after the PGN tags is a move
12121      * and not move number 1, retrieve it from the parser
12122      */
12123     if (cm != MoveNumberOne)
12124         LoadGameOneMove(cm);
12125
12126     /* load the remaining moves from the file */
12127     while (LoadGameOneMove(EndOfFile)) {
12128       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12129       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12130     }
12131
12132     /* rewind to the start of the game */
12133     currentMove = backwardMostMove;
12134
12135     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12136
12137     if (oldGameMode == AnalyzeFile ||
12138         oldGameMode == AnalyzeMode) {
12139       AnalyzeFileEvent();
12140     }
12141
12142     if (!matchMode && pos >= 0) {
12143         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12144     } else
12145     if (matchMode || appData.timeDelay == 0) {
12146       ToEndEvent();
12147     } else if (appData.timeDelay > 0) {
12148       AutoPlayGameLoop();
12149     }
12150
12151     if (appData.debugMode)
12152         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12153
12154     loadFlag = 0; /* [HGM] true game starts */
12155     return TRUE;
12156 }
12157
12158 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12159 int
12160 ReloadPosition (int offset)
12161 {
12162     int positionNumber = lastLoadPositionNumber + offset;
12163     if (lastLoadPositionFP == NULL) {
12164         DisplayError(_("No position has been loaded yet"), 0);
12165         return FALSE;
12166     }
12167     if (positionNumber <= 0) {
12168         DisplayError(_("Can't back up any further"), 0);
12169         return FALSE;
12170     }
12171     return LoadPosition(lastLoadPositionFP, positionNumber,
12172                         lastLoadPositionTitle);
12173 }
12174
12175 /* Load the nth position from the given file */
12176 int
12177 LoadPositionFromFile (char *filename, int n, char *title)
12178 {
12179     FILE *f;
12180     char buf[MSG_SIZ];
12181
12182     if (strcmp(filename, "-") == 0) {
12183         return LoadPosition(stdin, n, "stdin");
12184     } else {
12185         f = fopen(filename, "rb");
12186         if (f == NULL) {
12187             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12188             DisplayError(buf, errno);
12189             return FALSE;
12190         } else {
12191             return LoadPosition(f, n, title);
12192         }
12193     }
12194 }
12195
12196 /* Load the nth position from the given open file, and close it */
12197 int
12198 LoadPosition (FILE *f, int positionNumber, char *title)
12199 {
12200     char *p, line[MSG_SIZ];
12201     Board initial_position;
12202     int i, j, fenMode, pn;
12203
12204     if (gameMode == Training )
12205         SetTrainingModeOff();
12206
12207     if (gameMode != BeginningOfGame) {
12208         Reset(FALSE, TRUE);
12209     }
12210     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12211         fclose(lastLoadPositionFP);
12212     }
12213     if (positionNumber == 0) positionNumber = 1;
12214     lastLoadPositionFP = f;
12215     lastLoadPositionNumber = positionNumber;
12216     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12217     if (first.pr == NoProc && !appData.noChessProgram) {
12218       StartChessProgram(&first);
12219       InitChessProgram(&first, FALSE);
12220     }
12221     pn = positionNumber;
12222     if (positionNumber < 0) {
12223         /* Negative position number means to seek to that byte offset */
12224         if (fseek(f, -positionNumber, 0) == -1) {
12225             DisplayError(_("Can't seek on position file"), 0);
12226             return FALSE;
12227         };
12228         pn = 1;
12229     } else {
12230         if (fseek(f, 0, 0) == -1) {
12231             if (f == lastLoadPositionFP ?
12232                 positionNumber == lastLoadPositionNumber + 1 :
12233                 positionNumber == 1) {
12234                 pn = 1;
12235             } else {
12236                 DisplayError(_("Can't seek on position file"), 0);
12237                 return FALSE;
12238             }
12239         }
12240     }
12241     /* See if this file is FEN or old-style xboard */
12242     if (fgets(line, MSG_SIZ, f) == NULL) {
12243         DisplayError(_("Position not found in file"), 0);
12244         return FALSE;
12245     }
12246     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12247     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12248
12249     if (pn >= 2) {
12250         if (fenMode || line[0] == '#') pn--;
12251         while (pn > 0) {
12252             /* skip positions before number pn */
12253             if (fgets(line, MSG_SIZ, f) == NULL) {
12254                 Reset(TRUE, TRUE);
12255                 DisplayError(_("Position not found in file"), 0);
12256                 return FALSE;
12257             }
12258             if (fenMode || line[0] == '#') pn--;
12259         }
12260     }
12261
12262     if (fenMode) {
12263         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12264             DisplayError(_("Bad FEN position in file"), 0);
12265             return FALSE;
12266         }
12267     } else {
12268         (void) fgets(line, MSG_SIZ, f);
12269         (void) fgets(line, MSG_SIZ, f);
12270
12271         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12272             (void) fgets(line, MSG_SIZ, f);
12273             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12274                 if (*p == ' ')
12275                   continue;
12276                 initial_position[i][j++] = CharToPiece(*p);
12277             }
12278         }
12279
12280         blackPlaysFirst = FALSE;
12281         if (!feof(f)) {
12282             (void) fgets(line, MSG_SIZ, f);
12283             if (strncmp(line, "black", strlen("black"))==0)
12284               blackPlaysFirst = TRUE;
12285         }
12286     }
12287     startedFromSetupPosition = TRUE;
12288
12289     CopyBoard(boards[0], initial_position);
12290     if (blackPlaysFirst) {
12291         currentMove = forwardMostMove = backwardMostMove = 1;
12292         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12293         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12294         CopyBoard(boards[1], initial_position);
12295         DisplayMessage("", _("Black to play"));
12296     } else {
12297         currentMove = forwardMostMove = backwardMostMove = 0;
12298         DisplayMessage("", _("White to play"));
12299     }
12300     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12301     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12302         SendToProgram("force\n", &first);
12303         SendBoard(&first, forwardMostMove);
12304     }
12305     if (appData.debugMode) {
12306 int i, j;
12307   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12308   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12309         fprintf(debugFP, "Load Position\n");
12310     }
12311
12312     if (positionNumber > 1) {
12313       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12314         DisplayTitle(line);
12315     } else {
12316         DisplayTitle(title);
12317     }
12318     gameMode = EditGame;
12319     ModeHighlight();
12320     ResetClocks();
12321     timeRemaining[0][1] = whiteTimeRemaining;
12322     timeRemaining[1][1] = blackTimeRemaining;
12323     DrawPosition(FALSE, boards[currentMove]);
12324
12325     return TRUE;
12326 }
12327
12328
12329 void
12330 CopyPlayerNameIntoFileName (char **dest, char *src)
12331 {
12332     while (*src != NULLCHAR && *src != ',') {
12333         if (*src == ' ') {
12334             *(*dest)++ = '_';
12335             src++;
12336         } else {
12337             *(*dest)++ = *src++;
12338         }
12339     }
12340 }
12341
12342 char *
12343 DefaultFileName (char *ext)
12344 {
12345     static char def[MSG_SIZ];
12346     char *p;
12347
12348     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12349         p = def;
12350         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12351         *p++ = '-';
12352         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12353         *p++ = '.';
12354         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12355     } else {
12356         def[0] = NULLCHAR;
12357     }
12358     return def;
12359 }
12360
12361 /* Save the current game to the given file */
12362 int
12363 SaveGameToFile (char *filename, int append)
12364 {
12365     FILE *f;
12366     char buf[MSG_SIZ];
12367     int result, i, t,tot=0;
12368
12369     if (strcmp(filename, "-") == 0) {
12370         return SaveGame(stdout, 0, NULL);
12371     } else {
12372         for(i=0; i<10; i++) { // upto 10 tries
12373              f = fopen(filename, append ? "a" : "w");
12374              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12375              if(f || errno != 13) break;
12376              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12377              tot += t;
12378         }
12379         if (f == NULL) {
12380             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12381             DisplayError(buf, errno);
12382             return FALSE;
12383         } else {
12384             safeStrCpy(buf, lastMsg, MSG_SIZ);
12385             DisplayMessage(_("Waiting for access to save file"), "");
12386             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12387             DisplayMessage(_("Saving game"), "");
12388             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12389             result = SaveGame(f, 0, NULL);
12390             DisplayMessage(buf, "");
12391             return result;
12392         }
12393     }
12394 }
12395
12396 char *
12397 SavePart (char *str)
12398 {
12399     static char buf[MSG_SIZ];
12400     char *p;
12401
12402     p = strchr(str, ' ');
12403     if (p == NULL) return str;
12404     strncpy(buf, str, p - str);
12405     buf[p - str] = NULLCHAR;
12406     return buf;
12407 }
12408
12409 #define PGN_MAX_LINE 75
12410
12411 #define PGN_SIDE_WHITE  0
12412 #define PGN_SIDE_BLACK  1
12413
12414 static int
12415 FindFirstMoveOutOfBook (int side)
12416 {
12417     int result = -1;
12418
12419     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12420         int index = backwardMostMove;
12421         int has_book_hit = 0;
12422
12423         if( (index % 2) != side ) {
12424             index++;
12425         }
12426
12427         while( index < forwardMostMove ) {
12428             /* Check to see if engine is in book */
12429             int depth = pvInfoList[index].depth;
12430             int score = pvInfoList[index].score;
12431             int in_book = 0;
12432
12433             if( depth <= 2 ) {
12434                 in_book = 1;
12435             }
12436             else if( score == 0 && depth == 63 ) {
12437                 in_book = 1; /* Zappa */
12438             }
12439             else if( score == 2 && depth == 99 ) {
12440                 in_book = 1; /* Abrok */
12441             }
12442
12443             has_book_hit += in_book;
12444
12445             if( ! in_book ) {
12446                 result = index;
12447
12448                 break;
12449             }
12450
12451             index += 2;
12452         }
12453     }
12454
12455     return result;
12456 }
12457
12458 void
12459 GetOutOfBookInfo (char * buf)
12460 {
12461     int oob[2];
12462     int i;
12463     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12464
12465     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12466     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12467
12468     *buf = '\0';
12469
12470     if( oob[0] >= 0 || oob[1] >= 0 ) {
12471         for( i=0; i<2; i++ ) {
12472             int idx = oob[i];
12473
12474             if( idx >= 0 ) {
12475                 if( i > 0 && oob[0] >= 0 ) {
12476                     strcat( buf, "   " );
12477                 }
12478
12479                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12480                 sprintf( buf+strlen(buf), "%s%.2f",
12481                     pvInfoList[idx].score >= 0 ? "+" : "",
12482                     pvInfoList[idx].score / 100.0 );
12483             }
12484         }
12485     }
12486 }
12487
12488 /* Save game in PGN style and close the file */
12489 int
12490 SaveGamePGN (FILE *f)
12491 {
12492     int i, offset, linelen, newblock;
12493     time_t tm;
12494 //    char *movetext;
12495     char numtext[32];
12496     int movelen, numlen, blank;
12497     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12498
12499     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12500
12501     tm = time((time_t *) NULL);
12502
12503     PrintPGNTags(f, &gameInfo);
12504
12505     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12506
12507     if (backwardMostMove > 0 || startedFromSetupPosition) {
12508         char *fen = PositionToFEN(backwardMostMove, NULL);
12509         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12510         fprintf(f, "\n{--------------\n");
12511         PrintPosition(f, backwardMostMove);
12512         fprintf(f, "--------------}\n");
12513         free(fen);
12514     }
12515     else {
12516         /* [AS] Out of book annotation */
12517         if( appData.saveOutOfBookInfo ) {
12518             char buf[64];
12519
12520             GetOutOfBookInfo( buf );
12521
12522             if( buf[0] != '\0' ) {
12523                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12524             }
12525         }
12526
12527         fprintf(f, "\n");
12528     }
12529
12530     i = backwardMostMove;
12531     linelen = 0;
12532     newblock = TRUE;
12533
12534     while (i < forwardMostMove) {
12535         /* Print comments preceding this move */
12536         if (commentList[i] != NULL) {
12537             if (linelen > 0) fprintf(f, "\n");
12538             fprintf(f, "%s", commentList[i]);
12539             linelen = 0;
12540             newblock = TRUE;
12541         }
12542
12543         /* Format move number */
12544         if ((i % 2) == 0)
12545           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12546         else
12547           if (newblock)
12548             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12549           else
12550             numtext[0] = NULLCHAR;
12551
12552         numlen = strlen(numtext);
12553         newblock = FALSE;
12554
12555         /* Print move number */
12556         blank = linelen > 0 && numlen > 0;
12557         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12558             fprintf(f, "\n");
12559             linelen = 0;
12560             blank = 0;
12561         }
12562         if (blank) {
12563             fprintf(f, " ");
12564             linelen++;
12565         }
12566         fprintf(f, "%s", numtext);
12567         linelen += numlen;
12568
12569         /* Get move */
12570         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12571         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12572
12573         /* Print move */
12574         blank = linelen > 0 && movelen > 0;
12575         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12576             fprintf(f, "\n");
12577             linelen = 0;
12578             blank = 0;
12579         }
12580         if (blank) {
12581             fprintf(f, " ");
12582             linelen++;
12583         }
12584         fprintf(f, "%s", move_buffer);
12585         linelen += movelen;
12586
12587         /* [AS] Add PV info if present */
12588         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12589             /* [HGM] add time */
12590             char buf[MSG_SIZ]; int seconds;
12591
12592             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12593
12594             if( seconds <= 0)
12595               buf[0] = 0;
12596             else
12597               if( seconds < 30 )
12598                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12599               else
12600                 {
12601                   seconds = (seconds + 4)/10; // round to full seconds
12602                   if( seconds < 60 )
12603                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12604                   else
12605                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12606                 }
12607
12608             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12609                       pvInfoList[i].score >= 0 ? "+" : "",
12610                       pvInfoList[i].score / 100.0,
12611                       pvInfoList[i].depth,
12612                       buf );
12613
12614             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12615
12616             /* Print score/depth */
12617             blank = linelen > 0 && movelen > 0;
12618             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12619                 fprintf(f, "\n");
12620                 linelen = 0;
12621                 blank = 0;
12622             }
12623             if (blank) {
12624                 fprintf(f, " ");
12625                 linelen++;
12626             }
12627             fprintf(f, "%s", move_buffer);
12628             linelen += movelen;
12629         }
12630
12631         i++;
12632     }
12633
12634     /* Start a new line */
12635     if (linelen > 0) fprintf(f, "\n");
12636
12637     /* Print comments after last move */
12638     if (commentList[i] != NULL) {
12639         fprintf(f, "%s\n", commentList[i]);
12640     }
12641
12642     /* Print result */
12643     if (gameInfo.resultDetails != NULL &&
12644         gameInfo.resultDetails[0] != NULLCHAR) {
12645         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12646                 PGNResult(gameInfo.result));
12647     } else {
12648         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12649     }
12650
12651     fclose(f);
12652     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12653     return TRUE;
12654 }
12655
12656 /* Save game in old style and close the file */
12657 int
12658 SaveGameOldStyle (FILE *f)
12659 {
12660     int i, offset;
12661     time_t tm;
12662
12663     tm = time((time_t *) NULL);
12664
12665     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12666     PrintOpponents(f);
12667
12668     if (backwardMostMove > 0 || startedFromSetupPosition) {
12669         fprintf(f, "\n[--------------\n");
12670         PrintPosition(f, backwardMostMove);
12671         fprintf(f, "--------------]\n");
12672     } else {
12673         fprintf(f, "\n");
12674     }
12675
12676     i = backwardMostMove;
12677     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12678
12679     while (i < forwardMostMove) {
12680         if (commentList[i] != NULL) {
12681             fprintf(f, "[%s]\n", commentList[i]);
12682         }
12683
12684         if ((i % 2) == 1) {
12685             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12686             i++;
12687         } else {
12688             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12689             i++;
12690             if (commentList[i] != NULL) {
12691                 fprintf(f, "\n");
12692                 continue;
12693             }
12694             if (i >= forwardMostMove) {
12695                 fprintf(f, "\n");
12696                 break;
12697             }
12698             fprintf(f, "%s\n", parseList[i]);
12699             i++;
12700         }
12701     }
12702
12703     if (commentList[i] != NULL) {
12704         fprintf(f, "[%s]\n", commentList[i]);
12705     }
12706
12707     /* This isn't really the old style, but it's close enough */
12708     if (gameInfo.resultDetails != NULL &&
12709         gameInfo.resultDetails[0] != NULLCHAR) {
12710         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12711                 gameInfo.resultDetails);
12712     } else {
12713         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12714     }
12715
12716     fclose(f);
12717     return TRUE;
12718 }
12719
12720 /* Save the current game to open file f and close the file */
12721 int
12722 SaveGame (FILE *f, int dummy, char *dummy2)
12723 {
12724     if (gameMode == EditPosition) EditPositionDone(TRUE);
12725     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12726     if (appData.oldSaveStyle)
12727       return SaveGameOldStyle(f);
12728     else
12729       return SaveGamePGN(f);
12730 }
12731
12732 /* Save the current position to the given file */
12733 int
12734 SavePositionToFile (char *filename)
12735 {
12736     FILE *f;
12737     char buf[MSG_SIZ];
12738
12739     if (strcmp(filename, "-") == 0) {
12740         return SavePosition(stdout, 0, NULL);
12741     } else {
12742         f = fopen(filename, "a");
12743         if (f == NULL) {
12744             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12745             DisplayError(buf, errno);
12746             return FALSE;
12747         } else {
12748             safeStrCpy(buf, lastMsg, MSG_SIZ);
12749             DisplayMessage(_("Waiting for access to save file"), "");
12750             flock(fileno(f), LOCK_EX); // [HGM] lock
12751             DisplayMessage(_("Saving position"), "");
12752             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12753             SavePosition(f, 0, NULL);
12754             DisplayMessage(buf, "");
12755             return TRUE;
12756         }
12757     }
12758 }
12759
12760 /* Save the current position to the given open file and close the file */
12761 int
12762 SavePosition (FILE *f, int dummy, char *dummy2)
12763 {
12764     time_t tm;
12765     char *fen;
12766
12767     if (gameMode == EditPosition) EditPositionDone(TRUE);
12768     if (appData.oldSaveStyle) {
12769         tm = time((time_t *) NULL);
12770
12771         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12772         PrintOpponents(f);
12773         fprintf(f, "[--------------\n");
12774         PrintPosition(f, currentMove);
12775         fprintf(f, "--------------]\n");
12776     } else {
12777         fen = PositionToFEN(currentMove, NULL);
12778         fprintf(f, "%s\n", fen);
12779         free(fen);
12780     }
12781     fclose(f);
12782     return TRUE;
12783 }
12784
12785 void
12786 ReloadCmailMsgEvent (int unregister)
12787 {
12788 #if !WIN32
12789     static char *inFilename = NULL;
12790     static char *outFilename;
12791     int i;
12792     struct stat inbuf, outbuf;
12793     int status;
12794
12795     /* Any registered moves are unregistered if unregister is set, */
12796     /* i.e. invoked by the signal handler */
12797     if (unregister) {
12798         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12799             cmailMoveRegistered[i] = FALSE;
12800             if (cmailCommentList[i] != NULL) {
12801                 free(cmailCommentList[i]);
12802                 cmailCommentList[i] = NULL;
12803             }
12804         }
12805         nCmailMovesRegistered = 0;
12806     }
12807
12808     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12809         cmailResult[i] = CMAIL_NOT_RESULT;
12810     }
12811     nCmailResults = 0;
12812
12813     if (inFilename == NULL) {
12814         /* Because the filenames are static they only get malloced once  */
12815         /* and they never get freed                                      */
12816         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12817         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12818
12819         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12820         sprintf(outFilename, "%s.out", appData.cmailGameName);
12821     }
12822
12823     status = stat(outFilename, &outbuf);
12824     if (status < 0) {
12825         cmailMailedMove = FALSE;
12826     } else {
12827         status = stat(inFilename, &inbuf);
12828         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12829     }
12830
12831     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12832        counts the games, notes how each one terminated, etc.
12833
12834        It would be nice to remove this kludge and instead gather all
12835        the information while building the game list.  (And to keep it
12836        in the game list nodes instead of having a bunch of fixed-size
12837        parallel arrays.)  Note this will require getting each game's
12838        termination from the PGN tags, as the game list builder does
12839        not process the game moves.  --mann
12840        */
12841     cmailMsgLoaded = TRUE;
12842     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12843
12844     /* Load first game in the file or popup game menu */
12845     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12846
12847 #endif /* !WIN32 */
12848     return;
12849 }
12850
12851 int
12852 RegisterMove ()
12853 {
12854     FILE *f;
12855     char string[MSG_SIZ];
12856
12857     if (   cmailMailedMove
12858         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12859         return TRUE;            /* Allow free viewing  */
12860     }
12861
12862     /* Unregister move to ensure that we don't leave RegisterMove        */
12863     /* with the move registered when the conditions for registering no   */
12864     /* longer hold                                                       */
12865     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12866         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12867         nCmailMovesRegistered --;
12868
12869         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12870           {
12871               free(cmailCommentList[lastLoadGameNumber - 1]);
12872               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12873           }
12874     }
12875
12876     if (cmailOldMove == -1) {
12877         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12878         return FALSE;
12879     }
12880
12881     if (currentMove > cmailOldMove + 1) {
12882         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12883         return FALSE;
12884     }
12885
12886     if (currentMove < cmailOldMove) {
12887         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12888         return FALSE;
12889     }
12890
12891     if (forwardMostMove > currentMove) {
12892         /* Silently truncate extra moves */
12893         TruncateGame();
12894     }
12895
12896     if (   (currentMove == cmailOldMove + 1)
12897         || (   (currentMove == cmailOldMove)
12898             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12899                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12900         if (gameInfo.result != GameUnfinished) {
12901             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12902         }
12903
12904         if (commentList[currentMove] != NULL) {
12905             cmailCommentList[lastLoadGameNumber - 1]
12906               = StrSave(commentList[currentMove]);
12907         }
12908         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12909
12910         if (appData.debugMode)
12911           fprintf(debugFP, "Saving %s for game %d\n",
12912                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12913
12914         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12915
12916         f = fopen(string, "w");
12917         if (appData.oldSaveStyle) {
12918             SaveGameOldStyle(f); /* also closes the file */
12919
12920             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12921             f = fopen(string, "w");
12922             SavePosition(f, 0, NULL); /* also closes the file */
12923         } else {
12924             fprintf(f, "{--------------\n");
12925             PrintPosition(f, currentMove);
12926             fprintf(f, "--------------}\n\n");
12927
12928             SaveGame(f, 0, NULL); /* also closes the file*/
12929         }
12930
12931         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12932         nCmailMovesRegistered ++;
12933     } else if (nCmailGames == 1) {
12934         DisplayError(_("You have not made a move yet"), 0);
12935         return FALSE;
12936     }
12937
12938     return TRUE;
12939 }
12940
12941 void
12942 MailMoveEvent ()
12943 {
12944 #if !WIN32
12945     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12946     FILE *commandOutput;
12947     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12948     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12949     int nBuffers;
12950     int i;
12951     int archived;
12952     char *arcDir;
12953
12954     if (! cmailMsgLoaded) {
12955         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12956         return;
12957     }
12958
12959     if (nCmailGames == nCmailResults) {
12960         DisplayError(_("No unfinished games"), 0);
12961         return;
12962     }
12963
12964 #if CMAIL_PROHIBIT_REMAIL
12965     if (cmailMailedMove) {
12966       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);
12967         DisplayError(msg, 0);
12968         return;
12969     }
12970 #endif
12971
12972     if (! (cmailMailedMove || RegisterMove())) return;
12973
12974     if (   cmailMailedMove
12975         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12976       snprintf(string, MSG_SIZ, partCommandString,
12977                appData.debugMode ? " -v" : "", appData.cmailGameName);
12978         commandOutput = popen(string, "r");
12979
12980         if (commandOutput == NULL) {
12981             DisplayError(_("Failed to invoke cmail"), 0);
12982         } else {
12983             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12984                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12985             }
12986             if (nBuffers > 1) {
12987                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12988                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12989                 nBytes = MSG_SIZ - 1;
12990             } else {
12991                 (void) memcpy(msg, buffer, nBytes);
12992             }
12993             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12994
12995             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12996                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12997
12998                 archived = TRUE;
12999                 for (i = 0; i < nCmailGames; i ++) {
13000                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13001                         archived = FALSE;
13002                     }
13003                 }
13004                 if (   archived
13005                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13006                         != NULL)) {
13007                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13008                            arcDir,
13009                            appData.cmailGameName,
13010                            gameInfo.date);
13011                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13012                     cmailMsgLoaded = FALSE;
13013                 }
13014             }
13015
13016             DisplayInformation(msg);
13017             pclose(commandOutput);
13018         }
13019     } else {
13020         if ((*cmailMsg) != '\0') {
13021             DisplayInformation(cmailMsg);
13022         }
13023     }
13024
13025     return;
13026 #endif /* !WIN32 */
13027 }
13028
13029 char *
13030 CmailMsg ()
13031 {
13032 #if WIN32
13033     return NULL;
13034 #else
13035     int  prependComma = 0;
13036     char number[5];
13037     char string[MSG_SIZ];       /* Space for game-list */
13038     int  i;
13039
13040     if (!cmailMsgLoaded) return "";
13041
13042     if (cmailMailedMove) {
13043       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13044     } else {
13045         /* Create a list of games left */
13046       snprintf(string, MSG_SIZ, "[");
13047         for (i = 0; i < nCmailGames; i ++) {
13048             if (! (   cmailMoveRegistered[i]
13049                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13050                 if (prependComma) {
13051                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13052                 } else {
13053                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13054                     prependComma = 1;
13055                 }
13056
13057                 strcat(string, number);
13058             }
13059         }
13060         strcat(string, "]");
13061
13062         if (nCmailMovesRegistered + nCmailResults == 0) {
13063             switch (nCmailGames) {
13064               case 1:
13065                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13066                 break;
13067
13068               case 2:
13069                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13070                 break;
13071
13072               default:
13073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13074                          nCmailGames);
13075                 break;
13076             }
13077         } else {
13078             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13079               case 1:
13080                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13081                          string);
13082                 break;
13083
13084               case 0:
13085                 if (nCmailResults == nCmailGames) {
13086                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13087                 } else {
13088                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13089                 }
13090                 break;
13091
13092               default:
13093                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13094                          string);
13095             }
13096         }
13097     }
13098     return cmailMsg;
13099 #endif /* WIN32 */
13100 }
13101
13102 void
13103 ResetGameEvent ()
13104 {
13105     if (gameMode == Training)
13106       SetTrainingModeOff();
13107
13108     Reset(TRUE, TRUE);
13109     cmailMsgLoaded = FALSE;
13110     if (appData.icsActive) {
13111       SendToICS(ics_prefix);
13112       SendToICS("refresh\n");
13113     }
13114 }
13115
13116 void
13117 ExitEvent (int status)
13118 {
13119     exiting++;
13120     if (exiting > 2) {
13121       /* Give up on clean exit */
13122       exit(status);
13123     }
13124     if (exiting > 1) {
13125       /* Keep trying for clean exit */
13126       return;
13127     }
13128
13129     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13130
13131     if (telnetISR != NULL) {
13132       RemoveInputSource(telnetISR);
13133     }
13134     if (icsPR != NoProc) {
13135       DestroyChildProcess(icsPR, TRUE);
13136     }
13137
13138     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13139     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13140
13141     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13142     /* make sure this other one finishes before killing it!                  */
13143     if(endingGame) { int count = 0;
13144         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13145         while(endingGame && count++ < 10) DoSleep(1);
13146         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13147     }
13148
13149     /* Kill off chess programs */
13150     if (first.pr != NoProc) {
13151         ExitAnalyzeMode();
13152
13153         DoSleep( appData.delayBeforeQuit );
13154         SendToProgram("quit\n", &first);
13155         DoSleep( appData.delayAfterQuit );
13156         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13157     }
13158     if (second.pr != NoProc) {
13159         DoSleep( appData.delayBeforeQuit );
13160         SendToProgram("quit\n", &second);
13161         DoSleep( appData.delayAfterQuit );
13162         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13163     }
13164     if (first.isr != NULL) {
13165         RemoveInputSource(first.isr);
13166     }
13167     if (second.isr != NULL) {
13168         RemoveInputSource(second.isr);
13169     }
13170
13171     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13172     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13173
13174     ShutDownFrontEnd();
13175     exit(status);
13176 }
13177
13178 void
13179 PauseEvent ()
13180 {
13181     if (appData.debugMode)
13182         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13183     if (pausing) {
13184         pausing = FALSE;
13185         ModeHighlight();
13186         if (gameMode == MachinePlaysWhite ||
13187             gameMode == MachinePlaysBlack) {
13188             StartClocks();
13189         } else {
13190             DisplayBothClocks();
13191         }
13192         if (gameMode == PlayFromGameFile) {
13193             if (appData.timeDelay >= 0)
13194                 AutoPlayGameLoop();
13195         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13196             Reset(FALSE, TRUE);
13197             SendToICS(ics_prefix);
13198             SendToICS("refresh\n");
13199         } else if (currentMove < forwardMostMove) {
13200             ForwardInner(forwardMostMove);
13201         }
13202         pauseExamInvalid = FALSE;
13203     } else {
13204         switch (gameMode) {
13205           default:
13206             return;
13207           case IcsExamining:
13208             pauseExamForwardMostMove = forwardMostMove;
13209             pauseExamInvalid = FALSE;
13210             /* fall through */
13211           case IcsObserving:
13212           case IcsPlayingWhite:
13213           case IcsPlayingBlack:
13214             pausing = TRUE;
13215             ModeHighlight();
13216             return;
13217           case PlayFromGameFile:
13218             (void) StopLoadGameTimer();
13219             pausing = TRUE;
13220             ModeHighlight();
13221             break;
13222           case BeginningOfGame:
13223             if (appData.icsActive) return;
13224             /* else fall through */
13225           case MachinePlaysWhite:
13226           case MachinePlaysBlack:
13227           case TwoMachinesPlay:
13228             if (forwardMostMove == 0)
13229               return;           /* don't pause if no one has moved */
13230             if ((gameMode == MachinePlaysWhite &&
13231                  !WhiteOnMove(forwardMostMove)) ||
13232                 (gameMode == MachinePlaysBlack &&
13233                  WhiteOnMove(forwardMostMove))) {
13234                 StopClocks();
13235             }
13236             pausing = TRUE;
13237             ModeHighlight();
13238             break;
13239         }
13240     }
13241 }
13242
13243 void
13244 EditCommentEvent ()
13245 {
13246     char title[MSG_SIZ];
13247
13248     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13249       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13250     } else {
13251       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13252                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13253                parseList[currentMove - 1]);
13254     }
13255
13256     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13257 }
13258
13259
13260 void
13261 EditTagsEvent ()
13262 {
13263     char *tags = PGNTags(&gameInfo);
13264     bookUp = FALSE;
13265     EditTagsPopUp(tags, NULL);
13266     free(tags);
13267 }
13268
13269 void
13270 AnalyzeModeEvent ()
13271 {
13272     if (appData.noChessProgram || gameMode == AnalyzeMode)
13273       return;
13274
13275     if (gameMode != AnalyzeFile) {
13276         if (!appData.icsEngineAnalyze) {
13277                EditGameEvent();
13278                if (gameMode != EditGame) return;
13279         }
13280         ResurrectChessProgram();
13281         SendToProgram("analyze\n", &first);
13282         first.analyzing = TRUE;
13283         /*first.maybeThinking = TRUE;*/
13284         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13285         EngineOutputPopUp();
13286     }
13287     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13288     pausing = FALSE;
13289     ModeHighlight();
13290     SetGameInfo();
13291
13292     StartAnalysisClock();
13293     GetTimeMark(&lastNodeCountTime);
13294     lastNodeCount = 0;
13295 }
13296
13297 void
13298 AnalyzeFileEvent ()
13299 {
13300     if (appData.noChessProgram || gameMode == AnalyzeFile)
13301       return;
13302
13303     if (gameMode != AnalyzeMode) {
13304         EditGameEvent();
13305         if (gameMode != EditGame) return;
13306         ResurrectChessProgram();
13307         SendToProgram("analyze\n", &first);
13308         first.analyzing = TRUE;
13309         /*first.maybeThinking = TRUE;*/
13310         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13311         EngineOutputPopUp();
13312     }
13313     gameMode = AnalyzeFile;
13314     pausing = FALSE;
13315     ModeHighlight();
13316     SetGameInfo();
13317
13318     StartAnalysisClock();
13319     GetTimeMark(&lastNodeCountTime);
13320     lastNodeCount = 0;
13321     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13322 }
13323
13324 void
13325 MachineWhiteEvent ()
13326 {
13327     char buf[MSG_SIZ];
13328     char *bookHit = NULL;
13329
13330     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13331       return;
13332
13333
13334     if (gameMode == PlayFromGameFile ||
13335         gameMode == TwoMachinesPlay  ||
13336         gameMode == Training         ||
13337         gameMode == AnalyzeMode      ||
13338         gameMode == EndOfGame)
13339         EditGameEvent();
13340
13341     if (gameMode == EditPosition)
13342         EditPositionDone(TRUE);
13343
13344     if (!WhiteOnMove(currentMove)) {
13345         DisplayError(_("It is not White's turn"), 0);
13346         return;
13347     }
13348
13349     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13350       ExitAnalyzeMode();
13351
13352     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13353         gameMode == AnalyzeFile)
13354         TruncateGame();
13355
13356     ResurrectChessProgram();    /* in case it isn't running */
13357     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13358         gameMode = MachinePlaysWhite;
13359         ResetClocks();
13360     } else
13361     gameMode = MachinePlaysWhite;
13362     pausing = FALSE;
13363     ModeHighlight();
13364     SetGameInfo();
13365     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13366     DisplayTitle(buf);
13367     if (first.sendName) {
13368       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13369       SendToProgram(buf, &first);
13370     }
13371     if (first.sendTime) {
13372       if (first.useColors) {
13373         SendToProgram("black\n", &first); /*gnu kludge*/
13374       }
13375       SendTimeRemaining(&first, TRUE);
13376     }
13377     if (first.useColors) {
13378       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13379     }
13380     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13381     SetMachineThinkingEnables();
13382     first.maybeThinking = TRUE;
13383     StartClocks();
13384     firstMove = FALSE;
13385
13386     if (appData.autoFlipView && !flipView) {
13387       flipView = !flipView;
13388       DrawPosition(FALSE, NULL);
13389       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13390     }
13391
13392     if(bookHit) { // [HGM] book: simulate book reply
13393         static char bookMove[MSG_SIZ]; // a bit generous?
13394
13395         programStats.nodes = programStats.depth = programStats.time =
13396         programStats.score = programStats.got_only_move = 0;
13397         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13398
13399         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13400         strcat(bookMove, bookHit);
13401         HandleMachineMove(bookMove, &first);
13402     }
13403 }
13404
13405 void
13406 MachineBlackEvent ()
13407 {
13408   char buf[MSG_SIZ];
13409   char *bookHit = NULL;
13410
13411     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13412         return;
13413
13414
13415     if (gameMode == PlayFromGameFile ||
13416         gameMode == TwoMachinesPlay  ||
13417         gameMode == Training         ||
13418         gameMode == AnalyzeMode      ||
13419         gameMode == EndOfGame)
13420         EditGameEvent();
13421
13422     if (gameMode == EditPosition)
13423         EditPositionDone(TRUE);
13424
13425     if (WhiteOnMove(currentMove)) {
13426         DisplayError(_("It is not Black's turn"), 0);
13427         return;
13428     }
13429
13430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13431       ExitAnalyzeMode();
13432
13433     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13434         gameMode == AnalyzeFile)
13435         TruncateGame();
13436
13437     ResurrectChessProgram();    /* in case it isn't running */
13438     gameMode = MachinePlaysBlack;
13439     pausing = FALSE;
13440     ModeHighlight();
13441     SetGameInfo();
13442     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13443     DisplayTitle(buf);
13444     if (first.sendName) {
13445       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13446       SendToProgram(buf, &first);
13447     }
13448     if (first.sendTime) {
13449       if (first.useColors) {
13450         SendToProgram("white\n", &first); /*gnu kludge*/
13451       }
13452       SendTimeRemaining(&first, FALSE);
13453     }
13454     if (first.useColors) {
13455       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13456     }
13457     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13458     SetMachineThinkingEnables();
13459     first.maybeThinking = TRUE;
13460     StartClocks();
13461
13462     if (appData.autoFlipView && flipView) {
13463       flipView = !flipView;
13464       DrawPosition(FALSE, NULL);
13465       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13466     }
13467     if(bookHit) { // [HGM] book: simulate book reply
13468         static char bookMove[MSG_SIZ]; // a bit generous?
13469
13470         programStats.nodes = programStats.depth = programStats.time =
13471         programStats.score = programStats.got_only_move = 0;
13472         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13473
13474         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13475         strcat(bookMove, bookHit);
13476         HandleMachineMove(bookMove, &first);
13477     }
13478 }
13479
13480
13481 void
13482 DisplayTwoMachinesTitle ()
13483 {
13484     char buf[MSG_SIZ];
13485     if (appData.matchGames > 0) {
13486         if(appData.tourneyFile[0]) {
13487           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13488                    gameInfo.white, _("vs."), gameInfo.black,
13489                    nextGame+1, appData.matchGames+1,
13490                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13491         } else 
13492         if (first.twoMachinesColor[0] == 'w') {
13493           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13494                    gameInfo.white, _("vs."),  gameInfo.black,
13495                    first.matchWins, second.matchWins,
13496                    matchGame - 1 - (first.matchWins + second.matchWins));
13497         } else {
13498           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13499                    gameInfo.white, _("vs."), gameInfo.black,
13500                    second.matchWins, first.matchWins,
13501                    matchGame - 1 - (first.matchWins + second.matchWins));
13502         }
13503     } else {
13504       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13505     }
13506     DisplayTitle(buf);
13507 }
13508
13509 void
13510 SettingsMenuIfReady ()
13511 {
13512   if (second.lastPing != second.lastPong) {
13513     DisplayMessage("", _("Waiting for second chess program"));
13514     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13515     return;
13516   }
13517   ThawUI();
13518   DisplayMessage("", "");
13519   SettingsPopUp(&second);
13520 }
13521
13522 int
13523 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13524 {
13525     char buf[MSG_SIZ];
13526     if (cps->pr == NoProc) {
13527         StartChessProgram(cps);
13528         if (cps->protocolVersion == 1) {
13529           retry();
13530         } else {
13531           /* kludge: allow timeout for initial "feature" command */
13532           FreezeUI();
13533           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13534           DisplayMessage("", buf);
13535           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13536         }
13537         return 1;
13538     }
13539     return 0;
13540 }
13541
13542 void
13543 TwoMachinesEvent P((void))
13544 {
13545     int i;
13546     char buf[MSG_SIZ];
13547     ChessProgramState *onmove;
13548     char *bookHit = NULL;
13549     static int stalling = 0;
13550     TimeMark now;
13551     long wait;
13552
13553     if (appData.noChessProgram) return;
13554
13555     switch (gameMode) {
13556       case TwoMachinesPlay:
13557         return;
13558       case MachinePlaysWhite:
13559       case MachinePlaysBlack:
13560         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13561             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13562             return;
13563         }
13564         /* fall through */
13565       case BeginningOfGame:
13566       case PlayFromGameFile:
13567       case EndOfGame:
13568         EditGameEvent();
13569         if (gameMode != EditGame) return;
13570         break;
13571       case EditPosition:
13572         EditPositionDone(TRUE);
13573         break;
13574       case AnalyzeMode:
13575       case AnalyzeFile:
13576         ExitAnalyzeMode();
13577         break;
13578       case EditGame:
13579       default:
13580         break;
13581     }
13582
13583 //    forwardMostMove = currentMove;
13584     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13585
13586     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13587
13588     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13589     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13590       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13591       return;
13592     }
13593     if(!stalling) {
13594       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13595       SendToProgram("force\n", &second);
13596       stalling = 1;
13597       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13598       return;
13599     }
13600     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13601     if(appData.matchPause>10000 || appData.matchPause<10)
13602                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13603     wait = SubtractTimeMarks(&now, &pauseStart);
13604     if(wait < appData.matchPause) {
13605         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13606         return;
13607     }
13608     // we are now committed to starting the game
13609     stalling = 0;
13610     DisplayMessage("", "");
13611     if (startedFromSetupPosition) {
13612         SendBoard(&second, backwardMostMove);
13613     if (appData.debugMode) {
13614         fprintf(debugFP, "Two Machines\n");
13615     }
13616     }
13617     for (i = backwardMostMove; i < forwardMostMove; i++) {
13618         SendMoveToProgram(i, &second);
13619     }
13620
13621     gameMode = TwoMachinesPlay;
13622     pausing = FALSE;
13623     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13624     SetGameInfo();
13625     DisplayTwoMachinesTitle();
13626     firstMove = TRUE;
13627     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13628         onmove = &first;
13629     } else {
13630         onmove = &second;
13631     }
13632     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13633     SendToProgram(first.computerString, &first);
13634     if (first.sendName) {
13635       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13636       SendToProgram(buf, &first);
13637     }
13638     SendToProgram(second.computerString, &second);
13639     if (second.sendName) {
13640       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13641       SendToProgram(buf, &second);
13642     }
13643
13644     ResetClocks();
13645     if (!first.sendTime || !second.sendTime) {
13646         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13647         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13648     }
13649     if (onmove->sendTime) {
13650       if (onmove->useColors) {
13651         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13652       }
13653       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13654     }
13655     if (onmove->useColors) {
13656       SendToProgram(onmove->twoMachinesColor, onmove);
13657     }
13658     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13659 //    SendToProgram("go\n", onmove);
13660     onmove->maybeThinking = TRUE;
13661     SetMachineThinkingEnables();
13662
13663     StartClocks();
13664
13665     if(bookHit) { // [HGM] book: simulate book reply
13666         static char bookMove[MSG_SIZ]; // a bit generous?
13667
13668         programStats.nodes = programStats.depth = programStats.time =
13669         programStats.score = programStats.got_only_move = 0;
13670         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13671
13672         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13673         strcat(bookMove, bookHit);
13674         savedMessage = bookMove; // args for deferred call
13675         savedState = onmove;
13676         ScheduleDelayedEvent(DeferredBookMove, 1);
13677     }
13678 }
13679
13680 void
13681 TrainingEvent ()
13682 {
13683     if (gameMode == Training) {
13684       SetTrainingModeOff();
13685       gameMode = PlayFromGameFile;
13686       DisplayMessage("", _("Training mode off"));
13687     } else {
13688       gameMode = Training;
13689       animateTraining = appData.animate;
13690
13691       /* make sure we are not already at the end of the game */
13692       if (currentMove < forwardMostMove) {
13693         SetTrainingModeOn();
13694         DisplayMessage("", _("Training mode on"));
13695       } else {
13696         gameMode = PlayFromGameFile;
13697         DisplayError(_("Already at end of game"), 0);
13698       }
13699     }
13700     ModeHighlight();
13701 }
13702
13703 void
13704 IcsClientEvent ()
13705 {
13706     if (!appData.icsActive) return;
13707     switch (gameMode) {
13708       case IcsPlayingWhite:
13709       case IcsPlayingBlack:
13710       case IcsObserving:
13711       case IcsIdle:
13712       case BeginningOfGame:
13713       case IcsExamining:
13714         return;
13715
13716       case EditGame:
13717         break;
13718
13719       case EditPosition:
13720         EditPositionDone(TRUE);
13721         break;
13722
13723       case AnalyzeMode:
13724       case AnalyzeFile:
13725         ExitAnalyzeMode();
13726         break;
13727
13728       default:
13729         EditGameEvent();
13730         break;
13731     }
13732
13733     gameMode = IcsIdle;
13734     ModeHighlight();
13735     return;
13736 }
13737
13738 void
13739 EditGameEvent ()
13740 {
13741     int i;
13742
13743     switch (gameMode) {
13744       case Training:
13745         SetTrainingModeOff();
13746         break;
13747       case MachinePlaysWhite:
13748       case MachinePlaysBlack:
13749       case BeginningOfGame:
13750         SendToProgram("force\n", &first);
13751         SetUserThinkingEnables();
13752         break;
13753       case PlayFromGameFile:
13754         (void) StopLoadGameTimer();
13755         if (gameFileFP != NULL) {
13756             gameFileFP = NULL;
13757         }
13758         break;
13759       case EditPosition:
13760         EditPositionDone(TRUE);
13761         break;
13762       case AnalyzeMode:
13763       case AnalyzeFile:
13764         ExitAnalyzeMode();
13765         SendToProgram("force\n", &first);
13766         break;
13767       case TwoMachinesPlay:
13768         GameEnds(EndOfFile, NULL, GE_PLAYER);
13769         ResurrectChessProgram();
13770         SetUserThinkingEnables();
13771         break;
13772       case EndOfGame:
13773         ResurrectChessProgram();
13774         break;
13775       case IcsPlayingBlack:
13776       case IcsPlayingWhite:
13777         DisplayError(_("Warning: You are still playing a game"), 0);
13778         break;
13779       case IcsObserving:
13780         DisplayError(_("Warning: You are still observing a game"), 0);
13781         break;
13782       case IcsExamining:
13783         DisplayError(_("Warning: You are still examining a game"), 0);
13784         break;
13785       case IcsIdle:
13786         break;
13787       case EditGame:
13788       default:
13789         return;
13790     }
13791
13792     pausing = FALSE;
13793     StopClocks();
13794     first.offeredDraw = second.offeredDraw = 0;
13795
13796     if (gameMode == PlayFromGameFile) {
13797         whiteTimeRemaining = timeRemaining[0][currentMove];
13798         blackTimeRemaining = timeRemaining[1][currentMove];
13799         DisplayTitle("");
13800     }
13801
13802     if (gameMode == MachinePlaysWhite ||
13803         gameMode == MachinePlaysBlack ||
13804         gameMode == TwoMachinesPlay ||
13805         gameMode == EndOfGame) {
13806         i = forwardMostMove;
13807         while (i > currentMove) {
13808             SendToProgram("undo\n", &first);
13809             i--;
13810         }
13811         if(!adjustedClock) {
13812         whiteTimeRemaining = timeRemaining[0][currentMove];
13813         blackTimeRemaining = timeRemaining[1][currentMove];
13814         DisplayBothClocks();
13815         }
13816         if (whiteFlag || blackFlag) {
13817             whiteFlag = blackFlag = 0;
13818         }
13819         DisplayTitle("");
13820     }
13821
13822     gameMode = EditGame;
13823     ModeHighlight();
13824     SetGameInfo();
13825 }
13826
13827
13828 void
13829 EditPositionEvent ()
13830 {
13831     if (gameMode == EditPosition) {
13832         EditGameEvent();
13833         return;
13834     }
13835
13836     EditGameEvent();
13837     if (gameMode != EditGame) return;
13838
13839     gameMode = EditPosition;
13840     ModeHighlight();
13841     SetGameInfo();
13842     if (currentMove > 0)
13843       CopyBoard(boards[0], boards[currentMove]);
13844
13845     blackPlaysFirst = !WhiteOnMove(currentMove);
13846     ResetClocks();
13847     currentMove = forwardMostMove = backwardMostMove = 0;
13848     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13849     DisplayMove(-1);
13850     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13851 }
13852
13853 void
13854 ExitAnalyzeMode ()
13855 {
13856     /* [DM] icsEngineAnalyze - possible call from other functions */
13857     if (appData.icsEngineAnalyze) {
13858         appData.icsEngineAnalyze = FALSE;
13859
13860         DisplayMessage("",_("Close ICS engine analyze..."));
13861     }
13862     if (first.analysisSupport && first.analyzing) {
13863       SendToProgram("exit\n", &first);
13864       first.analyzing = FALSE;
13865     }
13866     thinkOutput[0] = NULLCHAR;
13867 }
13868
13869 void
13870 EditPositionDone (Boolean fakeRights)
13871 {
13872     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13873
13874     startedFromSetupPosition = TRUE;
13875     InitChessProgram(&first, FALSE);
13876     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13877       boards[0][EP_STATUS] = EP_NONE;
13878       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13879     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13880         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13881         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13882       } else boards[0][CASTLING][2] = NoRights;
13883     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13884         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13885         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13886       } else boards[0][CASTLING][5] = NoRights;
13887     }
13888     SendToProgram("force\n", &first);
13889     if (blackPlaysFirst) {
13890         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13891         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13892         currentMove = forwardMostMove = backwardMostMove = 1;
13893         CopyBoard(boards[1], boards[0]);
13894     } else {
13895         currentMove = forwardMostMove = backwardMostMove = 0;
13896     }
13897     SendBoard(&first, forwardMostMove);
13898     if (appData.debugMode) {
13899         fprintf(debugFP, "EditPosDone\n");
13900     }
13901     DisplayTitle("");
13902     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13903     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13904     gameMode = EditGame;
13905     ModeHighlight();
13906     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13907     ClearHighlights(); /* [AS] */
13908 }
13909
13910 /* Pause for `ms' milliseconds */
13911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13912 void
13913 TimeDelay (long ms)
13914 {
13915     TimeMark m1, m2;
13916
13917     GetTimeMark(&m1);
13918     do {
13919         GetTimeMark(&m2);
13920     } while (SubtractTimeMarks(&m2, &m1) < ms);
13921 }
13922
13923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13924 void
13925 SendMultiLineToICS (char *buf)
13926 {
13927     char temp[MSG_SIZ+1], *p;
13928     int len;
13929
13930     len = strlen(buf);
13931     if (len > MSG_SIZ)
13932       len = MSG_SIZ;
13933
13934     strncpy(temp, buf, len);
13935     temp[len] = 0;
13936
13937     p = temp;
13938     while (*p) {
13939         if (*p == '\n' || *p == '\r')
13940           *p = ' ';
13941         ++p;
13942     }
13943
13944     strcat(temp, "\n");
13945     SendToICS(temp);
13946     SendToPlayer(temp, strlen(temp));
13947 }
13948
13949 void
13950 SetWhiteToPlayEvent ()
13951 {
13952     if (gameMode == EditPosition) {
13953         blackPlaysFirst = FALSE;
13954         DisplayBothClocks();    /* works because currentMove is 0 */
13955     } else if (gameMode == IcsExamining) {
13956         SendToICS(ics_prefix);
13957         SendToICS("tomove white\n");
13958     }
13959 }
13960
13961 void
13962 SetBlackToPlayEvent ()
13963 {
13964     if (gameMode == EditPosition) {
13965         blackPlaysFirst = TRUE;
13966         currentMove = 1;        /* kludge */
13967         DisplayBothClocks();
13968         currentMove = 0;
13969     } else if (gameMode == IcsExamining) {
13970         SendToICS(ics_prefix);
13971         SendToICS("tomove black\n");
13972     }
13973 }
13974
13975 void
13976 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13977 {
13978     char buf[MSG_SIZ];
13979     ChessSquare piece = boards[0][y][x];
13980
13981     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13982
13983     switch (selection) {
13984       case ClearBoard:
13985         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13986             SendToICS(ics_prefix);
13987             SendToICS("bsetup clear\n");
13988         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13989             SendToICS(ics_prefix);
13990             SendToICS("clearboard\n");
13991         } else {
13992             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13993                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13994                 for (y = 0; y < BOARD_HEIGHT; y++) {
13995                     if (gameMode == IcsExamining) {
13996                         if (boards[currentMove][y][x] != EmptySquare) {
13997                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13998                                     AAA + x, ONE + y);
13999                             SendToICS(buf);
14000                         }
14001                     } else {
14002                         boards[0][y][x] = p;
14003                     }
14004                 }
14005             }
14006         }
14007         if (gameMode == EditPosition) {
14008             DrawPosition(FALSE, boards[0]);
14009         }
14010         break;
14011
14012       case WhitePlay:
14013         SetWhiteToPlayEvent();
14014         break;
14015
14016       case BlackPlay:
14017         SetBlackToPlayEvent();
14018         break;
14019
14020       case EmptySquare:
14021         if (gameMode == IcsExamining) {
14022             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14023             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14024             SendToICS(buf);
14025         } else {
14026             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14027                 if(x == BOARD_LEFT-2) {
14028                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14029                     boards[0][y][1] = 0;
14030                 } else
14031                 if(x == BOARD_RGHT+1) {
14032                     if(y >= gameInfo.holdingsSize) break;
14033                     boards[0][y][BOARD_WIDTH-2] = 0;
14034                 } else break;
14035             }
14036             boards[0][y][x] = EmptySquare;
14037             DrawPosition(FALSE, boards[0]);
14038         }
14039         break;
14040
14041       case PromotePiece:
14042         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14043            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14044             selection = (ChessSquare) (PROMOTED piece);
14045         } else if(piece == EmptySquare) selection = WhiteSilver;
14046         else selection = (ChessSquare)((int)piece - 1);
14047         goto defaultlabel;
14048
14049       case DemotePiece:
14050         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14051            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14052             selection = (ChessSquare) (DEMOTED piece);
14053         } else if(piece == EmptySquare) selection = BlackSilver;
14054         else selection = (ChessSquare)((int)piece + 1);
14055         goto defaultlabel;
14056
14057       case WhiteQueen:
14058       case BlackQueen:
14059         if(gameInfo.variant == VariantShatranj ||
14060            gameInfo.variant == VariantXiangqi  ||
14061            gameInfo.variant == VariantCourier  ||
14062            gameInfo.variant == VariantMakruk     )
14063             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14064         goto defaultlabel;
14065
14066       case WhiteKing:
14067       case BlackKing:
14068         if(gameInfo.variant == VariantXiangqi)
14069             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14070         if(gameInfo.variant == VariantKnightmate)
14071             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14072       default:
14073         defaultlabel:
14074         if (gameMode == IcsExamining) {
14075             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14076             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14077                      PieceToChar(selection), AAA + x, ONE + y);
14078             SendToICS(buf);
14079         } else {
14080             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14081                 int n;
14082                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14083                     n = PieceToNumber(selection - BlackPawn);
14084                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14085                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14086                     boards[0][BOARD_HEIGHT-1-n][1]++;
14087                 } else
14088                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14089                     n = PieceToNumber(selection);
14090                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14091                     boards[0][n][BOARD_WIDTH-1] = selection;
14092                     boards[0][n][BOARD_WIDTH-2]++;
14093                 }
14094             } else
14095             boards[0][y][x] = selection;
14096             DrawPosition(TRUE, boards[0]);
14097             ClearHighlights();
14098             fromX = fromY = -1;
14099         }
14100         break;
14101     }
14102 }
14103
14104
14105 void
14106 DropMenuEvent (ChessSquare selection, int x, int y)
14107 {
14108     ChessMove moveType;
14109
14110     switch (gameMode) {
14111       case IcsPlayingWhite:
14112       case MachinePlaysBlack:
14113         if (!WhiteOnMove(currentMove)) {
14114             DisplayMoveError(_("It is Black's turn"));
14115             return;
14116         }
14117         moveType = WhiteDrop;
14118         break;
14119       case IcsPlayingBlack:
14120       case MachinePlaysWhite:
14121         if (WhiteOnMove(currentMove)) {
14122             DisplayMoveError(_("It is White's turn"));
14123             return;
14124         }
14125         moveType = BlackDrop;
14126         break;
14127       case EditGame:
14128         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14129         break;
14130       default:
14131         return;
14132     }
14133
14134     if (moveType == BlackDrop && selection < BlackPawn) {
14135       selection = (ChessSquare) ((int) selection
14136                                  + (int) BlackPawn - (int) WhitePawn);
14137     }
14138     if (boards[currentMove][y][x] != EmptySquare) {
14139         DisplayMoveError(_("That square is occupied"));
14140         return;
14141     }
14142
14143     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14144 }
14145
14146 void
14147 AcceptEvent ()
14148 {
14149     /* Accept a pending offer of any kind from opponent */
14150
14151     if (appData.icsActive) {
14152         SendToICS(ics_prefix);
14153         SendToICS("accept\n");
14154     } else if (cmailMsgLoaded) {
14155         if (currentMove == cmailOldMove &&
14156             commentList[cmailOldMove] != NULL &&
14157             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14158                    "Black offers a draw" : "White offers a draw")) {
14159             TruncateGame();
14160             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14161             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14162         } else {
14163             DisplayError(_("There is no pending offer on this move"), 0);
14164             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14165         }
14166     } else {
14167         /* Not used for offers from chess program */
14168     }
14169 }
14170
14171 void
14172 DeclineEvent ()
14173 {
14174     /* Decline a pending offer of any kind from opponent */
14175
14176     if (appData.icsActive) {
14177         SendToICS(ics_prefix);
14178         SendToICS("decline\n");
14179     } else if (cmailMsgLoaded) {
14180         if (currentMove == cmailOldMove &&
14181             commentList[cmailOldMove] != NULL &&
14182             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14183                    "Black offers a draw" : "White offers a draw")) {
14184 #ifdef NOTDEF
14185             AppendComment(cmailOldMove, "Draw declined", TRUE);
14186             DisplayComment(cmailOldMove - 1, "Draw declined");
14187 #endif /*NOTDEF*/
14188         } else {
14189             DisplayError(_("There is no pending offer on this move"), 0);
14190         }
14191     } else {
14192         /* Not used for offers from chess program */
14193     }
14194 }
14195
14196 void
14197 RematchEvent ()
14198 {
14199     /* Issue ICS rematch command */
14200     if (appData.icsActive) {
14201         SendToICS(ics_prefix);
14202         SendToICS("rematch\n");
14203     }
14204 }
14205
14206 void
14207 CallFlagEvent ()
14208 {
14209     /* Call your opponent's flag (claim a win on time) */
14210     if (appData.icsActive) {
14211         SendToICS(ics_prefix);
14212         SendToICS("flag\n");
14213     } else {
14214         switch (gameMode) {
14215           default:
14216             return;
14217           case MachinePlaysWhite:
14218             if (whiteFlag) {
14219                 if (blackFlag)
14220                   GameEnds(GameIsDrawn, "Both players ran out of time",
14221                            GE_PLAYER);
14222                 else
14223                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14224             } else {
14225                 DisplayError(_("Your opponent is not out of time"), 0);
14226             }
14227             break;
14228           case MachinePlaysBlack:
14229             if (blackFlag) {
14230                 if (whiteFlag)
14231                   GameEnds(GameIsDrawn, "Both players ran out of time",
14232                            GE_PLAYER);
14233                 else
14234                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14235             } else {
14236                 DisplayError(_("Your opponent is not out of time"), 0);
14237             }
14238             break;
14239         }
14240     }
14241 }
14242
14243 void
14244 ClockClick (int which)
14245 {       // [HGM] code moved to back-end from winboard.c
14246         if(which) { // black clock
14247           if (gameMode == EditPosition || gameMode == IcsExamining) {
14248             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14249             SetBlackToPlayEvent();
14250           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14251           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14252           } else if (shiftKey) {
14253             AdjustClock(which, -1);
14254           } else if (gameMode == IcsPlayingWhite ||
14255                      gameMode == MachinePlaysBlack) {
14256             CallFlagEvent();
14257           }
14258         } else { // white clock
14259           if (gameMode == EditPosition || gameMode == IcsExamining) {
14260             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14261             SetWhiteToPlayEvent();
14262           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14263           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14264           } else if (shiftKey) {
14265             AdjustClock(which, -1);
14266           } else if (gameMode == IcsPlayingBlack ||
14267                    gameMode == MachinePlaysWhite) {
14268             CallFlagEvent();
14269           }
14270         }
14271 }
14272
14273 void
14274 DrawEvent ()
14275 {
14276     /* Offer draw or accept pending draw offer from opponent */
14277
14278     if (appData.icsActive) {
14279         /* Note: tournament rules require draw offers to be
14280            made after you make your move but before you punch
14281            your clock.  Currently ICS doesn't let you do that;
14282            instead, you immediately punch your clock after making
14283            a move, but you can offer a draw at any time. */
14284
14285         SendToICS(ics_prefix);
14286         SendToICS("draw\n");
14287         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14288     } else if (cmailMsgLoaded) {
14289         if (currentMove == cmailOldMove &&
14290             commentList[cmailOldMove] != NULL &&
14291             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14292                    "Black offers a draw" : "White offers a draw")) {
14293             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14295         } else if (currentMove == cmailOldMove + 1) {
14296             char *offer = WhiteOnMove(cmailOldMove) ?
14297               "White offers a draw" : "Black offers a draw";
14298             AppendComment(currentMove, offer, TRUE);
14299             DisplayComment(currentMove - 1, offer);
14300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14301         } else {
14302             DisplayError(_("You must make your move before offering a draw"), 0);
14303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14304         }
14305     } else if (first.offeredDraw) {
14306         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14307     } else {
14308         if (first.sendDrawOffers) {
14309             SendToProgram("draw\n", &first);
14310             userOfferedDraw = TRUE;
14311         }
14312     }
14313 }
14314
14315 void
14316 AdjournEvent ()
14317 {
14318     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14319
14320     if (appData.icsActive) {
14321         SendToICS(ics_prefix);
14322         SendToICS("adjourn\n");
14323     } else {
14324         /* Currently GNU Chess doesn't offer or accept Adjourns */
14325     }
14326 }
14327
14328
14329 void
14330 AbortEvent ()
14331 {
14332     /* Offer Abort or accept pending Abort offer from opponent */
14333
14334     if (appData.icsActive) {
14335         SendToICS(ics_prefix);
14336         SendToICS("abort\n");
14337     } else {
14338         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14339     }
14340 }
14341
14342 void
14343 ResignEvent ()
14344 {
14345     /* Resign.  You can do this even if it's not your turn. */
14346
14347     if (appData.icsActive) {
14348         SendToICS(ics_prefix);
14349         SendToICS("resign\n");
14350     } else {
14351         switch (gameMode) {
14352           case MachinePlaysWhite:
14353             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14354             break;
14355           case MachinePlaysBlack:
14356             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14357             break;
14358           case EditGame:
14359             if (cmailMsgLoaded) {
14360                 TruncateGame();
14361                 if (WhiteOnMove(cmailOldMove)) {
14362                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14363                 } else {
14364                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14365                 }
14366                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14367             }
14368             break;
14369           default:
14370             break;
14371         }
14372     }
14373 }
14374
14375
14376 void
14377 StopObservingEvent ()
14378 {
14379     /* Stop observing current games */
14380     SendToICS(ics_prefix);
14381     SendToICS("unobserve\n");
14382 }
14383
14384 void
14385 StopExaminingEvent ()
14386 {
14387     /* Stop observing current game */
14388     SendToICS(ics_prefix);
14389     SendToICS("unexamine\n");
14390 }
14391
14392 void
14393 ForwardInner (int target)
14394 {
14395     int limit; int oldSeekGraphUp = seekGraphUp;
14396
14397     if (appData.debugMode)
14398         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14399                 target, currentMove, forwardMostMove);
14400
14401     if (gameMode == EditPosition)
14402       return;
14403
14404     seekGraphUp = FALSE;
14405     MarkTargetSquares(1);
14406
14407     if (gameMode == PlayFromGameFile && !pausing)
14408       PauseEvent();
14409
14410     if (gameMode == IcsExamining && pausing)
14411       limit = pauseExamForwardMostMove;
14412     else
14413       limit = forwardMostMove;
14414
14415     if (target > limit) target = limit;
14416
14417     if (target > 0 && moveList[target - 1][0]) {
14418         int fromX, fromY, toX, toY;
14419         toX = moveList[target - 1][2] - AAA;
14420         toY = moveList[target - 1][3] - ONE;
14421         if (moveList[target - 1][1] == '@') {
14422             if (appData.highlightLastMove) {
14423                 SetHighlights(-1, -1, toX, toY);
14424             }
14425         } else {
14426             fromX = moveList[target - 1][0] - AAA;
14427             fromY = moveList[target - 1][1] - ONE;
14428             if (target == currentMove + 1) {
14429                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14430             }
14431             if (appData.highlightLastMove) {
14432                 SetHighlights(fromX, fromY, toX, toY);
14433             }
14434         }
14435     }
14436     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14437         gameMode == Training || gameMode == PlayFromGameFile ||
14438         gameMode == AnalyzeFile) {
14439         while (currentMove < target) {
14440             SendMoveToProgram(currentMove++, &first);
14441         }
14442     } else {
14443         currentMove = target;
14444     }
14445
14446     if (gameMode == EditGame || gameMode == EndOfGame) {
14447         whiteTimeRemaining = timeRemaining[0][currentMove];
14448         blackTimeRemaining = timeRemaining[1][currentMove];
14449     }
14450     DisplayBothClocks();
14451     DisplayMove(currentMove - 1);
14452     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14453     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14454     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14455         DisplayComment(currentMove - 1, commentList[currentMove]);
14456     }
14457     ClearMap(); // [HGM] exclude: invalidate map
14458 }
14459
14460
14461 void
14462 ForwardEvent ()
14463 {
14464     if (gameMode == IcsExamining && !pausing) {
14465         SendToICS(ics_prefix);
14466         SendToICS("forward\n");
14467     } else {
14468         ForwardInner(currentMove + 1);
14469     }
14470 }
14471
14472 void
14473 ToEndEvent ()
14474 {
14475     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14476         /* to optimze, we temporarily turn off analysis mode while we feed
14477          * the remaining moves to the engine. Otherwise we get analysis output
14478          * after each move.
14479          */
14480         if (first.analysisSupport) {
14481           SendToProgram("exit\nforce\n", &first);
14482           first.analyzing = FALSE;
14483         }
14484     }
14485
14486     if (gameMode == IcsExamining && !pausing) {
14487         SendToICS(ics_prefix);
14488         SendToICS("forward 999999\n");
14489     } else {
14490         ForwardInner(forwardMostMove);
14491     }
14492
14493     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14494         /* we have fed all the moves, so reactivate analysis mode */
14495         SendToProgram("analyze\n", &first);
14496         first.analyzing = TRUE;
14497         /*first.maybeThinking = TRUE;*/
14498         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14499     }
14500 }
14501
14502 void
14503 BackwardInner (int target)
14504 {
14505     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14506
14507     if (appData.debugMode)
14508         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14509                 target, currentMove, forwardMostMove);
14510
14511     if (gameMode == EditPosition) return;
14512     seekGraphUp = FALSE;
14513     MarkTargetSquares(1);
14514     if (currentMove <= backwardMostMove) {
14515         ClearHighlights();
14516         DrawPosition(full_redraw, boards[currentMove]);
14517         return;
14518     }
14519     if (gameMode == PlayFromGameFile && !pausing)
14520       PauseEvent();
14521
14522     if (moveList[target][0]) {
14523         int fromX, fromY, toX, toY;
14524         toX = moveList[target][2] - AAA;
14525         toY = moveList[target][3] - ONE;
14526         if (moveList[target][1] == '@') {
14527             if (appData.highlightLastMove) {
14528                 SetHighlights(-1, -1, toX, toY);
14529             }
14530         } else {
14531             fromX = moveList[target][0] - AAA;
14532             fromY = moveList[target][1] - ONE;
14533             if (target == currentMove - 1) {
14534                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14535             }
14536             if (appData.highlightLastMove) {
14537                 SetHighlights(fromX, fromY, toX, toY);
14538             }
14539         }
14540     }
14541     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14542         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14543         while (currentMove > target) {
14544             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14545                 // null move cannot be undone. Reload program with move history before it.
14546                 int i;
14547                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14548                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14549                 }
14550                 SendBoard(&first, i); 
14551                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14552                 break;
14553             }
14554             SendToProgram("undo\n", &first);
14555             currentMove--;
14556         }
14557     } else {
14558         currentMove = target;
14559     }
14560
14561     if (gameMode == EditGame || gameMode == EndOfGame) {
14562         whiteTimeRemaining = timeRemaining[0][currentMove];
14563         blackTimeRemaining = timeRemaining[1][currentMove];
14564     }
14565     DisplayBothClocks();
14566     DisplayMove(currentMove - 1);
14567     DrawPosition(full_redraw, boards[currentMove]);
14568     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14569     // [HGM] PV info: routine tests if comment empty
14570     DisplayComment(currentMove - 1, commentList[currentMove]);
14571     ClearMap(); // [HGM] exclude: invalidate map
14572 }
14573
14574 void
14575 BackwardEvent ()
14576 {
14577     if (gameMode == IcsExamining && !pausing) {
14578         SendToICS(ics_prefix);
14579         SendToICS("backward\n");
14580     } else {
14581         BackwardInner(currentMove - 1);
14582     }
14583 }
14584
14585 void
14586 ToStartEvent ()
14587 {
14588     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14589         /* to optimize, we temporarily turn off analysis mode while we undo
14590          * all the moves. Otherwise we get analysis output after each undo.
14591          */
14592         if (first.analysisSupport) {
14593           SendToProgram("exit\nforce\n", &first);
14594           first.analyzing = FALSE;
14595         }
14596     }
14597
14598     if (gameMode == IcsExamining && !pausing) {
14599         SendToICS(ics_prefix);
14600         SendToICS("backward 999999\n");
14601     } else {
14602         BackwardInner(backwardMostMove);
14603     }
14604
14605     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14606         /* we have fed all the moves, so reactivate analysis mode */
14607         SendToProgram("analyze\n", &first);
14608         first.analyzing = TRUE;
14609         /*first.maybeThinking = TRUE;*/
14610         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14611     }
14612 }
14613
14614 void
14615 ToNrEvent (int to)
14616 {
14617   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14618   if (to >= forwardMostMove) to = forwardMostMove;
14619   if (to <= backwardMostMove) to = backwardMostMove;
14620   if (to < currentMove) {
14621     BackwardInner(to);
14622   } else {
14623     ForwardInner(to);
14624   }
14625 }
14626
14627 void
14628 RevertEvent (Boolean annotate)
14629 {
14630     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14631         return;
14632     }
14633     if (gameMode != IcsExamining) {
14634         DisplayError(_("You are not examining a game"), 0);
14635         return;
14636     }
14637     if (pausing) {
14638         DisplayError(_("You can't revert while pausing"), 0);
14639         return;
14640     }
14641     SendToICS(ics_prefix);
14642     SendToICS("revert\n");
14643 }
14644
14645 void
14646 RetractMoveEvent ()
14647 {
14648     switch (gameMode) {
14649       case MachinePlaysWhite:
14650       case MachinePlaysBlack:
14651         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14652             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14653             return;
14654         }
14655         if (forwardMostMove < 2) return;
14656         currentMove = forwardMostMove = forwardMostMove - 2;
14657         whiteTimeRemaining = timeRemaining[0][currentMove];
14658         blackTimeRemaining = timeRemaining[1][currentMove];
14659         DisplayBothClocks();
14660         DisplayMove(currentMove - 1);
14661         ClearHighlights();/*!! could figure this out*/
14662         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14663         SendToProgram("remove\n", &first);
14664         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14665         break;
14666
14667       case BeginningOfGame:
14668       default:
14669         break;
14670
14671       case IcsPlayingWhite:
14672       case IcsPlayingBlack:
14673         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14674             SendToICS(ics_prefix);
14675             SendToICS("takeback 2\n");
14676         } else {
14677             SendToICS(ics_prefix);
14678             SendToICS("takeback 1\n");
14679         }
14680         break;
14681     }
14682 }
14683
14684 void
14685 MoveNowEvent ()
14686 {
14687     ChessProgramState *cps;
14688
14689     switch (gameMode) {
14690       case MachinePlaysWhite:
14691         if (!WhiteOnMove(forwardMostMove)) {
14692             DisplayError(_("It is your turn"), 0);
14693             return;
14694         }
14695         cps = &first;
14696         break;
14697       case MachinePlaysBlack:
14698         if (WhiteOnMove(forwardMostMove)) {
14699             DisplayError(_("It is your turn"), 0);
14700             return;
14701         }
14702         cps = &first;
14703         break;
14704       case TwoMachinesPlay:
14705         if (WhiteOnMove(forwardMostMove) ==
14706             (first.twoMachinesColor[0] == 'w')) {
14707             cps = &first;
14708         } else {
14709             cps = &second;
14710         }
14711         break;
14712       case BeginningOfGame:
14713       default:
14714         return;
14715     }
14716     SendToProgram("?\n", cps);
14717 }
14718
14719 void
14720 TruncateGameEvent ()
14721 {
14722     EditGameEvent();
14723     if (gameMode != EditGame) return;
14724     TruncateGame();
14725 }
14726
14727 void
14728 TruncateGame ()
14729 {
14730     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14731     if (forwardMostMove > currentMove) {
14732         if (gameInfo.resultDetails != NULL) {
14733             free(gameInfo.resultDetails);
14734             gameInfo.resultDetails = NULL;
14735             gameInfo.result = GameUnfinished;
14736         }
14737         forwardMostMove = currentMove;
14738         HistorySet(parseList, backwardMostMove, forwardMostMove,
14739                    currentMove-1);
14740     }
14741 }
14742
14743 void
14744 HintEvent ()
14745 {
14746     if (appData.noChessProgram) return;
14747     switch (gameMode) {
14748       case MachinePlaysWhite:
14749         if (WhiteOnMove(forwardMostMove)) {
14750             DisplayError(_("Wait until your turn"), 0);
14751             return;
14752         }
14753         break;
14754       case BeginningOfGame:
14755       case MachinePlaysBlack:
14756         if (!WhiteOnMove(forwardMostMove)) {
14757             DisplayError(_("Wait until your turn"), 0);
14758             return;
14759         }
14760         break;
14761       default:
14762         DisplayError(_("No hint available"), 0);
14763         return;
14764     }
14765     SendToProgram("hint\n", &first);
14766     hintRequested = TRUE;
14767 }
14768
14769 void
14770 BookEvent ()
14771 {
14772     if (appData.noChessProgram) return;
14773     switch (gameMode) {
14774       case MachinePlaysWhite:
14775         if (WhiteOnMove(forwardMostMove)) {
14776             DisplayError(_("Wait until your turn"), 0);
14777             return;
14778         }
14779         break;
14780       case BeginningOfGame:
14781       case MachinePlaysBlack:
14782         if (!WhiteOnMove(forwardMostMove)) {
14783             DisplayError(_("Wait until your turn"), 0);
14784             return;
14785         }
14786         break;
14787       case EditPosition:
14788         EditPositionDone(TRUE);
14789         break;
14790       case TwoMachinesPlay:
14791         return;
14792       default:
14793         break;
14794     }
14795     SendToProgram("bk\n", &first);
14796     bookOutput[0] = NULLCHAR;
14797     bookRequested = TRUE;
14798 }
14799
14800 void
14801 AboutGameEvent ()
14802 {
14803     char *tags = PGNTags(&gameInfo);
14804     TagsPopUp(tags, CmailMsg());
14805     free(tags);
14806 }
14807
14808 /* end button procedures */
14809
14810 void
14811 PrintPosition (FILE *fp, int move)
14812 {
14813     int i, j;
14814
14815     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14816         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14817             char c = PieceToChar(boards[move][i][j]);
14818             fputc(c == 'x' ? '.' : c, fp);
14819             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14820         }
14821     }
14822     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14823       fprintf(fp, "white to play\n");
14824     else
14825       fprintf(fp, "black to play\n");
14826 }
14827
14828 void
14829 PrintOpponents (FILE *fp)
14830 {
14831     if (gameInfo.white != NULL) {
14832         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14833     } else {
14834         fprintf(fp, "\n");
14835     }
14836 }
14837
14838 /* Find last component of program's own name, using some heuristics */
14839 void
14840 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14841 {
14842     char *p, *q, c;
14843     int local = (strcmp(host, "localhost") == 0);
14844     while (!local && (p = strchr(prog, ';')) != NULL) {
14845         p++;
14846         while (*p == ' ') p++;
14847         prog = p;
14848     }
14849     if (*prog == '"' || *prog == '\'') {
14850         q = strchr(prog + 1, *prog);
14851     } else {
14852         q = strchr(prog, ' ');
14853     }
14854     if (q == NULL) q = prog + strlen(prog);
14855     p = q;
14856     while (p >= prog && *p != '/' && *p != '\\') p--;
14857     p++;
14858     if(p == prog && *p == '"') p++;
14859     c = *q; *q = 0;
14860     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14861     memcpy(buf, p, q - p);
14862     buf[q - p] = NULLCHAR;
14863     if (!local) {
14864         strcat(buf, "@");
14865         strcat(buf, host);
14866     }
14867 }
14868
14869 char *
14870 TimeControlTagValue ()
14871 {
14872     char buf[MSG_SIZ];
14873     if (!appData.clockMode) {
14874       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14875     } else if (movesPerSession > 0) {
14876       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14877     } else if (timeIncrement == 0) {
14878       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14879     } else {
14880       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14881     }
14882     return StrSave(buf);
14883 }
14884
14885 void
14886 SetGameInfo ()
14887 {
14888     /* This routine is used only for certain modes */
14889     VariantClass v = gameInfo.variant;
14890     ChessMove r = GameUnfinished;
14891     char *p = NULL;
14892
14893     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14894         r = gameInfo.result;
14895         p = gameInfo.resultDetails;
14896         gameInfo.resultDetails = NULL;
14897     }
14898     ClearGameInfo(&gameInfo);
14899     gameInfo.variant = v;
14900
14901     switch (gameMode) {
14902       case MachinePlaysWhite:
14903         gameInfo.event = StrSave( appData.pgnEventHeader );
14904         gameInfo.site = StrSave(HostName());
14905         gameInfo.date = PGNDate();
14906         gameInfo.round = StrSave("-");
14907         gameInfo.white = StrSave(first.tidy);
14908         gameInfo.black = StrSave(UserName());
14909         gameInfo.timeControl = TimeControlTagValue();
14910         break;
14911
14912       case MachinePlaysBlack:
14913         gameInfo.event = StrSave( appData.pgnEventHeader );
14914         gameInfo.site = StrSave(HostName());
14915         gameInfo.date = PGNDate();
14916         gameInfo.round = StrSave("-");
14917         gameInfo.white = StrSave(UserName());
14918         gameInfo.black = StrSave(first.tidy);
14919         gameInfo.timeControl = TimeControlTagValue();
14920         break;
14921
14922       case TwoMachinesPlay:
14923         gameInfo.event = StrSave( appData.pgnEventHeader );
14924         gameInfo.site = StrSave(HostName());
14925         gameInfo.date = PGNDate();
14926         if (roundNr > 0) {
14927             char buf[MSG_SIZ];
14928             snprintf(buf, MSG_SIZ, "%d", roundNr);
14929             gameInfo.round = StrSave(buf);
14930         } else {
14931             gameInfo.round = StrSave("-");
14932         }
14933         if (first.twoMachinesColor[0] == 'w') {
14934             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14935             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14936         } else {
14937             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14938             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14939         }
14940         gameInfo.timeControl = TimeControlTagValue();
14941         break;
14942
14943       case EditGame:
14944         gameInfo.event = StrSave("Edited game");
14945         gameInfo.site = StrSave(HostName());
14946         gameInfo.date = PGNDate();
14947         gameInfo.round = StrSave("-");
14948         gameInfo.white = StrSave("-");
14949         gameInfo.black = StrSave("-");
14950         gameInfo.result = r;
14951         gameInfo.resultDetails = p;
14952         break;
14953
14954       case EditPosition:
14955         gameInfo.event = StrSave("Edited position");
14956         gameInfo.site = StrSave(HostName());
14957         gameInfo.date = PGNDate();
14958         gameInfo.round = StrSave("-");
14959         gameInfo.white = StrSave("-");
14960         gameInfo.black = StrSave("-");
14961         break;
14962
14963       case IcsPlayingWhite:
14964       case IcsPlayingBlack:
14965       case IcsObserving:
14966       case IcsExamining:
14967         break;
14968
14969       case PlayFromGameFile:
14970         gameInfo.event = StrSave("Game from non-PGN file");
14971         gameInfo.site = StrSave(HostName());
14972         gameInfo.date = PGNDate();
14973         gameInfo.round = StrSave("-");
14974         gameInfo.white = StrSave("?");
14975         gameInfo.black = StrSave("?");
14976         break;
14977
14978       default:
14979         break;
14980     }
14981 }
14982
14983 void
14984 ReplaceComment (int index, char *text)
14985 {
14986     int len;
14987     char *p;
14988     float score;
14989
14990     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14991        pvInfoList[index-1].depth == len &&
14992        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14993        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14994     while (*text == '\n') text++;
14995     len = strlen(text);
14996     while (len > 0 && text[len - 1] == '\n') len--;
14997
14998     if (commentList[index] != NULL)
14999       free(commentList[index]);
15000
15001     if (len == 0) {
15002         commentList[index] = NULL;
15003         return;
15004     }
15005   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15006       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15007       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15008     commentList[index] = (char *) malloc(len + 2);
15009     strncpy(commentList[index], text, len);
15010     commentList[index][len] = '\n';
15011     commentList[index][len + 1] = NULLCHAR;
15012   } else {
15013     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15014     char *p;
15015     commentList[index] = (char *) malloc(len + 7);
15016     safeStrCpy(commentList[index], "{\n", 3);
15017     safeStrCpy(commentList[index]+2, text, len+1);
15018     commentList[index][len+2] = NULLCHAR;
15019     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15020     strcat(commentList[index], "\n}\n");
15021   }
15022 }
15023
15024 void
15025 CrushCRs (char *text)
15026 {
15027   char *p = text;
15028   char *q = text;
15029   char ch;
15030
15031   do {
15032     ch = *p++;
15033     if (ch == '\r') continue;
15034     *q++ = ch;
15035   } while (ch != '\0');
15036 }
15037
15038 void
15039 AppendComment (int index, char *text, Boolean addBraces)
15040 /* addBraces  tells if we should add {} */
15041 {
15042     int oldlen, len;
15043     char *old;
15044
15045 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15046     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15047
15048     CrushCRs(text);
15049     while (*text == '\n') text++;
15050     len = strlen(text);
15051     while (len > 0 && text[len - 1] == '\n') len--;
15052     text[len] = NULLCHAR;
15053
15054     if (len == 0) return;
15055
15056     if (commentList[index] != NULL) {
15057       Boolean addClosingBrace = addBraces;
15058         old = commentList[index];
15059         oldlen = strlen(old);
15060         while(commentList[index][oldlen-1] ==  '\n')
15061           commentList[index][--oldlen] = NULLCHAR;
15062         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15063         safeStrCpy(commentList[index], old, oldlen + len + 6);
15064         free(old);
15065         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15066         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15067           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15068           while (*text == '\n') { text++; len--; }
15069           commentList[index][--oldlen] = NULLCHAR;
15070       }
15071         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15072         else          strcat(commentList[index], "\n");
15073         strcat(commentList[index], text);
15074         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15075         else          strcat(commentList[index], "\n");
15076     } else {
15077         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15078         if(addBraces)
15079           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15080         else commentList[index][0] = NULLCHAR;
15081         strcat(commentList[index], text);
15082         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15083         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15084     }
15085 }
15086
15087 static char *
15088 FindStr (char * text, char * sub_text)
15089 {
15090     char * result = strstr( text, sub_text );
15091
15092     if( result != NULL ) {
15093         result += strlen( sub_text );
15094     }
15095
15096     return result;
15097 }
15098
15099 /* [AS] Try to extract PV info from PGN comment */
15100 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15101 char *
15102 GetInfoFromComment (int index, char * text)
15103 {
15104     char * sep = text, *p;
15105
15106     if( text != NULL && index > 0 ) {
15107         int score = 0;
15108         int depth = 0;
15109         int time = -1, sec = 0, deci;
15110         char * s_eval = FindStr( text, "[%eval " );
15111         char * s_emt = FindStr( text, "[%emt " );
15112
15113         if( s_eval != NULL || s_emt != NULL ) {
15114             /* New style */
15115             char delim;
15116
15117             if( s_eval != NULL ) {
15118                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15119                     return text;
15120                 }
15121
15122                 if( delim != ']' ) {
15123                     return text;
15124                 }
15125             }
15126
15127             if( s_emt != NULL ) {
15128             }
15129                 return text;
15130         }
15131         else {
15132             /* We expect something like: [+|-]nnn.nn/dd */
15133             int score_lo = 0;
15134
15135             if(*text != '{') return text; // [HGM] braces: must be normal comment
15136
15137             sep = strchr( text, '/' );
15138             if( sep == NULL || sep < (text+4) ) {
15139                 return text;
15140             }
15141
15142             p = text;
15143             if(p[1] == '(') { // comment starts with PV
15144                p = strchr(p, ')'); // locate end of PV
15145                if(p == NULL || sep < p+5) return text;
15146                // at this point we have something like "{(.*) +0.23/6 ..."
15147                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15148                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15149                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15150             }
15151             time = -1; sec = -1; deci = -1;
15152             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15153                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15154                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15155                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15156                 return text;
15157             }
15158
15159             if( score_lo < 0 || score_lo >= 100 ) {
15160                 return text;
15161             }
15162
15163             if(sec >= 0) time = 600*time + 10*sec; else
15164             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15165
15166             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15167
15168             /* [HGM] PV time: now locate end of PV info */
15169             while( *++sep >= '0' && *sep <= '9'); // strip depth
15170             if(time >= 0)
15171             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15172             if(sec >= 0)
15173             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15174             if(deci >= 0)
15175             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15176             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15177         }
15178
15179         if( depth <= 0 ) {
15180             return text;
15181         }
15182
15183         if( time < 0 ) {
15184             time = -1;
15185         }
15186
15187         pvInfoList[index-1].depth = depth;
15188         pvInfoList[index-1].score = score;
15189         pvInfoList[index-1].time  = 10*time; // centi-sec
15190         if(*sep == '}') *sep = 0; else *--sep = '{';
15191         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15192     }
15193     return sep;
15194 }
15195
15196 void
15197 SendToProgram (char *message, ChessProgramState *cps)
15198 {
15199     int count, outCount, error;
15200     char buf[MSG_SIZ];
15201
15202     if (cps->pr == NoProc) return;
15203     Attention(cps);
15204
15205     if (appData.debugMode) {
15206         TimeMark now;
15207         GetTimeMark(&now);
15208         fprintf(debugFP, "%ld >%-6s: %s",
15209                 SubtractTimeMarks(&now, &programStartTime),
15210                 cps->which, message);
15211         if(serverFP)
15212             fprintf(serverFP, "%ld >%-6s: %s",
15213                 SubtractTimeMarks(&now, &programStartTime),
15214                 cps->which, message), fflush(serverFP);
15215     }
15216
15217     count = strlen(message);
15218     outCount = OutputToProcess(cps->pr, message, count, &error);
15219     if (outCount < count && !exiting
15220                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15221       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15222       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15223         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15224             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15225                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15226                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15227                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15228             } else {
15229                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15230                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15231                 gameInfo.result = res;
15232             }
15233             gameInfo.resultDetails = StrSave(buf);
15234         }
15235         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15236         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15237     }
15238 }
15239
15240 void
15241 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15242 {
15243     char *end_str;
15244     char buf[MSG_SIZ];
15245     ChessProgramState *cps = (ChessProgramState *)closure;
15246
15247     if (isr != cps->isr) return; /* Killed intentionally */
15248     if (count <= 0) {
15249         if (count == 0) {
15250             RemoveInputSource(cps->isr);
15251             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15252             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15253                     _(cps->which), cps->program);
15254         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15255                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15256                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15257                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15258                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15259                 } else {
15260                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15261                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15262                     gameInfo.result = res;
15263                 }
15264                 gameInfo.resultDetails = StrSave(buf);
15265             }
15266             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15267             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15268         } else {
15269             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15270                     _(cps->which), cps->program);
15271             RemoveInputSource(cps->isr);
15272
15273             /* [AS] Program is misbehaving badly... kill it */
15274             if( count == -2 ) {
15275                 DestroyChildProcess( cps->pr, 9 );
15276                 cps->pr = NoProc;
15277             }
15278
15279             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15280         }
15281         return;
15282     }
15283
15284     if ((end_str = strchr(message, '\r')) != NULL)
15285       *end_str = NULLCHAR;
15286     if ((end_str = strchr(message, '\n')) != NULL)
15287       *end_str = NULLCHAR;
15288
15289     if (appData.debugMode) {
15290         TimeMark now; int print = 1;
15291         char *quote = ""; char c; int i;
15292
15293         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15294                 char start = message[0];
15295                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15296                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15297                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15298                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15299                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15300                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15301                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15302                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15303                    sscanf(message, "hint: %c", &c)!=1 && 
15304                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15305                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15306                     print = (appData.engineComments >= 2);
15307                 }
15308                 message[0] = start; // restore original message
15309         }
15310         if(print) {
15311                 GetTimeMark(&now);
15312                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15313                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15314                         quote,
15315                         message);
15316                 if(serverFP)
15317                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15318                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15319                         quote,
15320                         message), fflush(serverFP);
15321         }
15322     }
15323
15324     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15325     if (appData.icsEngineAnalyze) {
15326         if (strstr(message, "whisper") != NULL ||
15327              strstr(message, "kibitz") != NULL ||
15328             strstr(message, "tellics") != NULL) return;
15329     }
15330
15331     HandleMachineMove(message, cps);
15332 }
15333
15334
15335 void
15336 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15337 {
15338     char buf[MSG_SIZ];
15339     int seconds;
15340
15341     if( timeControl_2 > 0 ) {
15342         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15343             tc = timeControl_2;
15344         }
15345     }
15346     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15347     inc /= cps->timeOdds;
15348     st  /= cps->timeOdds;
15349
15350     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15351
15352     if (st > 0) {
15353       /* Set exact time per move, normally using st command */
15354       if (cps->stKludge) {
15355         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15356         seconds = st % 60;
15357         if (seconds == 0) {
15358           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15359         } else {
15360           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15361         }
15362       } else {
15363         snprintf(buf, MSG_SIZ, "st %d\n", st);
15364       }
15365     } else {
15366       /* Set conventional or incremental time control, using level command */
15367       if (seconds == 0) {
15368         /* Note old gnuchess bug -- minutes:seconds used to not work.
15369            Fixed in later versions, but still avoid :seconds
15370            when seconds is 0. */
15371         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15372       } else {
15373         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15374                  seconds, inc/1000.);
15375       }
15376     }
15377     SendToProgram(buf, cps);
15378
15379     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15380     /* Orthogonally, limit search to given depth */
15381     if (sd > 0) {
15382       if (cps->sdKludge) {
15383         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15384       } else {
15385         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15386       }
15387       SendToProgram(buf, cps);
15388     }
15389
15390     if(cps->nps >= 0) { /* [HGM] nps */
15391         if(cps->supportsNPS == FALSE)
15392           cps->nps = -1; // don't use if engine explicitly says not supported!
15393         else {
15394           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15395           SendToProgram(buf, cps);
15396         }
15397     }
15398 }
15399
15400 ChessProgramState *
15401 WhitePlayer ()
15402 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15403 {
15404     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15405        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15406         return &second;
15407     return &first;
15408 }
15409
15410 void
15411 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15412 {
15413     char message[MSG_SIZ];
15414     long time, otime;
15415
15416     /* Note: this routine must be called when the clocks are stopped
15417        or when they have *just* been set or switched; otherwise
15418        it will be off by the time since the current tick started.
15419     */
15420     if (machineWhite) {
15421         time = whiteTimeRemaining / 10;
15422         otime = blackTimeRemaining / 10;
15423     } else {
15424         time = blackTimeRemaining / 10;
15425         otime = whiteTimeRemaining / 10;
15426     }
15427     /* [HGM] translate opponent's time by time-odds factor */
15428     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15429
15430     if (time <= 0) time = 1;
15431     if (otime <= 0) otime = 1;
15432
15433     snprintf(message, MSG_SIZ, "time %ld\n", time);
15434     SendToProgram(message, cps);
15435
15436     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15437     SendToProgram(message, cps);
15438 }
15439
15440 int
15441 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15442 {
15443   char buf[MSG_SIZ];
15444   int len = strlen(name);
15445   int val;
15446
15447   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15448     (*p) += len + 1;
15449     sscanf(*p, "%d", &val);
15450     *loc = (val != 0);
15451     while (**p && **p != ' ')
15452       (*p)++;
15453     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15454     SendToProgram(buf, cps);
15455     return TRUE;
15456   }
15457   return FALSE;
15458 }
15459
15460 int
15461 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15462 {
15463   char buf[MSG_SIZ];
15464   int len = strlen(name);
15465   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15466     (*p) += len + 1;
15467     sscanf(*p, "%d", loc);
15468     while (**p && **p != ' ') (*p)++;
15469     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15470     SendToProgram(buf, cps);
15471     return TRUE;
15472   }
15473   return FALSE;
15474 }
15475
15476 int
15477 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15478 {
15479   char buf[MSG_SIZ];
15480   int len = strlen(name);
15481   if (strncmp((*p), name, len) == 0
15482       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15483     (*p) += len + 2;
15484     sscanf(*p, "%[^\"]", loc);
15485     while (**p && **p != '\"') (*p)++;
15486     if (**p == '\"') (*p)++;
15487     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15488     SendToProgram(buf, cps);
15489     return TRUE;
15490   }
15491   return FALSE;
15492 }
15493
15494 int
15495 ParseOption (Option *opt, ChessProgramState *cps)
15496 // [HGM] options: process the string that defines an engine option, and determine
15497 // name, type, default value, and allowed value range
15498 {
15499         char *p, *q, buf[MSG_SIZ];
15500         int n, min = (-1)<<31, max = 1<<31, def;
15501
15502         if(p = strstr(opt->name, " -spin ")) {
15503             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15504             if(max < min) max = min; // enforce consistency
15505             if(def < min) def = min;
15506             if(def > max) def = max;
15507             opt->value = def;
15508             opt->min = min;
15509             opt->max = max;
15510             opt->type = Spin;
15511         } else if((p = strstr(opt->name, " -slider "))) {
15512             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15513             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15514             if(max < min) max = min; // enforce consistency
15515             if(def < min) def = min;
15516             if(def > max) def = max;
15517             opt->value = def;
15518             opt->min = min;
15519             opt->max = max;
15520             opt->type = Spin; // Slider;
15521         } else if((p = strstr(opt->name, " -string "))) {
15522             opt->textValue = p+9;
15523             opt->type = TextBox;
15524         } else if((p = strstr(opt->name, " -file "))) {
15525             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15526             opt->textValue = p+7;
15527             opt->type = FileName; // FileName;
15528         } else if((p = strstr(opt->name, " -path "))) {
15529             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15530             opt->textValue = p+7;
15531             opt->type = PathName; // PathName;
15532         } else if(p = strstr(opt->name, " -check ")) {
15533             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15534             opt->value = (def != 0);
15535             opt->type = CheckBox;
15536         } else if(p = strstr(opt->name, " -combo ")) {
15537             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15538             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15539             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15540             opt->value = n = 0;
15541             while(q = StrStr(q, " /// ")) {
15542                 n++; *q = 0;    // count choices, and null-terminate each of them
15543                 q += 5;
15544                 if(*q == '*') { // remember default, which is marked with * prefix
15545                     q++;
15546                     opt->value = n;
15547                 }
15548                 cps->comboList[cps->comboCnt++] = q;
15549             }
15550             cps->comboList[cps->comboCnt++] = NULL;
15551             opt->max = n + 1;
15552             opt->type = ComboBox;
15553         } else if(p = strstr(opt->name, " -button")) {
15554             opt->type = Button;
15555         } else if(p = strstr(opt->name, " -save")) {
15556             opt->type = SaveButton;
15557         } else return FALSE;
15558         *p = 0; // terminate option name
15559         // now look if the command-line options define a setting for this engine option.
15560         if(cps->optionSettings && cps->optionSettings[0])
15561             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15562         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15563           snprintf(buf, MSG_SIZ, "option %s", p);
15564                 if(p = strstr(buf, ",")) *p = 0;
15565                 if(q = strchr(buf, '=')) switch(opt->type) {
15566                     case ComboBox:
15567                         for(n=0; n<opt->max; n++)
15568                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15569                         break;
15570                     case TextBox:
15571                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15572                         break;
15573                     case Spin:
15574                     case CheckBox:
15575                         opt->value = atoi(q+1);
15576                     default:
15577                         break;
15578                 }
15579                 strcat(buf, "\n");
15580                 SendToProgram(buf, cps);
15581         }
15582         return TRUE;
15583 }
15584
15585 void
15586 FeatureDone (ChessProgramState *cps, int val)
15587 {
15588   DelayedEventCallback cb = GetDelayedEvent();
15589   if ((cb == InitBackEnd3 && cps == &first) ||
15590       (cb == SettingsMenuIfReady && cps == &second) ||
15591       (cb == LoadEngine) ||
15592       (cb == TwoMachinesEventIfReady)) {
15593     CancelDelayedEvent();
15594     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15595   }
15596   cps->initDone = val;
15597 }
15598
15599 /* Parse feature command from engine */
15600 void
15601 ParseFeatures (char *args, ChessProgramState *cps)
15602 {
15603   char *p = args;
15604   char *q;
15605   int val;
15606   char buf[MSG_SIZ];
15607
15608   for (;;) {
15609     while (*p == ' ') p++;
15610     if (*p == NULLCHAR) return;
15611
15612     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15613     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15614     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15615     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15616     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15617     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15618     if (BoolFeature(&p, "reuse", &val, cps)) {
15619       /* Engine can disable reuse, but can't enable it if user said no */
15620       if (!val) cps->reuse = FALSE;
15621       continue;
15622     }
15623     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15624     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15625       if (gameMode == TwoMachinesPlay) {
15626         DisplayTwoMachinesTitle();
15627       } else {
15628         DisplayTitle("");
15629       }
15630       continue;
15631     }
15632     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15633     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15634     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15635     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15636     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15637     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15638     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15639     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15640     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15641     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15642     if (IntFeature(&p, "done", &val, cps)) {
15643       FeatureDone(cps, val);
15644       continue;
15645     }
15646     /* Added by Tord: */
15647     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15648     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15649     /* End of additions by Tord */
15650
15651     /* [HGM] added features: */
15652     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15653     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15654     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15655     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15656     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15657     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15658     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15659         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15660           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15661             SendToProgram(buf, cps);
15662             continue;
15663         }
15664         if(cps->nrOptions >= MAX_OPTIONS) {
15665             cps->nrOptions--;
15666             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15667             DisplayError(buf, 0);
15668         }
15669         continue;
15670     }
15671     /* End of additions by HGM */
15672
15673     /* unknown feature: complain and skip */
15674     q = p;
15675     while (*q && *q != '=') q++;
15676     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15677     SendToProgram(buf, cps);
15678     p = q;
15679     if (*p == '=') {
15680       p++;
15681       if (*p == '\"') {
15682         p++;
15683         while (*p && *p != '\"') p++;
15684         if (*p == '\"') p++;
15685       } else {
15686         while (*p && *p != ' ') p++;
15687       }
15688     }
15689   }
15690
15691 }
15692
15693 void
15694 PeriodicUpdatesEvent (int newState)
15695 {
15696     if (newState == appData.periodicUpdates)
15697       return;
15698
15699     appData.periodicUpdates=newState;
15700
15701     /* Display type changes, so update it now */
15702 //    DisplayAnalysis();
15703
15704     /* Get the ball rolling again... */
15705     if (newState) {
15706         AnalysisPeriodicEvent(1);
15707         StartAnalysisClock();
15708     }
15709 }
15710
15711 void
15712 PonderNextMoveEvent (int newState)
15713 {
15714     if (newState == appData.ponderNextMove) return;
15715     if (gameMode == EditPosition) EditPositionDone(TRUE);
15716     if (newState) {
15717         SendToProgram("hard\n", &first);
15718         if (gameMode == TwoMachinesPlay) {
15719             SendToProgram("hard\n", &second);
15720         }
15721     } else {
15722         SendToProgram("easy\n", &first);
15723         thinkOutput[0] = NULLCHAR;
15724         if (gameMode == TwoMachinesPlay) {
15725             SendToProgram("easy\n", &second);
15726         }
15727     }
15728     appData.ponderNextMove = newState;
15729 }
15730
15731 void
15732 NewSettingEvent (int option, int *feature, char *command, int value)
15733 {
15734     char buf[MSG_SIZ];
15735
15736     if (gameMode == EditPosition) EditPositionDone(TRUE);
15737     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15738     if(feature == NULL || *feature) SendToProgram(buf, &first);
15739     if (gameMode == TwoMachinesPlay) {
15740         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15741     }
15742 }
15743
15744 void
15745 ShowThinkingEvent ()
15746 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15747 {
15748     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15749     int newState = appData.showThinking
15750         // [HGM] thinking: other features now need thinking output as well
15751         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15752
15753     if (oldState == newState) return;
15754     oldState = newState;
15755     if (gameMode == EditPosition) EditPositionDone(TRUE);
15756     if (oldState) {
15757         SendToProgram("post\n", &first);
15758         if (gameMode == TwoMachinesPlay) {
15759             SendToProgram("post\n", &second);
15760         }
15761     } else {
15762         SendToProgram("nopost\n", &first);
15763         thinkOutput[0] = NULLCHAR;
15764         if (gameMode == TwoMachinesPlay) {
15765             SendToProgram("nopost\n", &second);
15766         }
15767     }
15768 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15769 }
15770
15771 void
15772 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15773 {
15774   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15775   if (pr == NoProc) return;
15776   AskQuestion(title, question, replyPrefix, pr);
15777 }
15778
15779 void
15780 TypeInEvent (char firstChar)
15781 {
15782     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15783         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15784         gameMode == AnalyzeMode || gameMode == EditGame || 
15785         gameMode == EditPosition || gameMode == IcsExamining ||
15786         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15787         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15788                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15789                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15790         gameMode == Training) PopUpMoveDialog(firstChar);
15791 }
15792
15793 void
15794 TypeInDoneEvent (char *move)
15795 {
15796         Board board;
15797         int n, fromX, fromY, toX, toY;
15798         char promoChar;
15799         ChessMove moveType;
15800
15801         // [HGM] FENedit
15802         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15803                 EditPositionPasteFEN(move);
15804                 return;
15805         }
15806         // [HGM] movenum: allow move number to be typed in any mode
15807         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15808           ToNrEvent(2*n-1);
15809           return;
15810         }
15811         // undocumented kludge: allow command-line option to be typed in!
15812         // (potentially fatal, and does not implement the effect of the option.)
15813         // should only be used for options that are values on which future decisions will be made,
15814         // and definitely not on options that would be used during initialization.
15815         if(strstr(move, "!!! -") == move) {
15816             ParseArgsFromString(move+4);
15817             return;
15818         }
15819
15820       if (gameMode != EditGame && currentMove != forwardMostMove && 
15821         gameMode != Training) {
15822         DisplayMoveError(_("Displayed move is not current"));
15823       } else {
15824         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15825           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15826         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15827         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15828           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15829           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15830         } else {
15831           DisplayMoveError(_("Could not parse move"));
15832         }
15833       }
15834 }
15835
15836 void
15837 DisplayMove (int moveNumber)
15838 {
15839     char message[MSG_SIZ];
15840     char res[MSG_SIZ];
15841     char cpThinkOutput[MSG_SIZ];
15842
15843     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15844
15845     if (moveNumber == forwardMostMove - 1 ||
15846         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15847
15848         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15849
15850         if (strchr(cpThinkOutput, '\n')) {
15851             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15852         }
15853     } else {
15854         *cpThinkOutput = NULLCHAR;
15855     }
15856
15857     /* [AS] Hide thinking from human user */
15858     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15859         *cpThinkOutput = NULLCHAR;
15860         if( thinkOutput[0] != NULLCHAR ) {
15861             int i;
15862
15863             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15864                 cpThinkOutput[i] = '.';
15865             }
15866             cpThinkOutput[i] = NULLCHAR;
15867             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15868         }
15869     }
15870
15871     if (moveNumber == forwardMostMove - 1 &&
15872         gameInfo.resultDetails != NULL) {
15873         if (gameInfo.resultDetails[0] == NULLCHAR) {
15874           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15875         } else {
15876           snprintf(res, MSG_SIZ, " {%s} %s",
15877                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15878         }
15879     } else {
15880         res[0] = NULLCHAR;
15881     }
15882
15883     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15884         DisplayMessage(res, cpThinkOutput);
15885     } else {
15886       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15887                 WhiteOnMove(moveNumber) ? " " : ".. ",
15888                 parseList[moveNumber], res);
15889         DisplayMessage(message, cpThinkOutput);
15890     }
15891 }
15892
15893 void
15894 DisplayComment (int moveNumber, char *text)
15895 {
15896     char title[MSG_SIZ];
15897
15898     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15899       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15900     } else {
15901       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15902               WhiteOnMove(moveNumber) ? " " : ".. ",
15903               parseList[moveNumber]);
15904     }
15905     if (text != NULL && (appData.autoDisplayComment || commentUp))
15906         CommentPopUp(title, text);
15907 }
15908
15909 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15910  * might be busy thinking or pondering.  It can be omitted if your
15911  * gnuchess is configured to stop thinking immediately on any user
15912  * input.  However, that gnuchess feature depends on the FIONREAD
15913  * ioctl, which does not work properly on some flavors of Unix.
15914  */
15915 void
15916 Attention (ChessProgramState *cps)
15917 {
15918 #if ATTENTION
15919     if (!cps->useSigint) return;
15920     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15921     switch (gameMode) {
15922       case MachinePlaysWhite:
15923       case MachinePlaysBlack:
15924       case TwoMachinesPlay:
15925       case IcsPlayingWhite:
15926       case IcsPlayingBlack:
15927       case AnalyzeMode:
15928       case AnalyzeFile:
15929         /* Skip if we know it isn't thinking */
15930         if (!cps->maybeThinking) return;
15931         if (appData.debugMode)
15932           fprintf(debugFP, "Interrupting %s\n", cps->which);
15933         InterruptChildProcess(cps->pr);
15934         cps->maybeThinking = FALSE;
15935         break;
15936       default:
15937         break;
15938     }
15939 #endif /*ATTENTION*/
15940 }
15941
15942 int
15943 CheckFlags ()
15944 {
15945     if (whiteTimeRemaining <= 0) {
15946         if (!whiteFlag) {
15947             whiteFlag = TRUE;
15948             if (appData.icsActive) {
15949                 if (appData.autoCallFlag &&
15950                     gameMode == IcsPlayingBlack && !blackFlag) {
15951                   SendToICS(ics_prefix);
15952                   SendToICS("flag\n");
15953                 }
15954             } else {
15955                 if (blackFlag) {
15956                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15957                 } else {
15958                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15959                     if (appData.autoCallFlag) {
15960                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15961                         return TRUE;
15962                     }
15963                 }
15964             }
15965         }
15966     }
15967     if (blackTimeRemaining <= 0) {
15968         if (!blackFlag) {
15969             blackFlag = TRUE;
15970             if (appData.icsActive) {
15971                 if (appData.autoCallFlag &&
15972                     gameMode == IcsPlayingWhite && !whiteFlag) {
15973                   SendToICS(ics_prefix);
15974                   SendToICS("flag\n");
15975                 }
15976             } else {
15977                 if (whiteFlag) {
15978                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15979                 } else {
15980                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15981                     if (appData.autoCallFlag) {
15982                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15983                         return TRUE;
15984                     }
15985                 }
15986             }
15987         }
15988     }
15989     return FALSE;
15990 }
15991
15992 void
15993 CheckTimeControl ()
15994 {
15995     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15996         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15997
15998     /*
15999      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16000      */
16001     if ( !WhiteOnMove(forwardMostMove) ) {
16002         /* White made time control */
16003         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16004         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16005         /* [HGM] time odds: correct new time quota for time odds! */
16006                                             / WhitePlayer()->timeOdds;
16007         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16008     } else {
16009         lastBlack -= blackTimeRemaining;
16010         /* Black made time control */
16011         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16012                                             / WhitePlayer()->other->timeOdds;
16013         lastWhite = whiteTimeRemaining;
16014     }
16015 }
16016
16017 void
16018 DisplayBothClocks ()
16019 {
16020     int wom = gameMode == EditPosition ?
16021       !blackPlaysFirst : WhiteOnMove(currentMove);
16022     DisplayWhiteClock(whiteTimeRemaining, wom);
16023     DisplayBlackClock(blackTimeRemaining, !wom);
16024 }
16025
16026
16027 /* Timekeeping seems to be a portability nightmare.  I think everyone
16028    has ftime(), but I'm really not sure, so I'm including some ifdefs
16029    to use other calls if you don't.  Clocks will be less accurate if
16030    you have neither ftime nor gettimeofday.
16031 */
16032
16033 /* VS 2008 requires the #include outside of the function */
16034 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16035 #include <sys/timeb.h>
16036 #endif
16037
16038 /* Get the current time as a TimeMark */
16039 void
16040 GetTimeMark (TimeMark *tm)
16041 {
16042 #if HAVE_GETTIMEOFDAY
16043
16044     struct timeval timeVal;
16045     struct timezone timeZone;
16046
16047     gettimeofday(&timeVal, &timeZone);
16048     tm->sec = (long) timeVal.tv_sec;
16049     tm->ms = (int) (timeVal.tv_usec / 1000L);
16050
16051 #else /*!HAVE_GETTIMEOFDAY*/
16052 #if HAVE_FTIME
16053
16054 // include <sys/timeb.h> / moved to just above start of function
16055     struct timeb timeB;
16056
16057     ftime(&timeB);
16058     tm->sec = (long) timeB.time;
16059     tm->ms = (int) timeB.millitm;
16060
16061 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16062     tm->sec = (long) time(NULL);
16063     tm->ms = 0;
16064 #endif
16065 #endif
16066 }
16067
16068 /* Return the difference in milliseconds between two
16069    time marks.  We assume the difference will fit in a long!
16070 */
16071 long
16072 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16073 {
16074     return 1000L*(tm2->sec - tm1->sec) +
16075            (long) (tm2->ms - tm1->ms);
16076 }
16077
16078
16079 /*
16080  * Code to manage the game clocks.
16081  *
16082  * In tournament play, black starts the clock and then white makes a move.
16083  * We give the human user a slight advantage if he is playing white---the
16084  * clocks don't run until he makes his first move, so it takes zero time.
16085  * Also, we don't account for network lag, so we could get out of sync
16086  * with GNU Chess's clock -- but then, referees are always right.
16087  */
16088
16089 static TimeMark tickStartTM;
16090 static long intendedTickLength;
16091
16092 long
16093 NextTickLength (long timeRemaining)
16094 {
16095     long nominalTickLength, nextTickLength;
16096
16097     if (timeRemaining > 0L && timeRemaining <= 10000L)
16098       nominalTickLength = 100L;
16099     else
16100       nominalTickLength = 1000L;
16101     nextTickLength = timeRemaining % nominalTickLength;
16102     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16103
16104     return nextTickLength;
16105 }
16106
16107 /* Adjust clock one minute up or down */
16108 void
16109 AdjustClock (Boolean which, int dir)
16110 {
16111     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16112     if(which) blackTimeRemaining += 60000*dir;
16113     else      whiteTimeRemaining += 60000*dir;
16114     DisplayBothClocks();
16115     adjustedClock = TRUE;
16116 }
16117
16118 /* Stop clocks and reset to a fresh time control */
16119 void
16120 ResetClocks ()
16121 {
16122     (void) StopClockTimer();
16123     if (appData.icsActive) {
16124         whiteTimeRemaining = blackTimeRemaining = 0;
16125     } else if (searchTime) {
16126         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16127         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16128     } else { /* [HGM] correct new time quote for time odds */
16129         whiteTC = blackTC = fullTimeControlString;
16130         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16131         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16132     }
16133     if (whiteFlag || blackFlag) {
16134         DisplayTitle("");
16135         whiteFlag = blackFlag = FALSE;
16136     }
16137     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16138     DisplayBothClocks();
16139     adjustedClock = FALSE;
16140 }
16141
16142 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16143
16144 /* Decrement running clock by amount of time that has passed */
16145 void
16146 DecrementClocks ()
16147 {
16148     long timeRemaining;
16149     long lastTickLength, fudge;
16150     TimeMark now;
16151
16152     if (!appData.clockMode) return;
16153     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16154
16155     GetTimeMark(&now);
16156
16157     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16158
16159     /* Fudge if we woke up a little too soon */
16160     fudge = intendedTickLength - lastTickLength;
16161     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16162
16163     if (WhiteOnMove(forwardMostMove)) {
16164         if(whiteNPS >= 0) lastTickLength = 0;
16165         timeRemaining = whiteTimeRemaining -= lastTickLength;
16166         if(timeRemaining < 0 && !appData.icsActive) {
16167             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16168             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16169                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16170                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16171             }
16172         }
16173         DisplayWhiteClock(whiteTimeRemaining - fudge,
16174                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16175     } else {
16176         if(blackNPS >= 0) lastTickLength = 0;
16177         timeRemaining = blackTimeRemaining -= lastTickLength;
16178         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16179             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16180             if(suddenDeath) {
16181                 blackStartMove = forwardMostMove;
16182                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16183             }
16184         }
16185         DisplayBlackClock(blackTimeRemaining - fudge,
16186                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16187     }
16188     if (CheckFlags()) return;
16189
16190     tickStartTM = now;
16191     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16192     StartClockTimer(intendedTickLength);
16193
16194     /* if the time remaining has fallen below the alarm threshold, sound the
16195      * alarm. if the alarm has sounded and (due to a takeback or time control
16196      * with increment) the time remaining has increased to a level above the
16197      * threshold, reset the alarm so it can sound again.
16198      */
16199
16200     if (appData.icsActive && appData.icsAlarm) {
16201
16202         /* make sure we are dealing with the user's clock */
16203         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16204                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16205            )) return;
16206
16207         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16208             alarmSounded = FALSE;
16209         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16210             PlayAlarmSound();
16211             alarmSounded = TRUE;
16212         }
16213     }
16214 }
16215
16216
16217 /* A player has just moved, so stop the previously running
16218    clock and (if in clock mode) start the other one.
16219    We redisplay both clocks in case we're in ICS mode, because
16220    ICS gives us an update to both clocks after every move.
16221    Note that this routine is called *after* forwardMostMove
16222    is updated, so the last fractional tick must be subtracted
16223    from the color that is *not* on move now.
16224 */
16225 void
16226 SwitchClocks (int newMoveNr)
16227 {
16228     long lastTickLength;
16229     TimeMark now;
16230     int flagged = FALSE;
16231
16232     GetTimeMark(&now);
16233
16234     if (StopClockTimer() && appData.clockMode) {
16235         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16236         if (!WhiteOnMove(forwardMostMove)) {
16237             if(blackNPS >= 0) lastTickLength = 0;
16238             blackTimeRemaining -= lastTickLength;
16239            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16240 //         if(pvInfoList[forwardMostMove].time == -1)
16241                  pvInfoList[forwardMostMove].time =               // use GUI time
16242                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16243         } else {
16244            if(whiteNPS >= 0) lastTickLength = 0;
16245            whiteTimeRemaining -= lastTickLength;
16246            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16247 //         if(pvInfoList[forwardMostMove].time == -1)
16248                  pvInfoList[forwardMostMove].time =
16249                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16250         }
16251         flagged = CheckFlags();
16252     }
16253     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16254     CheckTimeControl();
16255
16256     if (flagged || !appData.clockMode) return;
16257
16258     switch (gameMode) {
16259       case MachinePlaysBlack:
16260       case MachinePlaysWhite:
16261       case BeginningOfGame:
16262         if (pausing) return;
16263         break;
16264
16265       case EditGame:
16266       case PlayFromGameFile:
16267       case IcsExamining:
16268         return;
16269
16270       default:
16271         break;
16272     }
16273
16274     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16275         if(WhiteOnMove(forwardMostMove))
16276              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16277         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16278     }
16279
16280     tickStartTM = now;
16281     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16282       whiteTimeRemaining : blackTimeRemaining);
16283     StartClockTimer(intendedTickLength);
16284 }
16285
16286
16287 /* Stop both clocks */
16288 void
16289 StopClocks ()
16290 {
16291     long lastTickLength;
16292     TimeMark now;
16293
16294     if (!StopClockTimer()) return;
16295     if (!appData.clockMode) return;
16296
16297     GetTimeMark(&now);
16298
16299     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16300     if (WhiteOnMove(forwardMostMove)) {
16301         if(whiteNPS >= 0) lastTickLength = 0;
16302         whiteTimeRemaining -= lastTickLength;
16303         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16304     } else {
16305         if(blackNPS >= 0) lastTickLength = 0;
16306         blackTimeRemaining -= lastTickLength;
16307         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16308     }
16309     CheckFlags();
16310 }
16311
16312 /* Start clock of player on move.  Time may have been reset, so
16313    if clock is already running, stop and restart it. */
16314 void
16315 StartClocks ()
16316 {
16317     (void) StopClockTimer(); /* in case it was running already */
16318     DisplayBothClocks();
16319     if (CheckFlags()) return;
16320
16321     if (!appData.clockMode) return;
16322     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16323
16324     GetTimeMark(&tickStartTM);
16325     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16326       whiteTimeRemaining : blackTimeRemaining);
16327
16328    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16329     whiteNPS = blackNPS = -1;
16330     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16331        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16332         whiteNPS = first.nps;
16333     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16334        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16335         blackNPS = first.nps;
16336     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16337         whiteNPS = second.nps;
16338     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16339         blackNPS = second.nps;
16340     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16341
16342     StartClockTimer(intendedTickLength);
16343 }
16344
16345 char *
16346 TimeString (long ms)
16347 {
16348     long second, minute, hour, day;
16349     char *sign = "";
16350     static char buf[32];
16351
16352     if (ms > 0 && ms <= 9900) {
16353       /* convert milliseconds to tenths, rounding up */
16354       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16355
16356       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16357       return buf;
16358     }
16359
16360     /* convert milliseconds to seconds, rounding up */
16361     /* use floating point to avoid strangeness of integer division
16362        with negative dividends on many machines */
16363     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16364
16365     if (second < 0) {
16366         sign = "-";
16367         second = -second;
16368     }
16369
16370     day = second / (60 * 60 * 24);
16371     second = second % (60 * 60 * 24);
16372     hour = second / (60 * 60);
16373     second = second % (60 * 60);
16374     minute = second / 60;
16375     second = second % 60;
16376
16377     if (day > 0)
16378       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16379               sign, day, hour, minute, second);
16380     else if (hour > 0)
16381       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16382     else
16383       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16384
16385     return buf;
16386 }
16387
16388
16389 /*
16390  * This is necessary because some C libraries aren't ANSI C compliant yet.
16391  */
16392 char *
16393 StrStr (char *string, char *match)
16394 {
16395     int i, length;
16396
16397     length = strlen(match);
16398
16399     for (i = strlen(string) - length; i >= 0; i--, string++)
16400       if (!strncmp(match, string, length))
16401         return string;
16402
16403     return NULL;
16404 }
16405
16406 char *
16407 StrCaseStr (char *string, char *match)
16408 {
16409     int i, j, length;
16410
16411     length = strlen(match);
16412
16413     for (i = strlen(string) - length; i >= 0; i--, string++) {
16414         for (j = 0; j < length; j++) {
16415             if (ToLower(match[j]) != ToLower(string[j]))
16416               break;
16417         }
16418         if (j == length) return string;
16419     }
16420
16421     return NULL;
16422 }
16423
16424 #ifndef _amigados
16425 int
16426 StrCaseCmp (char *s1, char *s2)
16427 {
16428     char c1, c2;
16429
16430     for (;;) {
16431         c1 = ToLower(*s1++);
16432         c2 = ToLower(*s2++);
16433         if (c1 > c2) return 1;
16434         if (c1 < c2) return -1;
16435         if (c1 == NULLCHAR) return 0;
16436     }
16437 }
16438
16439
16440 int
16441 ToLower (int c)
16442 {
16443     return isupper(c) ? tolower(c) : c;
16444 }
16445
16446
16447 int
16448 ToUpper (int c)
16449 {
16450     return islower(c) ? toupper(c) : c;
16451 }
16452 #endif /* !_amigados    */
16453
16454 char *
16455 StrSave (char *s)
16456 {
16457   char *ret;
16458
16459   if ((ret = (char *) malloc(strlen(s) + 1)))
16460     {
16461       safeStrCpy(ret, s, strlen(s)+1);
16462     }
16463   return ret;
16464 }
16465
16466 char *
16467 StrSavePtr (char *s, char **savePtr)
16468 {
16469     if (*savePtr) {
16470         free(*savePtr);
16471     }
16472     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16473       safeStrCpy(*savePtr, s, strlen(s)+1);
16474     }
16475     return(*savePtr);
16476 }
16477
16478 char *
16479 PGNDate ()
16480 {
16481     time_t clock;
16482     struct tm *tm;
16483     char buf[MSG_SIZ];
16484
16485     clock = time((time_t *)NULL);
16486     tm = localtime(&clock);
16487     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16488             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16489     return StrSave(buf);
16490 }
16491
16492
16493 char *
16494 PositionToFEN (int move, char *overrideCastling)
16495 {
16496     int i, j, fromX, fromY, toX, toY;
16497     int whiteToPlay;
16498     char buf[MSG_SIZ];
16499     char *p, *q;
16500     int emptycount;
16501     ChessSquare piece;
16502
16503     whiteToPlay = (gameMode == EditPosition) ?
16504       !blackPlaysFirst : (move % 2 == 0);
16505     p = buf;
16506
16507     /* Piece placement data */
16508     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16509         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16510         emptycount = 0;
16511         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16512             if (boards[move][i][j] == EmptySquare) {
16513                 emptycount++;
16514             } else { ChessSquare piece = boards[move][i][j];
16515                 if (emptycount > 0) {
16516                     if(emptycount<10) /* [HGM] can be >= 10 */
16517                         *p++ = '0' + emptycount;
16518                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16519                     emptycount = 0;
16520                 }
16521                 if(PieceToChar(piece) == '+') {
16522                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16523                     *p++ = '+';
16524                     piece = (ChessSquare)(DEMOTED piece);
16525                 }
16526                 *p++ = PieceToChar(piece);
16527                 if(p[-1] == '~') {
16528                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16529                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16530                     *p++ = '~';
16531                 }
16532             }
16533         }
16534         if (emptycount > 0) {
16535             if(emptycount<10) /* [HGM] can be >= 10 */
16536                 *p++ = '0' + emptycount;
16537             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16538             emptycount = 0;
16539         }
16540         *p++ = '/';
16541     }
16542     *(p - 1) = ' ';
16543
16544     /* [HGM] print Crazyhouse or Shogi holdings */
16545     if( gameInfo.holdingsWidth ) {
16546         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16547         q = p;
16548         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16549             piece = boards[move][i][BOARD_WIDTH-1];
16550             if( piece != EmptySquare )
16551               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16552                   *p++ = PieceToChar(piece);
16553         }
16554         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16555             piece = boards[move][BOARD_HEIGHT-i-1][0];
16556             if( piece != EmptySquare )
16557               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16558                   *p++ = PieceToChar(piece);
16559         }
16560
16561         if( q == p ) *p++ = '-';
16562         *p++ = ']';
16563         *p++ = ' ';
16564     }
16565
16566     /* Active color */
16567     *p++ = whiteToPlay ? 'w' : 'b';
16568     *p++ = ' ';
16569
16570   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16571     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16572   } else {
16573   if(nrCastlingRights) {
16574      q = p;
16575      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16576        /* [HGM] write directly from rights */
16577            if(boards[move][CASTLING][2] != NoRights &&
16578               boards[move][CASTLING][0] != NoRights   )
16579                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16580            if(boards[move][CASTLING][2] != NoRights &&
16581               boards[move][CASTLING][1] != NoRights   )
16582                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16583            if(boards[move][CASTLING][5] != NoRights &&
16584               boards[move][CASTLING][3] != NoRights   )
16585                 *p++ = boards[move][CASTLING][3] + AAA;
16586            if(boards[move][CASTLING][5] != NoRights &&
16587               boards[move][CASTLING][4] != NoRights   )
16588                 *p++ = boards[move][CASTLING][4] + AAA;
16589      } else {
16590
16591         /* [HGM] write true castling rights */
16592         if( nrCastlingRights == 6 ) {
16593             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16594                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16595             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16596                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16597             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16598                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16599             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16600                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16601         }
16602      }
16603      if (q == p) *p++ = '-'; /* No castling rights */
16604      *p++ = ' ';
16605   }
16606
16607   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16608      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16609     /* En passant target square */
16610     if (move > backwardMostMove) {
16611         fromX = moveList[move - 1][0] - AAA;
16612         fromY = moveList[move - 1][1] - ONE;
16613         toX = moveList[move - 1][2] - AAA;
16614         toY = moveList[move - 1][3] - ONE;
16615         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16616             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16617             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16618             fromX == toX) {
16619             /* 2-square pawn move just happened */
16620             *p++ = toX + AAA;
16621             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16622         } else {
16623             *p++ = '-';
16624         }
16625     } else if(move == backwardMostMove) {
16626         // [HGM] perhaps we should always do it like this, and forget the above?
16627         if((signed char)boards[move][EP_STATUS] >= 0) {
16628             *p++ = boards[move][EP_STATUS] + AAA;
16629             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16630         } else {
16631             *p++ = '-';
16632         }
16633     } else {
16634         *p++ = '-';
16635     }
16636     *p++ = ' ';
16637   }
16638   }
16639
16640     /* [HGM] find reversible plies */
16641     {   int i = 0, j=move;
16642
16643         if (appData.debugMode) { int k;
16644             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16645             for(k=backwardMostMove; k<=forwardMostMove; k++)
16646                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16647
16648         }
16649
16650         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16651         if( j == backwardMostMove ) i += initialRulePlies;
16652         sprintf(p, "%d ", i);
16653         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16654     }
16655     /* Fullmove number */
16656     sprintf(p, "%d", (move / 2) + 1);
16657
16658     return StrSave(buf);
16659 }
16660
16661 Boolean
16662 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16663 {
16664     int i, j;
16665     char *p, c;
16666     int emptycount;
16667     ChessSquare piece;
16668
16669     p = fen;
16670
16671     /* [HGM] by default clear Crazyhouse holdings, if present */
16672     if(gameInfo.holdingsWidth) {
16673        for(i=0; i<BOARD_HEIGHT; i++) {
16674            board[i][0]             = EmptySquare; /* black holdings */
16675            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16676            board[i][1]             = (ChessSquare) 0; /* black counts */
16677            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16678        }
16679     }
16680
16681     /* Piece placement data */
16682     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16683         j = 0;
16684         for (;;) {
16685             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16686                 if (*p == '/') p++;
16687                 emptycount = gameInfo.boardWidth - j;
16688                 while (emptycount--)
16689                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16690                 break;
16691 #if(BOARD_FILES >= 10)
16692             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16693                 p++; emptycount=10;
16694                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16695                 while (emptycount--)
16696                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16697 #endif
16698             } else if (isdigit(*p)) {
16699                 emptycount = *p++ - '0';
16700                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16701                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16702                 while (emptycount--)
16703                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16704             } else if (*p == '+' || isalpha(*p)) {
16705                 if (j >= gameInfo.boardWidth) return FALSE;
16706                 if(*p=='+') {
16707                     piece = CharToPiece(*++p);
16708                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16709                     piece = (ChessSquare) (PROMOTED piece ); p++;
16710                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16711                 } else piece = CharToPiece(*p++);
16712
16713                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16714                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16715                     piece = (ChessSquare) (PROMOTED piece);
16716                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16717                     p++;
16718                 }
16719                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16720             } else {
16721                 return FALSE;
16722             }
16723         }
16724     }
16725     while (*p == '/' || *p == ' ') p++;
16726
16727     /* [HGM] look for Crazyhouse holdings here */
16728     while(*p==' ') p++;
16729     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16730         if(*p == '[') p++;
16731         if(*p == '-' ) p++; /* empty holdings */ else {
16732             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16733             /* if we would allow FEN reading to set board size, we would   */
16734             /* have to add holdings and shift the board read so far here   */
16735             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16736                 p++;
16737                 if((int) piece >= (int) BlackPawn ) {
16738                     i = (int)piece - (int)BlackPawn;
16739                     i = PieceToNumber((ChessSquare)i);
16740                     if( i >= gameInfo.holdingsSize ) return FALSE;
16741                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16742                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16743                 } else {
16744                     i = (int)piece - (int)WhitePawn;
16745                     i = PieceToNumber((ChessSquare)i);
16746                     if( i >= gameInfo.holdingsSize ) return FALSE;
16747                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16748                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16749                 }
16750             }
16751         }
16752         if(*p == ']') p++;
16753     }
16754
16755     while(*p == ' ') p++;
16756
16757     /* Active color */
16758     c = *p++;
16759     if(appData.colorNickNames) {
16760       if( c == appData.colorNickNames[0] ) c = 'w'; else
16761       if( c == appData.colorNickNames[1] ) c = 'b';
16762     }
16763     switch (c) {
16764       case 'w':
16765         *blackPlaysFirst = FALSE;
16766         break;
16767       case 'b':
16768         *blackPlaysFirst = TRUE;
16769         break;
16770       default:
16771         return FALSE;
16772     }
16773
16774     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16775     /* return the extra info in global variiables             */
16776
16777     /* set defaults in case FEN is incomplete */
16778     board[EP_STATUS] = EP_UNKNOWN;
16779     for(i=0; i<nrCastlingRights; i++ ) {
16780         board[CASTLING][i] =
16781             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16782     }   /* assume possible unless obviously impossible */
16783     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16784     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16785     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16786                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16787     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16788     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16789     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16790                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16791     FENrulePlies = 0;
16792
16793     while(*p==' ') p++;
16794     if(nrCastlingRights) {
16795       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16796           /* castling indicator present, so default becomes no castlings */
16797           for(i=0; i<nrCastlingRights; i++ ) {
16798                  board[CASTLING][i] = NoRights;
16799           }
16800       }
16801       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16802              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16803              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16804              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16805         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16806
16807         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16808             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16809             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16810         }
16811         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16812             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16813         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16814                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16815         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16816                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16817         switch(c) {
16818           case'K':
16819               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16820               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16821               board[CASTLING][2] = whiteKingFile;
16822               break;
16823           case'Q':
16824               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16825               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16826               board[CASTLING][2] = whiteKingFile;
16827               break;
16828           case'k':
16829               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16830               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16831               board[CASTLING][5] = blackKingFile;
16832               break;
16833           case'q':
16834               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16835               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16836               board[CASTLING][5] = blackKingFile;
16837           case '-':
16838               break;
16839           default: /* FRC castlings */
16840               if(c >= 'a') { /* black rights */
16841                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16842                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16843                   if(i == BOARD_RGHT) break;
16844                   board[CASTLING][5] = i;
16845                   c -= AAA;
16846                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16847                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16848                   if(c > i)
16849                       board[CASTLING][3] = c;
16850                   else
16851                       board[CASTLING][4] = c;
16852               } else { /* white rights */
16853                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16854                     if(board[0][i] == WhiteKing) break;
16855                   if(i == BOARD_RGHT) break;
16856                   board[CASTLING][2] = i;
16857                   c -= AAA - 'a' + 'A';
16858                   if(board[0][c] >= WhiteKing) break;
16859                   if(c > i)
16860                       board[CASTLING][0] = c;
16861                   else
16862                       board[CASTLING][1] = c;
16863               }
16864         }
16865       }
16866       for(i=0; i<nrCastlingRights; i++)
16867         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16868     if (appData.debugMode) {
16869         fprintf(debugFP, "FEN castling rights:");
16870         for(i=0; i<nrCastlingRights; i++)
16871         fprintf(debugFP, " %d", board[CASTLING][i]);
16872         fprintf(debugFP, "\n");
16873     }
16874
16875       while(*p==' ') p++;
16876     }
16877
16878     /* read e.p. field in games that know e.p. capture */
16879     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16880        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16881       if(*p=='-') {
16882         p++; board[EP_STATUS] = EP_NONE;
16883       } else {
16884          char c = *p++ - AAA;
16885
16886          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16887          if(*p >= '0' && *p <='9') p++;
16888          board[EP_STATUS] = c;
16889       }
16890     }
16891
16892
16893     if(sscanf(p, "%d", &i) == 1) {
16894         FENrulePlies = i; /* 50-move ply counter */
16895         /* (The move number is still ignored)    */
16896     }
16897
16898     return TRUE;
16899 }
16900
16901 void
16902 EditPositionPasteFEN (char *fen)
16903 {
16904   if (fen != NULL) {
16905     Board initial_position;
16906
16907     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16908       DisplayError(_("Bad FEN position in clipboard"), 0);
16909       return ;
16910     } else {
16911       int savedBlackPlaysFirst = blackPlaysFirst;
16912       EditPositionEvent();
16913       blackPlaysFirst = savedBlackPlaysFirst;
16914       CopyBoard(boards[0], initial_position);
16915       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16916       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16917       DisplayBothClocks();
16918       DrawPosition(FALSE, boards[currentMove]);
16919     }
16920   }
16921 }
16922
16923 static char cseq[12] = "\\   ";
16924
16925 Boolean
16926 set_cont_sequence (char *new_seq)
16927 {
16928     int len;
16929     Boolean ret;
16930
16931     // handle bad attempts to set the sequence
16932         if (!new_seq)
16933                 return 0; // acceptable error - no debug
16934
16935     len = strlen(new_seq);
16936     ret = (len > 0) && (len < sizeof(cseq));
16937     if (ret)
16938       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16939     else if (appData.debugMode)
16940       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16941     return ret;
16942 }
16943
16944 /*
16945     reformat a source message so words don't cross the width boundary.  internal
16946     newlines are not removed.  returns the wrapped size (no null character unless
16947     included in source message).  If dest is NULL, only calculate the size required
16948     for the dest buffer.  lp argument indicats line position upon entry, and it's
16949     passed back upon exit.
16950 */
16951 int
16952 wrap (char *dest, char *src, int count, int width, int *lp)
16953 {
16954     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16955
16956     cseq_len = strlen(cseq);
16957     old_line = line = *lp;
16958     ansi = len = clen = 0;
16959
16960     for (i=0; i < count; i++)
16961     {
16962         if (src[i] == '\033')
16963             ansi = 1;
16964
16965         // if we hit the width, back up
16966         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16967         {
16968             // store i & len in case the word is too long
16969             old_i = i, old_len = len;
16970
16971             // find the end of the last word
16972             while (i && src[i] != ' ' && src[i] != '\n')
16973             {
16974                 i--;
16975                 len--;
16976             }
16977
16978             // word too long?  restore i & len before splitting it
16979             if ((old_i-i+clen) >= width)
16980             {
16981                 i = old_i;
16982                 len = old_len;
16983             }
16984
16985             // extra space?
16986             if (i && src[i-1] == ' ')
16987                 len--;
16988
16989             if (src[i] != ' ' && src[i] != '\n')
16990             {
16991                 i--;
16992                 if (len)
16993                     len--;
16994             }
16995
16996             // now append the newline and continuation sequence
16997             if (dest)
16998                 dest[len] = '\n';
16999             len++;
17000             if (dest)
17001                 strncpy(dest+len, cseq, cseq_len);
17002             len += cseq_len;
17003             line = cseq_len;
17004             clen = cseq_len;
17005             continue;
17006         }
17007
17008         if (dest)
17009             dest[len] = src[i];
17010         len++;
17011         if (!ansi)
17012             line++;
17013         if (src[i] == '\n')
17014             line = 0;
17015         if (src[i] == 'm')
17016             ansi = 0;
17017     }
17018     if (dest && appData.debugMode)
17019     {
17020         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17021             count, width, line, len, *lp);
17022         show_bytes(debugFP, src, count);
17023         fprintf(debugFP, "\ndest: ");
17024         show_bytes(debugFP, dest, len);
17025         fprintf(debugFP, "\n");
17026     }
17027     *lp = dest ? line : old_line;
17028
17029     return len;
17030 }
17031
17032 // [HGM] vari: routines for shelving variations
17033 Boolean modeRestore = FALSE;
17034
17035 void
17036 PushInner (int firstMove, int lastMove)
17037 {
17038         int i, j, nrMoves = lastMove - firstMove;
17039
17040         // push current tail of game on stack
17041         savedResult[storedGames] = gameInfo.result;
17042         savedDetails[storedGames] = gameInfo.resultDetails;
17043         gameInfo.resultDetails = NULL;
17044         savedFirst[storedGames] = firstMove;
17045         savedLast [storedGames] = lastMove;
17046         savedFramePtr[storedGames] = framePtr;
17047         framePtr -= nrMoves; // reserve space for the boards
17048         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17049             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17050             for(j=0; j<MOVE_LEN; j++)
17051                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17052             for(j=0; j<2*MOVE_LEN; j++)
17053                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17054             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17055             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17056             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17057             pvInfoList[firstMove+i-1].depth = 0;
17058             commentList[framePtr+i] = commentList[firstMove+i];
17059             commentList[firstMove+i] = NULL;
17060         }
17061
17062         storedGames++;
17063         forwardMostMove = firstMove; // truncate game so we can start variation
17064 }
17065
17066 void
17067 PushTail (int firstMove, int lastMove)
17068 {
17069         if(appData.icsActive) { // only in local mode
17070                 forwardMostMove = currentMove; // mimic old ICS behavior
17071                 return;
17072         }
17073         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17074
17075         PushInner(firstMove, lastMove);
17076         if(storedGames == 1) GreyRevert(FALSE);
17077         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17078 }
17079
17080 void
17081 PopInner (Boolean annotate)
17082 {
17083         int i, j, nrMoves;
17084         char buf[8000], moveBuf[20];
17085
17086         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17087         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17088         nrMoves = savedLast[storedGames] - currentMove;
17089         if(annotate) {
17090                 int cnt = 10;
17091                 if(!WhiteOnMove(currentMove))
17092                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17093                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17094                 for(i=currentMove; i<forwardMostMove; i++) {
17095                         if(WhiteOnMove(i))
17096                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17097                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17098                         strcat(buf, moveBuf);
17099                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17100                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17101                 }
17102                 strcat(buf, ")");
17103         }
17104         for(i=1; i<=nrMoves; i++) { // copy last variation back
17105             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17106             for(j=0; j<MOVE_LEN; j++)
17107                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17108             for(j=0; j<2*MOVE_LEN; j++)
17109                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17110             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17111             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17112             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17113             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17114             commentList[currentMove+i] = commentList[framePtr+i];
17115             commentList[framePtr+i] = NULL;
17116         }
17117         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17118         framePtr = savedFramePtr[storedGames];
17119         gameInfo.result = savedResult[storedGames];
17120         if(gameInfo.resultDetails != NULL) {
17121             free(gameInfo.resultDetails);
17122       }
17123         gameInfo.resultDetails = savedDetails[storedGames];
17124         forwardMostMove = currentMove + nrMoves;
17125 }
17126
17127 Boolean
17128 PopTail (Boolean annotate)
17129 {
17130         if(appData.icsActive) return FALSE; // only in local mode
17131         if(!storedGames) return FALSE; // sanity
17132         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17133
17134         PopInner(annotate);
17135         if(currentMove < forwardMostMove) ForwardEvent(); else
17136         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17137
17138         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17139         return TRUE;
17140 }
17141
17142 void
17143 CleanupTail ()
17144 {       // remove all shelved variations
17145         int i;
17146         for(i=0; i<storedGames; i++) {
17147             if(savedDetails[i])
17148                 free(savedDetails[i]);
17149             savedDetails[i] = NULL;
17150         }
17151         for(i=framePtr; i<MAX_MOVES; i++) {
17152                 if(commentList[i]) free(commentList[i]);
17153                 commentList[i] = NULL;
17154         }
17155         framePtr = MAX_MOVES-1;
17156         storedGames = 0;
17157 }
17158
17159 void
17160 LoadVariation (int index, char *text)
17161 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17162         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17163         int level = 0, move;
17164
17165         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17166         // first find outermost bracketing variation
17167         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17168             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17169                 if(*p == '{') wait = '}'; else
17170                 if(*p == '[') wait = ']'; else
17171                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17172                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17173             }
17174             if(*p == wait) wait = NULLCHAR; // closing ]} found
17175             p++;
17176         }
17177         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17178         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17179         end[1] = NULLCHAR; // clip off comment beyond variation
17180         ToNrEvent(currentMove-1);
17181         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17182         // kludge: use ParsePV() to append variation to game
17183         move = currentMove;
17184         ParsePV(start, TRUE, TRUE);
17185         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17186         ClearPremoveHighlights();
17187         CommentPopDown();
17188         ToNrEvent(currentMove+1);
17189 }
17190