3d65707c465d20d4395990dd58feb178b47130fd
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 void
901 Load (ChessProgramState *cps, int i)
902 {
903     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
904     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
905         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
906         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
907         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
908         appData.firstProtocolVersion = PROTOVER;
909         ParseArgsFromString(buf);
910         SwapEngines(i);
911         ReplaceEngine(cps, i);
912         FloatToFront(&appData.recentEngineList, engineLine);
913         return;
914     }
915     p = engineName;
916     while(q = strchr(p, SLASH)) p = q+1;
917     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
918     if(engineDir[0] != NULLCHAR)
919         appData.directory[i] = engineDir;
920     else if(p != engineName) { // derive directory from engine path, when not given
921         p[-1] = 0;
922         appData.directory[i] = strdup(engineName);
923         p[-1] = SLASH;
924         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
925     } else appData.directory[i] = ".";
926     if(params[0]) {
927         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
928         snprintf(command, MSG_SIZ, "%s %s", p, params);
929         p = command;
930     }
931     appData.chessProgram[i] = strdup(p);
932     appData.isUCI[i] = isUCI;
933     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
934     appData.hasOwnBookUCI[i] = hasBook;
935     if(!nickName[0]) useNick = FALSE;
936     if(useNick) ASSIGN(appData.pgnName[i], nickName);
937     if(addToList) {
938         int len;
939         char quote;
940         q = firstChessProgramNames;
941         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
942         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
943         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
944                         quote, p, quote, appData.directory[i], 
945                         useNick ? " -fn \"" : "",
946                         useNick ? nickName : "",
947                         useNick ? "\"" : "",
948                         v1 ? " -firstProtocolVersion 1" : "",
949                         hasBook ? "" : " -fNoOwnBookUCI",
950                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
951                         storeVariant ? " -variant " : "",
952                         storeVariant ? VariantName(gameInfo.variant) : "");
953         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
954         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
955         if(q)   free(q);
956         FloatToFront(&appData.recentEngineList, buf);
957     }
958     ReplaceEngine(cps, i);
959 }
960
961 void
962 InitTimeControls ()
963 {
964     int matched, min, sec;
965     /*
966      * Parse timeControl resource
967      */
968     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
969                           appData.movesPerSession)) {
970         char buf[MSG_SIZ];
971         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
972         DisplayFatalError(buf, 0, 2);
973     }
974
975     /*
976      * Parse searchTime resource
977      */
978     if (*appData.searchTime != NULLCHAR) {
979         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
980         if (matched == 1) {
981             searchTime = min * 60;
982         } else if (matched == 2) {
983             searchTime = min * 60 + sec;
984         } else {
985             char buf[MSG_SIZ];
986             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
987             DisplayFatalError(buf, 0, 2);
988         }
989     }
990 }
991
992 void
993 InitBackEnd1 ()
994 {
995
996     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
997     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
998
999     GetTimeMark(&programStartTime);
1000     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1001     appData.seedBase = random() + (random()<<15);
1002     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1003
1004     ClearProgramStats();
1005     programStats.ok_to_send = 1;
1006     programStats.seen_stat = 0;
1007
1008     /*
1009      * Initialize game list
1010      */
1011     ListNew(&gameList);
1012
1013
1014     /*
1015      * Internet chess server status
1016      */
1017     if (appData.icsActive) {
1018         appData.matchMode = FALSE;
1019         appData.matchGames = 0;
1020 #if ZIPPY
1021         appData.noChessProgram = !appData.zippyPlay;
1022 #else
1023         appData.zippyPlay = FALSE;
1024         appData.zippyTalk = FALSE;
1025         appData.noChessProgram = TRUE;
1026 #endif
1027         if (*appData.icsHelper != NULLCHAR) {
1028             appData.useTelnet = TRUE;
1029             appData.telnetProgram = appData.icsHelper;
1030         }
1031     } else {
1032         appData.zippyTalk = appData.zippyPlay = FALSE;
1033     }
1034
1035     /* [AS] Initialize pv info list [HGM] and game state */
1036     {
1037         int i, j;
1038
1039         for( i=0; i<=framePtr; i++ ) {
1040             pvInfoList[i].depth = -1;
1041             boards[i][EP_STATUS] = EP_NONE;
1042             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1043         }
1044     }
1045
1046     InitTimeControls();
1047
1048     /* [AS] Adjudication threshold */
1049     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1050
1051     InitEngine(&first, 0);
1052     InitEngine(&second, 1);
1053     CommonEngineInit();
1054
1055     pairing.which = "pairing"; // pairing engine
1056     pairing.pr = NoProc;
1057     pairing.isr = NULL;
1058     pairing.program = appData.pairingEngine;
1059     pairing.host = "localhost";
1060     pairing.dir = ".";
1061
1062     if (appData.icsActive) {
1063         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1064     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1065         appData.clockMode = FALSE;
1066         first.sendTime = second.sendTime = 0;
1067     }
1068
1069 #if ZIPPY
1070     /* Override some settings from environment variables, for backward
1071        compatibility.  Unfortunately it's not feasible to have the env
1072        vars just set defaults, at least in xboard.  Ugh.
1073     */
1074     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1075       ZippyInit();
1076     }
1077 #endif
1078
1079     if (!appData.icsActive) {
1080       char buf[MSG_SIZ];
1081       int len;
1082
1083       /* Check for variants that are supported only in ICS mode,
1084          or not at all.  Some that are accepted here nevertheless
1085          have bugs; see comments below.
1086       */
1087       VariantClass variant = StringToVariant(appData.variant);
1088       switch (variant) {
1089       case VariantBughouse:     /* need four players and two boards */
1090       case VariantKriegspiel:   /* need to hide pieces and move details */
1091         /* case VariantFischeRandom: (Fabien: moved below) */
1092         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1093         if( (len >= MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantUnknown:
1100       case VariantLoadable:
1101       case Variant29:
1102       case Variant30:
1103       case Variant31:
1104       case Variant32:
1105       case Variant33:
1106       case Variant34:
1107       case Variant35:
1108       case Variant36:
1109       default:
1110         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1111         if( (len >= MSG_SIZ) && appData.debugMode )
1112           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113
1114         DisplayFatalError(buf, 0, 2);
1115         return;
1116
1117       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1118       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1119       case VariantGothic:     /* [HGM] should work */
1120       case VariantCapablanca: /* [HGM] should work */
1121       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1122       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1123       case VariantKnightmate: /* [HGM] should work */
1124       case VariantCylinder:   /* [HGM] untested */
1125       case VariantFalcon:     /* [HGM] untested */
1126       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1127                                  offboard interposition not understood */
1128       case VariantNormal:     /* definitely works! */
1129       case VariantWildCastle: /* pieces not automatically shuffled */
1130       case VariantNoCastle:   /* pieces not automatically shuffled */
1131       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1132       case VariantLosers:     /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantSuicide:    /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantGiveaway:   /* should work except for win condition,
1137                                  and doesn't know captures are mandatory */
1138       case VariantTwoKings:   /* should work */
1139       case VariantAtomic:     /* should work except for win condition */
1140       case Variant3Check:     /* should work except for win condition */
1141       case VariantShatranj:   /* should work except for all win conditions */
1142       case VariantMakruk:     /* should work except for draw countdown */
1143       case VariantBerolina:   /* might work if TestLegality is off */
1144       case VariantCapaRandom: /* should work */
1145       case VariantJanus:      /* should work */
1146       case VariantSuper:      /* experimental */
1147       case VariantGreat:      /* experimental, requires legality testing to be off */
1148       case VariantSChess:     /* S-Chess, should work */
1149       case VariantGrand:      /* should work */
1150       case VariantSpartan:    /* should work */
1151         break;
1152       }
1153     }
1154
1155 }
1156
1157 int
1158 NextIntegerFromString (char ** str, long * value)
1159 {
1160     int result = -1;
1161     char * s = *str;
1162
1163     while( *s == ' ' || *s == '\t' ) {
1164         s++;
1165     }
1166
1167     *value = 0;
1168
1169     if( *s >= '0' && *s <= '9' ) {
1170         while( *s >= '0' && *s <= '9' ) {
1171             *value = *value * 10 + (*s - '0');
1172             s++;
1173         }
1174
1175         result = 0;
1176     }
1177
1178     *str = s;
1179
1180     return result;
1181 }
1182
1183 int
1184 NextTimeControlFromString (char ** str, long * value)
1185 {
1186     long temp;
1187     int result = NextIntegerFromString( str, &temp );
1188
1189     if( result == 0 ) {
1190         *value = temp * 60; /* Minutes */
1191         if( **str == ':' ) {
1192             (*str)++;
1193             result = NextIntegerFromString( str, &temp );
1194             *value += temp; /* Seconds */
1195         }
1196     }
1197
1198     return result;
1199 }
1200
1201 int
1202 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1203 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1204     int result = -1, type = 0; long temp, temp2;
1205
1206     if(**str != ':') return -1; // old params remain in force!
1207     (*str)++;
1208     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1209     if( NextIntegerFromString( str, &temp ) ) return -1;
1210     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1211
1212     if(**str != '/') {
1213         /* time only: incremental or sudden-death time control */
1214         if(**str == '+') { /* increment follows; read it */
1215             (*str)++;
1216             if(**str == '!') type = *(*str)++; // Bronstein TC
1217             if(result = NextIntegerFromString( str, &temp2)) return -1;
1218             *inc = temp2 * 1000;
1219             if(**str == '.') { // read fraction of increment
1220                 char *start = ++(*str);
1221                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1222                 temp2 *= 1000;
1223                 while(start++ < *str) temp2 /= 10;
1224                 *inc += temp2;
1225             }
1226         } else *inc = 0;
1227         *moves = 0; *tc = temp * 1000; *incType = type;
1228         return 0;
1229     }
1230
1231     (*str)++; /* classical time control */
1232     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1233
1234     if(result == 0) {
1235         *moves = temp;
1236         *tc    = temp2 * 1000;
1237         *inc   = 0;
1238         *incType = type;
1239     }
1240     return result;
1241 }
1242
1243 int
1244 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1245 {   /* [HGM] get time to add from the multi-session time-control string */
1246     int incType, moves=1; /* kludge to force reading of first session */
1247     long time, increment;
1248     char *s = tcString;
1249
1250     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1251     do {
1252         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1253         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1254         if(movenr == -1) return time;    /* last move before new session     */
1255         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1256         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1257         if(!moves) return increment;     /* current session is incremental   */
1258         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1259     } while(movenr >= -1);               /* try again for next session       */
1260
1261     return 0; // no new time quota on this move
1262 }
1263
1264 int
1265 ParseTimeControl (char *tc, float ti, int mps)
1266 {
1267   long tc1;
1268   long tc2;
1269   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1270   int min, sec=0;
1271
1272   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1273   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1274       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1275   if(ti > 0) {
1276
1277     if(mps)
1278       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1279     else 
1280       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1281   } else {
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s", mytc);
1286   }
1287   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1288   
1289   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1290     return FALSE;
1291   }
1292
1293   if( *tc == '/' ) {
1294     /* Parse second time control */
1295     tc++;
1296
1297     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1298       return FALSE;
1299     }
1300
1301     if( tc2 == 0 ) {
1302       return FALSE;
1303     }
1304
1305     timeControl_2 = tc2 * 1000;
1306   }
1307   else {
1308     timeControl_2 = 0;
1309   }
1310
1311   if( tc1 == 0 ) {
1312     return FALSE;
1313   }
1314
1315   timeControl = tc1 * 1000;
1316
1317   if (ti >= 0) {
1318     timeIncrement = ti * 1000;  /* convert to ms */
1319     movesPerSession = 0;
1320   } else {
1321     timeIncrement = 0;
1322     movesPerSession = mps;
1323   }
1324   return TRUE;
1325 }
1326
1327 void
1328 InitBackEnd2 ()
1329 {
1330     if (appData.debugMode) {
1331         fprintf(debugFP, "%s\n", programVersion);
1332     }
1333     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1334
1335     set_cont_sequence(appData.wrapContSeq);
1336     if (appData.matchGames > 0) {
1337         appData.matchMode = TRUE;
1338     } else if (appData.matchMode) {
1339         appData.matchGames = 1;
1340     }
1341     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1342         appData.matchGames = appData.sameColorGames;
1343     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1344         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1345         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1346     }
1347     Reset(TRUE, FALSE);
1348     if (appData.noChessProgram || first.protocolVersion == 1) {
1349       InitBackEnd3();
1350     } else {
1351       /* kludge: allow timeout for initial "feature" commands */
1352       FreezeUI();
1353       DisplayMessage("", _("Starting chess program"));
1354       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1355     }
1356 }
1357
1358 int
1359 CalculateIndex (int index, int gameNr)
1360 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1361     int res;
1362     if(index > 0) return index; // fixed nmber
1363     if(index == 0) return 1;
1364     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1365     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1366     return res;
1367 }
1368
1369 int
1370 LoadGameOrPosition (int gameNr)
1371 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1372     if (*appData.loadGameFile != NULLCHAR) {
1373         if (!LoadGameFromFile(appData.loadGameFile,
1374                 CalculateIndex(appData.loadGameIndex, gameNr),
1375                               appData.loadGameFile, FALSE)) {
1376             DisplayFatalError(_("Bad game file"), 0, 1);
1377             return 0;
1378         }
1379     } else if (*appData.loadPositionFile != NULLCHAR) {
1380         if (!LoadPositionFromFile(appData.loadPositionFile,
1381                 CalculateIndex(appData.loadPositionIndex, gameNr),
1382                                   appData.loadPositionFile)) {
1383             DisplayFatalError(_("Bad position file"), 0, 1);
1384             return 0;
1385         }
1386     }
1387     return 1;
1388 }
1389
1390 void
1391 ReserveGame (int gameNr, char resChar)
1392 {
1393     FILE *tf = fopen(appData.tourneyFile, "r+");
1394     char *p, *q, c, buf[MSG_SIZ];
1395     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1396     safeStrCpy(buf, lastMsg, MSG_SIZ);
1397     DisplayMessage(_("Pick new game"), "");
1398     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1399     ParseArgsFromFile(tf);
1400     p = q = appData.results;
1401     if(appData.debugMode) {
1402       char *r = appData.participants;
1403       fprintf(debugFP, "results = '%s'\n", p);
1404       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1405       fprintf(debugFP, "\n");
1406     }
1407     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1408     nextGame = q - p;
1409     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1410     safeStrCpy(q, p, strlen(p) + 2);
1411     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1412     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1413     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1414         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1415         q[nextGame] = '*';
1416     }
1417     fseek(tf, -(strlen(p)+4), SEEK_END);
1418     c = fgetc(tf);
1419     if(c != '"') // depending on DOS or Unix line endings we can be one off
1420          fseek(tf, -(strlen(p)+2), SEEK_END);
1421     else fseek(tf, -(strlen(p)+3), SEEK_END);
1422     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1423     DisplayMessage(buf, "");
1424     free(p); appData.results = q;
1425     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1426        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1427       int round = appData.defaultMatchGames * appData.tourneyType;
1428       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1429          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1430         UnloadEngine(&first);  // next game belongs to other pairing;
1431         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1432     }
1433     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1434 }
1435
1436 void
1437 MatchEvent (int mode)
1438 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1439         int dummy;
1440         if(matchMode) { // already in match mode: switch it off
1441             abortMatch = TRUE;
1442             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1443             return;
1444         }
1445 //      if(gameMode != BeginningOfGame) {
1446 //          DisplayError(_("You can only start a match from the initial position."), 0);
1447 //          return;
1448 //      }
1449         abortMatch = FALSE;
1450         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1451         /* Set up machine vs. machine match */
1452         nextGame = 0;
1453         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1454         if(appData.tourneyFile[0]) {
1455             ReserveGame(-1, 0);
1456             if(nextGame > appData.matchGames) {
1457                 char buf[MSG_SIZ];
1458                 if(strchr(appData.results, '*') == NULL) {
1459                     FILE *f;
1460                     appData.tourneyCycles++;
1461                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1462                         fclose(f);
1463                         NextTourneyGame(-1, &dummy);
1464                         ReserveGame(-1, 0);
1465                         if(nextGame <= appData.matchGames) {
1466                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1467                             matchMode = mode;
1468                             ScheduleDelayedEvent(NextMatchGame, 10000);
1469                             return;
1470                         }
1471                     }
1472                 }
1473                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1474                 DisplayError(buf, 0);
1475                 appData.tourneyFile[0] = 0;
1476                 return;
1477             }
1478         } else
1479         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1480             DisplayFatalError(_("Can't have a match with no chess programs"),
1481                               0, 2);
1482             return;
1483         }
1484         matchMode = mode;
1485         matchGame = roundNr = 1;
1486         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1487         NextMatchGame();
1488 }
1489
1490 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1491
1492 void
1493 InitBackEnd3 P((void))
1494 {
1495     GameMode initialMode;
1496     char buf[MSG_SIZ];
1497     int err, len;
1498
1499     InitChessProgram(&first, startedFromSetupPosition);
1500
1501     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1502         free(programVersion);
1503         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1504         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1505         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1506     }
1507
1508     if (appData.icsActive) {
1509 #ifdef WIN32
1510         /* [DM] Make a console window if needed [HGM] merged ifs */
1511         ConsoleCreate();
1512 #endif
1513         err = establish();
1514         if (err != 0)
1515           {
1516             if (*appData.icsCommPort != NULLCHAR)
1517               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1518                              appData.icsCommPort);
1519             else
1520               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1521                         appData.icsHost, appData.icsPort);
1522
1523             if( (len >= MSG_SIZ) && appData.debugMode )
1524               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1525
1526             DisplayFatalError(buf, err, 1);
1527             return;
1528         }
1529         SetICSMode();
1530         telnetISR =
1531           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1532         fromUserISR =
1533           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1534         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1535             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1536     } else if (appData.noChessProgram) {
1537         SetNCPMode();
1538     } else {
1539         SetGNUMode();
1540     }
1541
1542     if (*appData.cmailGameName != NULLCHAR) {
1543         SetCmailMode();
1544         OpenLoopback(&cmailPR);
1545         cmailISR =
1546           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1547     }
1548
1549     ThawUI();
1550     DisplayMessage("", "");
1551     if (StrCaseCmp(appData.initialMode, "") == 0) {
1552       initialMode = BeginningOfGame;
1553       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1554         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1555         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1556         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1557         ModeHighlight();
1558       }
1559     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1560       initialMode = TwoMachinesPlay;
1561     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1562       initialMode = AnalyzeFile;
1563     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1564       initialMode = AnalyzeMode;
1565     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1566       initialMode = MachinePlaysWhite;
1567     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1568       initialMode = MachinePlaysBlack;
1569     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1570       initialMode = EditGame;
1571     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1572       initialMode = EditPosition;
1573     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1574       initialMode = Training;
1575     } else {
1576       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1577       if( (len >= MSG_SIZ) && appData.debugMode )
1578         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1579
1580       DisplayFatalError(buf, 0, 2);
1581       return;
1582     }
1583
1584     if (appData.matchMode) {
1585         if(appData.tourneyFile[0]) { // start tourney from command line
1586             FILE *f;
1587             if(f = fopen(appData.tourneyFile, "r")) {
1588                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1589                 fclose(f);
1590                 appData.clockMode = TRUE;
1591                 SetGNUMode();
1592             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1593         }
1594         MatchEvent(TRUE);
1595     } else if (*appData.cmailGameName != NULLCHAR) {
1596         /* Set up cmail mode */
1597         ReloadCmailMsgEvent(TRUE);
1598     } else {
1599         /* Set up other modes */
1600         if (initialMode == AnalyzeFile) {
1601           if (*appData.loadGameFile == NULLCHAR) {
1602             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1603             return;
1604           }
1605         }
1606         if (*appData.loadGameFile != NULLCHAR) {
1607             (void) LoadGameFromFile(appData.loadGameFile,
1608                                     appData.loadGameIndex,
1609                                     appData.loadGameFile, TRUE);
1610         } else if (*appData.loadPositionFile != NULLCHAR) {
1611             (void) LoadPositionFromFile(appData.loadPositionFile,
1612                                         appData.loadPositionIndex,
1613                                         appData.loadPositionFile);
1614             /* [HGM] try to make self-starting even after FEN load */
1615             /* to allow automatic setup of fairy variants with wtm */
1616             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1617                 gameMode = BeginningOfGame;
1618                 setboardSpoiledMachineBlack = 1;
1619             }
1620             /* [HGM] loadPos: make that every new game uses the setup */
1621             /* from file as long as we do not switch variant          */
1622             if(!blackPlaysFirst) {
1623                 startedFromPositionFile = TRUE;
1624                 CopyBoard(filePosition, boards[0]);
1625             }
1626         }
1627         if (initialMode == AnalyzeMode) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1630             return;
1631           }
1632           if (appData.icsActive) {
1633             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1634             return;
1635           }
1636           AnalyzeModeEvent();
1637         } else if (initialMode == AnalyzeFile) {
1638           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1639           ShowThinkingEvent();
1640           AnalyzeFileEvent();
1641           AnalysisPeriodicEvent(1);
1642         } else if (initialMode == MachinePlaysWhite) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           MachineWhiteEvent();
1654         } else if (initialMode == MachinePlaysBlack) {
1655           if (appData.noChessProgram) {
1656             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1657                               0, 2);
1658             return;
1659           }
1660           if (appData.icsActive) {
1661             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1662                               0, 2);
1663             return;
1664           }
1665           MachineBlackEvent();
1666         } else if (initialMode == TwoMachinesPlay) {
1667           if (appData.noChessProgram) {
1668             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1669                               0, 2);
1670             return;
1671           }
1672           if (appData.icsActive) {
1673             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1674                               0, 2);
1675             return;
1676           }
1677           TwoMachinesEvent();
1678         } else if (initialMode == EditGame) {
1679           EditGameEvent();
1680         } else if (initialMode == EditPosition) {
1681           EditPositionEvent();
1682         } else if (initialMode == Training) {
1683           if (*appData.loadGameFile == NULLCHAR) {
1684             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1685             return;
1686           }
1687           TrainingEvent();
1688         }
1689     }
1690 }
1691
1692 void
1693 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1694 {
1695     DisplayBook(current+1);
1696
1697     MoveHistorySet( movelist, first, last, current, pvInfoList );
1698
1699     EvalGraphSet( first, last, current, pvInfoList );
1700
1701     MakeEngineOutputTitle();
1702 }
1703
1704 /*
1705  * Establish will establish a contact to a remote host.port.
1706  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1707  *  used to talk to the host.
1708  * Returns 0 if okay, error code if not.
1709  */
1710 int
1711 establish ()
1712 {
1713     char buf[MSG_SIZ];
1714
1715     if (*appData.icsCommPort != NULLCHAR) {
1716         /* Talk to the host through a serial comm port */
1717         return OpenCommPort(appData.icsCommPort, &icsPR);
1718
1719     } else if (*appData.gateway != NULLCHAR) {
1720         if (*appData.remoteShell == NULLCHAR) {
1721             /* Use the rcmd protocol to run telnet program on a gateway host */
1722             snprintf(buf, sizeof(buf), "%s %s %s",
1723                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1724             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1725
1726         } else {
1727             /* Use the rsh program to run telnet program on a gateway host */
1728             if (*appData.remoteUser == NULLCHAR) {
1729                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1730                         appData.gateway, appData.telnetProgram,
1731                         appData.icsHost, appData.icsPort);
1732             } else {
1733                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1734                         appData.remoteShell, appData.gateway,
1735                         appData.remoteUser, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             }
1738             return StartChildProcess(buf, "", &icsPR);
1739
1740         }
1741     } else if (appData.useTelnet) {
1742         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1743
1744     } else {
1745         /* TCP socket interface differs somewhat between
1746            Unix and NT; handle details in the front end.
1747            */
1748         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1749     }
1750 }
1751
1752 void
1753 EscapeExpand (char *p, char *q)
1754 {       // [HGM] initstring: routine to shape up string arguments
1755         while(*p++ = *q++) if(p[-1] == '\\')
1756             switch(*q++) {
1757                 case 'n': p[-1] = '\n'; break;
1758                 case 'r': p[-1] = '\r'; break;
1759                 case 't': p[-1] = '\t'; break;
1760                 case '\\': p[-1] = '\\'; break;
1761                 case 0: *p = 0; return;
1762                 default: p[-1] = q[-1]; break;
1763             }
1764 }
1765
1766 void
1767 show_bytes (FILE *fp, char *buf, int count)
1768 {
1769     while (count--) {
1770         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1771             fprintf(fp, "\\%03o", *buf & 0xff);
1772         } else {
1773             putc(*buf, fp);
1774         }
1775         buf++;
1776     }
1777     fflush(fp);
1778 }
1779
1780 /* Returns an errno value */
1781 int
1782 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1783 {
1784     char buf[8192], *p, *q, *buflim;
1785     int left, newcount, outcount;
1786
1787     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1788         *appData.gateway != NULLCHAR) {
1789         if (appData.debugMode) {
1790             fprintf(debugFP, ">ICS: ");
1791             show_bytes(debugFP, message, count);
1792             fprintf(debugFP, "\n");
1793         }
1794         return OutputToProcess(pr, message, count, outError);
1795     }
1796
1797     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1798     p = message;
1799     q = buf;
1800     left = count;
1801     newcount = 0;
1802     while (left) {
1803         if (q >= buflim) {
1804             if (appData.debugMode) {
1805                 fprintf(debugFP, ">ICS: ");
1806                 show_bytes(debugFP, buf, newcount);
1807                 fprintf(debugFP, "\n");
1808             }
1809             outcount = OutputToProcess(pr, buf, newcount, outError);
1810             if (outcount < newcount) return -1; /* to be sure */
1811             q = buf;
1812             newcount = 0;
1813         }
1814         if (*p == '\n') {
1815             *q++ = '\r';
1816             newcount++;
1817         } else if (((unsigned char) *p) == TN_IAC) {
1818             *q++ = (char) TN_IAC;
1819             newcount ++;
1820         }
1821         *q++ = *p++;
1822         newcount++;
1823         left--;
1824     }
1825     if (appData.debugMode) {
1826         fprintf(debugFP, ">ICS: ");
1827         show_bytes(debugFP, buf, newcount);
1828         fprintf(debugFP, "\n");
1829     }
1830     outcount = OutputToProcess(pr, buf, newcount, outError);
1831     if (outcount < newcount) return -1; /* to be sure */
1832     return count;
1833 }
1834
1835 void
1836 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1837 {
1838     int outError, outCount;
1839     static int gotEof = 0;
1840
1841     /* Pass data read from player on to ICS */
1842     if (count > 0) {
1843         gotEof = 0;
1844         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1845         if (outCount < count) {
1846             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1847         }
1848     } else if (count < 0) {
1849         RemoveInputSource(isr);
1850         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1851     } else if (gotEof++ > 0) {
1852         RemoveInputSource(isr);
1853         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1854     }
1855 }
1856
1857 void
1858 KeepAlive ()
1859 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1860     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1861     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1862     SendToICS("date\n");
1863     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1864 }
1865
1866 /* added routine for printf style output to ics */
1867 void
1868 ics_printf (char *format, ...)
1869 {
1870     char buffer[MSG_SIZ];
1871     va_list args;
1872
1873     va_start(args, format);
1874     vsnprintf(buffer, sizeof(buffer), format, args);
1875     buffer[sizeof(buffer)-1] = '\0';
1876     SendToICS(buffer);
1877     va_end(args);
1878 }
1879
1880 void
1881 SendToICS (char *s)
1882 {
1883     int count, outCount, outError;
1884
1885     if (icsPR == NoProc) return;
1886
1887     count = strlen(s);
1888     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894 /* This is used for sending logon scripts to the ICS. Sending
1895    without a delay causes problems when using timestamp on ICC
1896    (at least on my machine). */
1897 void
1898 SendToICSDelayed (char *s, long msdelay)
1899 {
1900     int count, outCount, outError;
1901
1902     if (icsPR == NoProc) return;
1903
1904     count = strlen(s);
1905     if (appData.debugMode) {
1906         fprintf(debugFP, ">ICS: ");
1907         show_bytes(debugFP, s, count);
1908         fprintf(debugFP, "\n");
1909     }
1910     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1911                                       msdelay);
1912     if (outCount < count) {
1913         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1914     }
1915 }
1916
1917
1918 /* Remove all highlighting escape sequences in s
1919    Also deletes any suffix starting with '('
1920    */
1921 char *
1922 StripHighlightAndTitle (char *s)
1923 {
1924     static char retbuf[MSG_SIZ];
1925     char *p = retbuf;
1926
1927     while (*s != NULLCHAR) {
1928         while (*s == '\033') {
1929             while (*s != NULLCHAR && !isalpha(*s)) s++;
1930             if (*s != NULLCHAR) s++;
1931         }
1932         while (*s != NULLCHAR && *s != '\033') {
1933             if (*s == '(' || *s == '[') {
1934                 *p = NULLCHAR;
1935                 return retbuf;
1936             }
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 /* Remove all highlighting escape sequences in s */
1945 char *
1946 StripHighlight (char *s)
1947 {
1948     static char retbuf[MSG_SIZ];
1949     char *p = retbuf;
1950
1951     while (*s != NULLCHAR) {
1952         while (*s == '\033') {
1953             while (*s != NULLCHAR && !isalpha(*s)) s++;
1954             if (*s != NULLCHAR) s++;
1955         }
1956         while (*s != NULLCHAR && *s != '\033') {
1957             *p++ = *s++;
1958         }
1959     }
1960     *p = NULLCHAR;
1961     return retbuf;
1962 }
1963
1964 char *variantNames[] = VARIANT_NAMES;
1965 char *
1966 VariantName (VariantClass v)
1967 {
1968     return variantNames[v];
1969 }
1970
1971
1972 /* Identify a variant from the strings the chess servers use or the
1973    PGN Variant tag names we use. */
1974 VariantClass
1975 StringToVariant (char *e)
1976 {
1977     char *p;
1978     int wnum = -1;
1979     VariantClass v = VariantNormal;
1980     int i, found = FALSE;
1981     char buf[MSG_SIZ];
1982     int len;
1983
1984     if (!e) return v;
1985
1986     /* [HGM] skip over optional board-size prefixes */
1987     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1988         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1989         while( *e++ != '_');
1990     }
1991
1992     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1993         v = VariantNormal;
1994         found = TRUE;
1995     } else
1996     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1997       if (StrCaseStr(e, variantNames[i])) {
1998         v = (VariantClass) i;
1999         found = TRUE;
2000         break;
2001       }
2002     }
2003
2004     if (!found) {
2005       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2006           || StrCaseStr(e, "wild/fr")
2007           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2008         v = VariantFischeRandom;
2009       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2010                  (i = 1, p = StrCaseStr(e, "w"))) {
2011         p += i;
2012         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2013         if (isdigit(*p)) {
2014           wnum = atoi(p);
2015         } else {
2016           wnum = -1;
2017         }
2018         switch (wnum) {
2019         case 0: /* FICS only, actually */
2020         case 1:
2021           /* Castling legal even if K starts on d-file */
2022           v = VariantWildCastle;
2023           break;
2024         case 2:
2025         case 3:
2026         case 4:
2027           /* Castling illegal even if K & R happen to start in
2028              normal positions. */
2029           v = VariantNoCastle;
2030           break;
2031         case 5:
2032         case 7:
2033         case 8:
2034         case 10:
2035         case 11:
2036         case 12:
2037         case 13:
2038         case 14:
2039         case 15:
2040         case 18:
2041         case 19:
2042           /* Castling legal iff K & R start in normal positions */
2043           v = VariantNormal;
2044           break;
2045         case 6:
2046         case 20:
2047         case 21:
2048           /* Special wilds for position setup; unclear what to do here */
2049           v = VariantLoadable;
2050           break;
2051         case 9:
2052           /* Bizarre ICC game */
2053           v = VariantTwoKings;
2054           break;
2055         case 16:
2056           v = VariantKriegspiel;
2057           break;
2058         case 17:
2059           v = VariantLosers;
2060           break;
2061         case 22:
2062           v = VariantFischeRandom;
2063           break;
2064         case 23:
2065           v = VariantCrazyhouse;
2066           break;
2067         case 24:
2068           v = VariantBughouse;
2069           break;
2070         case 25:
2071           v = Variant3Check;
2072           break;
2073         case 26:
2074           /* Not quite the same as FICS suicide! */
2075           v = VariantGiveaway;
2076           break;
2077         case 27:
2078           v = VariantAtomic;
2079           break;
2080         case 28:
2081           v = VariantShatranj;
2082           break;
2083
2084         /* Temporary names for future ICC types.  The name *will* change in
2085            the next xboard/WinBoard release after ICC defines it. */
2086         case 29:
2087           v = Variant29;
2088           break;
2089         case 30:
2090           v = Variant30;
2091           break;
2092         case 31:
2093           v = Variant31;
2094           break;
2095         case 32:
2096           v = Variant32;
2097           break;
2098         case 33:
2099           v = Variant33;
2100           break;
2101         case 34:
2102           v = Variant34;
2103           break;
2104         case 35:
2105           v = Variant35;
2106           break;
2107         case 36:
2108           v = Variant36;
2109           break;
2110         case 37:
2111           v = VariantShogi;
2112           break;
2113         case 38:
2114           v = VariantXiangqi;
2115           break;
2116         case 39:
2117           v = VariantCourier;
2118           break;
2119         case 40:
2120           v = VariantGothic;
2121           break;
2122         case 41:
2123           v = VariantCapablanca;
2124           break;
2125         case 42:
2126           v = VariantKnightmate;
2127           break;
2128         case 43:
2129           v = VariantFairy;
2130           break;
2131         case 44:
2132           v = VariantCylinder;
2133           break;
2134         case 45:
2135           v = VariantFalcon;
2136           break;
2137         case 46:
2138           v = VariantCapaRandom;
2139           break;
2140         case 47:
2141           v = VariantBerolina;
2142           break;
2143         case 48:
2144           v = VariantJanus;
2145           break;
2146         case 49:
2147           v = VariantSuper;
2148           break;
2149         case 50:
2150           v = VariantGreat;
2151           break;
2152         case -1:
2153           /* Found "wild" or "w" in the string but no number;
2154              must assume it's normal chess. */
2155           v = VariantNormal;
2156           break;
2157         default:
2158           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2159           if( (len >= MSG_SIZ) && appData.debugMode )
2160             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2161
2162           DisplayError(buf, 0);
2163           v = VariantUnknown;
2164           break;
2165         }
2166       }
2167     }
2168     if (appData.debugMode) {
2169       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2170               e, wnum, VariantName(v));
2171     }
2172     return v;
2173 }
2174
2175 static int leftover_start = 0, leftover_len = 0;
2176 char star_match[STAR_MATCH_N][MSG_SIZ];
2177
2178 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2179    advance *index beyond it, and set leftover_start to the new value of
2180    *index; else return FALSE.  If pattern contains the character '*', it
2181    matches any sequence of characters not containing '\r', '\n', or the
2182    character following the '*' (if any), and the matched sequence(s) are
2183    copied into star_match.
2184    */
2185 int
2186 looking_at ( char *buf, int *index, char *pattern)
2187 {
2188     char *bufp = &buf[*index], *patternp = pattern;
2189     int star_count = 0;
2190     char *matchp = star_match[0];
2191
2192     for (;;) {
2193         if (*patternp == NULLCHAR) {
2194             *index = leftover_start = bufp - buf;
2195             *matchp = NULLCHAR;
2196             return TRUE;
2197         }
2198         if (*bufp == NULLCHAR) return FALSE;
2199         if (*patternp == '*') {
2200             if (*bufp == *(patternp + 1)) {
2201                 *matchp = NULLCHAR;
2202                 matchp = star_match[++star_count];
2203                 patternp += 2;
2204                 bufp++;
2205                 continue;
2206             } else if (*bufp == '\n' || *bufp == '\r') {
2207                 patternp++;
2208                 if (*patternp == NULLCHAR)
2209                   continue;
2210                 else
2211                   return FALSE;
2212             } else {
2213                 *matchp++ = *bufp++;
2214                 continue;
2215             }
2216         }
2217         if (*patternp != *bufp) return FALSE;
2218         patternp++;
2219         bufp++;
2220     }
2221 }
2222
2223 void
2224 SendToPlayer (char *data, int length)
2225 {
2226     int error, outCount;
2227     outCount = OutputToProcess(NoProc, data, length, &error);
2228     if (outCount < length) {
2229         DisplayFatalError(_("Error writing to display"), error, 1);
2230     }
2231 }
2232
2233 void
2234 PackHolding (char packed[], char *holding)
2235 {
2236     char *p = holding;
2237     char *q = packed;
2238     int runlength = 0;
2239     int curr = 9999;
2240     do {
2241         if (*p == curr) {
2242             runlength++;
2243         } else {
2244             switch (runlength) {
2245               case 0:
2246                 break;
2247               case 1:
2248                 *q++ = curr;
2249                 break;
2250               case 2:
2251                 *q++ = curr;
2252                 *q++ = curr;
2253                 break;
2254               default:
2255                 sprintf(q, "%d", runlength);
2256                 while (*q) q++;
2257                 *q++ = curr;
2258                 break;
2259             }
2260             runlength = 1;
2261             curr = *p;
2262         }
2263     } while (*p++);
2264     *q = NULLCHAR;
2265 }
2266
2267 /* Telnet protocol requests from the front end */
2268 void
2269 TelnetRequest (unsigned char ddww, unsigned char option)
2270 {
2271     unsigned char msg[3];
2272     int outCount, outError;
2273
2274     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2275
2276     if (appData.debugMode) {
2277         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2278         switch (ddww) {
2279           case TN_DO:
2280             ddwwStr = "DO";
2281             break;
2282           case TN_DONT:
2283             ddwwStr = "DONT";
2284             break;
2285           case TN_WILL:
2286             ddwwStr = "WILL";
2287             break;
2288           case TN_WONT:
2289             ddwwStr = "WONT";
2290             break;
2291           default:
2292             ddwwStr = buf1;
2293             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2294             break;
2295         }
2296         switch (option) {
2297           case TN_ECHO:
2298             optionStr = "ECHO";
2299             break;
2300           default:
2301             optionStr = buf2;
2302             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2303             break;
2304         }
2305         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2306     }
2307     msg[0] = TN_IAC;
2308     msg[1] = ddww;
2309     msg[2] = option;
2310     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2311     if (outCount < 3) {
2312         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2313     }
2314 }
2315
2316 void
2317 DoEcho ()
2318 {
2319     if (!appData.icsActive) return;
2320     TelnetRequest(TN_DO, TN_ECHO);
2321 }
2322
2323 void
2324 DontEcho ()
2325 {
2326     if (!appData.icsActive) return;
2327     TelnetRequest(TN_DONT, TN_ECHO);
2328 }
2329
2330 void
2331 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2332 {
2333     /* put the holdings sent to us by the server on the board holdings area */
2334     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2335     char p;
2336     ChessSquare piece;
2337
2338     if(gameInfo.holdingsWidth < 2)  return;
2339     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2340         return; // prevent overwriting by pre-board holdings
2341
2342     if( (int)lowestPiece >= BlackPawn ) {
2343         holdingsColumn = 0;
2344         countsColumn = 1;
2345         holdingsStartRow = BOARD_HEIGHT-1;
2346         direction = -1;
2347     } else {
2348         holdingsColumn = BOARD_WIDTH-1;
2349         countsColumn = BOARD_WIDTH-2;
2350         holdingsStartRow = 0;
2351         direction = 1;
2352     }
2353
2354     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2355         board[i][holdingsColumn] = EmptySquare;
2356         board[i][countsColumn]   = (ChessSquare) 0;
2357     }
2358     while( (p=*holdings++) != NULLCHAR ) {
2359         piece = CharToPiece( ToUpper(p) );
2360         if(piece == EmptySquare) continue;
2361         /*j = (int) piece - (int) WhitePawn;*/
2362         j = PieceToNumber(piece);
2363         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2364         if(j < 0) continue;               /* should not happen */
2365         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2366         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2367         board[holdingsStartRow+j*direction][countsColumn]++;
2368     }
2369 }
2370
2371
2372 void
2373 VariantSwitch (Board board, VariantClass newVariant)
2374 {
2375    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2376    static Board oldBoard;
2377
2378    startedFromPositionFile = FALSE;
2379    if(gameInfo.variant == newVariant) return;
2380
2381    /* [HGM] This routine is called each time an assignment is made to
2382     * gameInfo.variant during a game, to make sure the board sizes
2383     * are set to match the new variant. If that means adding or deleting
2384     * holdings, we shift the playing board accordingly
2385     * This kludge is needed because in ICS observe mode, we get boards
2386     * of an ongoing game without knowing the variant, and learn about the
2387     * latter only later. This can be because of the move list we requested,
2388     * in which case the game history is refilled from the beginning anyway,
2389     * but also when receiving holdings of a crazyhouse game. In the latter
2390     * case we want to add those holdings to the already received position.
2391     */
2392
2393
2394    if (appData.debugMode) {
2395      fprintf(debugFP, "Switch board from %s to %s\n",
2396              VariantName(gameInfo.variant), VariantName(newVariant));
2397      setbuf(debugFP, NULL);
2398    }
2399    shuffleOpenings = 0;       /* [HGM] shuffle */
2400    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2401    switch(newVariant)
2402      {
2403      case VariantShogi:
2404        newWidth = 9;  newHeight = 9;
2405        gameInfo.holdingsSize = 7;
2406      case VariantBughouse:
2407      case VariantCrazyhouse:
2408        newHoldingsWidth = 2; break;
2409      case VariantGreat:
2410        newWidth = 10;
2411      case VariantSuper:
2412        newHoldingsWidth = 2;
2413        gameInfo.holdingsSize = 8;
2414        break;
2415      case VariantGothic:
2416      case VariantCapablanca:
2417      case VariantCapaRandom:
2418        newWidth = 10;
2419      default:
2420        newHoldingsWidth = gameInfo.holdingsSize = 0;
2421      };
2422
2423    if(newWidth  != gameInfo.boardWidth  ||
2424       newHeight != gameInfo.boardHeight ||
2425       newHoldingsWidth != gameInfo.holdingsWidth ) {
2426
2427      /* shift position to new playing area, if needed */
2428      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2429        for(i=0; i<BOARD_HEIGHT; i++)
2430          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2431            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2432              board[i][j];
2433        for(i=0; i<newHeight; i++) {
2434          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2435          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2436        }
2437      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2438        for(i=0; i<BOARD_HEIGHT; i++)
2439          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2440            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2441              board[i][j];
2442      }
2443      gameInfo.boardWidth  = newWidth;
2444      gameInfo.boardHeight = newHeight;
2445      gameInfo.holdingsWidth = newHoldingsWidth;
2446      gameInfo.variant = newVariant;
2447      InitDrawingSizes(-2, 0);
2448    } else gameInfo.variant = newVariant;
2449    CopyBoard(oldBoard, board);   // remember correctly formatted board
2450      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2451    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2452 }
2453
2454 static int loggedOn = FALSE;
2455
2456 /*-- Game start info cache: --*/
2457 int gs_gamenum;
2458 char gs_kind[MSG_SIZ];
2459 static char player1Name[128] = "";
2460 static char player2Name[128] = "";
2461 static char cont_seq[] = "\n\\   ";
2462 static int player1Rating = -1;
2463 static int player2Rating = -1;
2464 /*----------------------------*/
2465
2466 ColorClass curColor = ColorNormal;
2467 int suppressKibitz = 0;
2468
2469 // [HGM] seekgraph
2470 Boolean soughtPending = FALSE;
2471 Boolean seekGraphUp;
2472 #define MAX_SEEK_ADS 200
2473 #define SQUARE 0x80
2474 char *seekAdList[MAX_SEEK_ADS];
2475 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2476 float tcList[MAX_SEEK_ADS];
2477 char colorList[MAX_SEEK_ADS];
2478 int nrOfSeekAds = 0;
2479 int minRating = 1010, maxRating = 2800;
2480 int hMargin = 10, vMargin = 20, h, w;
2481 extern int squareSize, lineGap;
2482
2483 void
2484 PlotSeekAd (int i)
2485 {
2486         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2487         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2488         if(r < minRating+100 && r >=0 ) r = minRating+100;
2489         if(r > maxRating) r = maxRating;
2490         if(tc < 1.) tc = 1.;
2491         if(tc > 95.) tc = 95.;
2492         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2493         y = ((double)r - minRating)/(maxRating - minRating)
2494             * (h-vMargin-squareSize/8-1) + vMargin;
2495         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2496         if(strstr(seekAdList[i], " u ")) color = 1;
2497         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2498            !strstr(seekAdList[i], "bullet") &&
2499            !strstr(seekAdList[i], "blitz") &&
2500            !strstr(seekAdList[i], "standard") ) color = 2;
2501         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2502         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2503 }
2504
2505 void
2506 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2507 {
2508         char buf[MSG_SIZ], *ext = "";
2509         VariantClass v = StringToVariant(type);
2510         if(strstr(type, "wild")) {
2511             ext = type + 4; // append wild number
2512             if(v == VariantFischeRandom) type = "chess960"; else
2513             if(v == VariantLoadable) type = "setup"; else
2514             type = VariantName(v);
2515         }
2516         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2517         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2518             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2519             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2520             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2521             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2522             seekNrList[nrOfSeekAds] = nr;
2523             zList[nrOfSeekAds] = 0;
2524             seekAdList[nrOfSeekAds++] = StrSave(buf);
2525             if(plot) PlotSeekAd(nrOfSeekAds-1);
2526         }
2527 }
2528
2529 void
2530 EraseSeekDot (int i)
2531 {
2532     int x = xList[i], y = yList[i], d=squareSize/4, k;
2533     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2534     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2535     // now replot every dot that overlapped
2536     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2537         int xx = xList[k], yy = yList[k];
2538         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2539             DrawSeekDot(xx, yy, colorList[k]);
2540     }
2541 }
2542
2543 void
2544 RemoveSeekAd (int nr)
2545 {
2546         int i;
2547         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2548             EraseSeekDot(i);
2549             if(seekAdList[i]) free(seekAdList[i]);
2550             seekAdList[i] = seekAdList[--nrOfSeekAds];
2551             seekNrList[i] = seekNrList[nrOfSeekAds];
2552             ratingList[i] = ratingList[nrOfSeekAds];
2553             colorList[i]  = colorList[nrOfSeekAds];
2554             tcList[i] = tcList[nrOfSeekAds];
2555             xList[i]  = xList[nrOfSeekAds];
2556             yList[i]  = yList[nrOfSeekAds];
2557             zList[i]  = zList[nrOfSeekAds];
2558             seekAdList[nrOfSeekAds] = NULL;
2559             break;
2560         }
2561 }
2562
2563 Boolean
2564 MatchSoughtLine (char *line)
2565 {
2566     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2567     int nr, base, inc, u=0; char dummy;
2568
2569     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2571        (u=1) &&
2572        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2573         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2574         // match: compact and save the line
2575         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2576         return TRUE;
2577     }
2578     return FALSE;
2579 }
2580
2581 int
2582 DrawSeekGraph ()
2583 {
2584     int i;
2585     if(!seekGraphUp) return FALSE;
2586     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2587     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2588
2589     DrawSeekBackground(0, 0, w, h);
2590     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2591     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2592     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2593         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2594         yy = h-1-yy;
2595         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2596         if(i%500 == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2600         }
2601     }
2602     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2603     for(i=1; i<100; i+=(i<10?1:5)) {
2604         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2605         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2606         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2607             char buf[MSG_SIZ];
2608             snprintf(buf, MSG_SIZ, "%d", i);
2609             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2610         }
2611     }
2612     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2613     return TRUE;
2614 }
2615
2616 int
2617 SeekGraphClick (ClickType click, int x, int y, int moving)
2618 {
2619     static int lastDown = 0, displayed = 0, lastSecond;
2620     if(y < 0) return FALSE;
2621     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2622         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2623         if(!seekGraphUp) return FALSE;
2624         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2625         DrawPosition(TRUE, NULL);
2626         return TRUE;
2627     }
2628     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2629         if(click == Release || moving) return FALSE;
2630         nrOfSeekAds = 0;
2631         soughtPending = TRUE;
2632         SendToICS(ics_prefix);
2633         SendToICS("sought\n"); // should this be "sought all"?
2634     } else { // issue challenge based on clicked ad
2635         int dist = 10000; int i, closest = 0, second = 0;
2636         for(i=0; i<nrOfSeekAds; i++) {
2637             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2638             if(d < dist) { dist = d; closest = i; }
2639             second += (d - zList[i] < 120); // count in-range ads
2640             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2641         }
2642         if(dist < 120) {
2643             char buf[MSG_SIZ];
2644             second = (second > 1);
2645             if(displayed != closest || second != lastSecond) {
2646                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2647                 lastSecond = second; displayed = closest;
2648             }
2649             if(click == Press) {
2650                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2651                 lastDown = closest;
2652                 return TRUE;
2653             } // on press 'hit', only show info
2654             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2655             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2656             SendToICS(ics_prefix);
2657             SendToICS(buf);
2658             return TRUE; // let incoming board of started game pop down the graph
2659         } else if(click == Release) { // release 'miss' is ignored
2660             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2661             if(moving == 2) { // right up-click
2662                 nrOfSeekAds = 0; // refresh graph
2663                 soughtPending = TRUE;
2664                 SendToICS(ics_prefix);
2665                 SendToICS("sought\n"); // should this be "sought all"?
2666             }
2667             return TRUE;
2668         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2669         // press miss or release hit 'pop down' seek graph
2670         seekGraphUp = FALSE;
2671         DrawPosition(TRUE, NULL);
2672     }
2673     return TRUE;
2674 }
2675
2676 void
2677 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2678 {
2679 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2680 #define STARTED_NONE 0
2681 #define STARTED_MOVES 1
2682 #define STARTED_BOARD 2
2683 #define STARTED_OBSERVE 3
2684 #define STARTED_HOLDINGS 4
2685 #define STARTED_CHATTER 5
2686 #define STARTED_COMMENT 6
2687 #define STARTED_MOVES_NOHIDE 7
2688
2689     static int started = STARTED_NONE;
2690     static char parse[20000];
2691     static int parse_pos = 0;
2692     static char buf[BUF_SIZE + 1];
2693     static int firstTime = TRUE, intfSet = FALSE;
2694     static ColorClass prevColor = ColorNormal;
2695     static int savingComment = FALSE;
2696     static int cmatch = 0; // continuation sequence match
2697     char *bp;
2698     char str[MSG_SIZ];
2699     int i, oldi;
2700     int buf_len;
2701     int next_out;
2702     int tkind;
2703     int backup;    /* [DM] For zippy color lines */
2704     char *p;
2705     char talker[MSG_SIZ]; // [HGM] chat
2706     int channel;
2707
2708     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2709
2710     if (appData.debugMode) {
2711       if (!error) {
2712         fprintf(debugFP, "<ICS: ");
2713         show_bytes(debugFP, data, count);
2714         fprintf(debugFP, "\n");
2715       }
2716     }
2717
2718     if (appData.debugMode) { int f = forwardMostMove;
2719         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2720                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2721                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2722     }
2723     if (count > 0) {
2724         /* If last read ended with a partial line that we couldn't parse,
2725            prepend it to the new read and try again. */
2726         if (leftover_len > 0) {
2727             for (i=0; i<leftover_len; i++)
2728               buf[i] = buf[leftover_start + i];
2729         }
2730
2731     /* copy new characters into the buffer */
2732     bp = buf + leftover_len;
2733     buf_len=leftover_len;
2734     for (i=0; i<count; i++)
2735     {
2736         // ignore these
2737         if (data[i] == '\r')
2738             continue;
2739
2740         // join lines split by ICS?
2741         if (!appData.noJoin)
2742         {
2743             /*
2744                 Joining just consists of finding matches against the
2745                 continuation sequence, and discarding that sequence
2746                 if found instead of copying it.  So, until a match
2747                 fails, there's nothing to do since it might be the
2748                 complete sequence, and thus, something we don't want
2749                 copied.
2750             */
2751             if (data[i] == cont_seq[cmatch])
2752             {
2753                 cmatch++;
2754                 if (cmatch == strlen(cont_seq))
2755                 {
2756                     cmatch = 0; // complete match.  just reset the counter
2757
2758                     /*
2759                         it's possible for the ICS to not include the space
2760                         at the end of the last word, making our [correct]
2761                         join operation fuse two separate words.  the server
2762                         does this when the space occurs at the width setting.
2763                     */
2764                     if (!buf_len || buf[buf_len-1] != ' ')
2765                     {
2766                         *bp++ = ' ';
2767                         buf_len++;
2768                     }
2769                 }
2770                 continue;
2771             }
2772             else if (cmatch)
2773             {
2774                 /*
2775                     match failed, so we have to copy what matched before
2776                     falling through and copying this character.  In reality,
2777                     this will only ever be just the newline character, but
2778                     it doesn't hurt to be precise.
2779                 */
2780                 strncpy(bp, cont_seq, cmatch);
2781                 bp += cmatch;
2782                 buf_len += cmatch;
2783                 cmatch = 0;
2784             }
2785         }
2786
2787         // copy this char
2788         *bp++ = data[i];
2789         buf_len++;
2790     }
2791
2792         buf[buf_len] = NULLCHAR;
2793 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2794         next_out = 0;
2795         leftover_start = 0;
2796
2797         i = 0;
2798         while (i < buf_len) {
2799             /* Deal with part of the TELNET option negotiation
2800                protocol.  We refuse to do anything beyond the
2801                defaults, except that we allow the WILL ECHO option,
2802                which ICS uses to turn off password echoing when we are
2803                directly connected to it.  We reject this option
2804                if localLineEditing mode is on (always on in xboard)
2805                and we are talking to port 23, which might be a real
2806                telnet server that will try to keep WILL ECHO on permanently.
2807              */
2808             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2809                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2810                 unsigned char option;
2811                 oldi = i;
2812                 switch ((unsigned char) buf[++i]) {
2813                   case TN_WILL:
2814                     if (appData.debugMode)
2815                       fprintf(debugFP, "\n<WILL ");
2816                     switch (option = (unsigned char) buf[++i]) {
2817                       case TN_ECHO:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "ECHO ");
2820                         /* Reply only if this is a change, according
2821                            to the protocol rules. */
2822                         if (remoteEchoOption) break;
2823                         if (appData.localLineEditing &&
2824                             atoi(appData.icsPort) == TN_PORT) {
2825                             TelnetRequest(TN_DONT, TN_ECHO);
2826                         } else {
2827                             EchoOff();
2828                             TelnetRequest(TN_DO, TN_ECHO);
2829                             remoteEchoOption = TRUE;
2830                         }
2831                         break;
2832                       default:
2833                         if (appData.debugMode)
2834                           fprintf(debugFP, "%d ", option);
2835                         /* Whatever this is, we don't want it. */
2836                         TelnetRequest(TN_DONT, option);
2837                         break;
2838                     }
2839                     break;
2840                   case TN_WONT:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<WONT ");
2843                     switch (option = (unsigned char) buf[++i]) {
2844                       case TN_ECHO:
2845                         if (appData.debugMode)
2846                           fprintf(debugFP, "ECHO ");
2847                         /* Reply only if this is a change, according
2848                            to the protocol rules. */
2849                         if (!remoteEchoOption) break;
2850                         EchoOn();
2851                         TelnetRequest(TN_DONT, TN_ECHO);
2852                         remoteEchoOption = FALSE;
2853                         break;
2854                       default:
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", (unsigned char) option);
2857                         /* Whatever this is, it must already be turned
2858                            off, because we never agree to turn on
2859                            anything non-default, so according to the
2860                            protocol rules, we don't reply. */
2861                         break;
2862                     }
2863                     break;
2864                   case TN_DO:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<DO ");
2867                     switch (option = (unsigned char) buf[++i]) {
2868                       default:
2869                         /* Whatever this is, we refuse to do it. */
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", option);
2872                         TelnetRequest(TN_WONT, option);
2873                         break;
2874                     }
2875                     break;
2876                   case TN_DONT:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<DONT ");
2879                     switch (option = (unsigned char) buf[++i]) {
2880                       default:
2881                         if (appData.debugMode)
2882                           fprintf(debugFP, "%d ", option);
2883                         /* Whatever this is, we are already not doing
2884                            it, because we never agree to do anything
2885                            non-default, so according to the protocol
2886                            rules, we don't reply. */
2887                         break;
2888                     }
2889                     break;
2890                   case TN_IAC:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<IAC ");
2893                     /* Doubled IAC; pass it through */
2894                     i--;
2895                     break;
2896                   default:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2899                     /* Drop all other telnet commands on the floor */
2900                     break;
2901                 }
2902                 if (oldi > next_out)
2903                   SendToPlayer(&buf[next_out], oldi - next_out);
2904                 if (++i > next_out)
2905                   next_out = i;
2906                 continue;
2907             }
2908
2909             /* OK, this at least will *usually* work */
2910             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2911                 loggedOn = TRUE;
2912             }
2913
2914             if (loggedOn && !intfSet) {
2915                 if (ics_type == ICS_ICC) {
2916                   snprintf(str, MSG_SIZ,
2917                           "/set-quietly interface %s\n/set-quietly style 12\n",
2918                           programVersion);
2919                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2920                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2921                 } else if (ics_type == ICS_CHESSNET) {
2922                   snprintf(str, MSG_SIZ, "/style 12\n");
2923                 } else {
2924                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2925                   strcat(str, programVersion);
2926                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2927                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2929 #ifdef WIN32
2930                   strcat(str, "$iset nohighlight 1\n");
2931 #endif
2932                   strcat(str, "$iset lock 1\n$style 12\n");
2933                 }
2934                 SendToICS(str);
2935                 NotifyFrontendLogin();
2936                 intfSet = TRUE;
2937             }
2938
2939             if (started == STARTED_COMMENT) {
2940                 /* Accumulate characters in comment */
2941                 parse[parse_pos++] = buf[i];
2942                 if (buf[i] == '\n') {
2943                     parse[parse_pos] = NULLCHAR;
2944                     if(chattingPartner>=0) {
2945                         char mess[MSG_SIZ];
2946                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2947                         OutputChatMessage(chattingPartner, mess);
2948                         chattingPartner = -1;
2949                         next_out = i+1; // [HGM] suppress printing in ICS window
2950                     } else
2951                     if(!suppressKibitz) // [HGM] kibitz
2952                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2953                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2954                         int nrDigit = 0, nrAlph = 0, j;
2955                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2956                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2957                         parse[parse_pos] = NULLCHAR;
2958                         // try to be smart: if it does not look like search info, it should go to
2959                         // ICS interaction window after all, not to engine-output window.
2960                         for(j=0; j<parse_pos; j++) { // count letters and digits
2961                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2962                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2963                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2964                         }
2965                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2966                             int depth=0; float score;
2967                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2968                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2969                                 pvInfoList[forwardMostMove-1].depth = depth;
2970                                 pvInfoList[forwardMostMove-1].score = 100*score;
2971                             }
2972                             OutputKibitz(suppressKibitz, parse);
2973                         } else {
2974                             char tmp[MSG_SIZ];
2975                             if(gameMode == IcsObserving) // restore original ICS messages
2976                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2977                             else
2978                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2979                             SendToPlayer(tmp, strlen(tmp));
2980                         }
2981                         next_out = i+1; // [HGM] suppress printing in ICS window
2982                     }
2983                     started = STARTED_NONE;
2984                 } else {
2985                     /* Don't match patterns against characters in comment */
2986                     i++;
2987                     continue;
2988                 }
2989             }
2990             if (started == STARTED_CHATTER) {
2991                 if (buf[i] != '\n') {
2992                     /* Don't match patterns against characters in chatter */
2993                     i++;
2994                     continue;
2995                 }
2996                 started = STARTED_NONE;
2997                 if(suppressKibitz) next_out = i+1;
2998             }
2999
3000             /* Kludge to deal with rcmd protocol */
3001             if (firstTime && looking_at(buf, &i, "\001*")) {
3002                 DisplayFatalError(&buf[1], 0, 1);
3003                 continue;
3004             } else {
3005                 firstTime = FALSE;
3006             }
3007
3008             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3009                 ics_type = ICS_ICC;
3010                 ics_prefix = "/";
3011                 if (appData.debugMode)
3012                   fprintf(debugFP, "ics_type %d\n", ics_type);
3013                 continue;
3014             }
3015             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3016                 ics_type = ICS_FICS;
3017                 ics_prefix = "$";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3023                 ics_type = ICS_CHESSNET;
3024                 ics_prefix = "/";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029
3030             if (!loggedOn &&
3031                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3032                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3033                  looking_at(buf, &i, "will be \"*\""))) {
3034               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3035               continue;
3036             }
3037
3038             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3039               char buf[MSG_SIZ];
3040               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3041               DisplayIcsInteractionTitle(buf);
3042               have_set_title = TRUE;
3043             }
3044
3045             /* skip finger notes */
3046             if (started == STARTED_NONE &&
3047                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3048                  (buf[i] == '1' && buf[i+1] == '0')) &&
3049                 buf[i+2] == ':' && buf[i+3] == ' ') {
3050               started = STARTED_CHATTER;
3051               i += 3;
3052               continue;
3053             }
3054
3055             oldi = i;
3056             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3057             if(appData.seekGraph) {
3058                 if(soughtPending && MatchSoughtLine(buf+i)) {
3059                     i = strstr(buf+i, "rated") - buf;
3060                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061                     next_out = leftover_start = i;
3062                     started = STARTED_CHATTER;
3063                     suppressKibitz = TRUE;
3064                     continue;
3065                 }
3066                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3067                         && looking_at(buf, &i, "* ads displayed")) {
3068                     soughtPending = FALSE;
3069                     seekGraphUp = TRUE;
3070                     DrawSeekGraph();
3071                     continue;
3072                 }
3073                 if(appData.autoRefresh) {
3074                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3075                         int s = (ics_type == ICS_ICC); // ICC format differs
3076                         if(seekGraphUp)
3077                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3078                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3079                         looking_at(buf, &i, "*% "); // eat prompt
3080                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i; // suppress
3083                         continue;
3084                     }
3085                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3086                         char *p = star_match[0];
3087                         while(*p) {
3088                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3089                             while(*p && *p++ != ' '); // next
3090                         }
3091                         looking_at(buf, &i, "*% "); // eat prompt
3092                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3093                         next_out = i;
3094                         continue;
3095                     }
3096                 }
3097             }
3098
3099             /* skip formula vars */
3100             if (started == STARTED_NONE &&
3101                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3102               started = STARTED_CHATTER;
3103               i += 3;
3104               continue;
3105             }
3106
3107             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3108             if (appData.autoKibitz && started == STARTED_NONE &&
3109                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3110                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3111                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3112                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3113                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3114                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3115                         suppressKibitz = TRUE;
3116                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                         next_out = i;
3118                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3119                                 && (gameMode == IcsPlayingWhite)) ||
3120                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3121                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3122                             started = STARTED_CHATTER; // own kibitz we simply discard
3123                         else {
3124                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3125                             parse_pos = 0; parse[0] = NULLCHAR;
3126                             savingComment = TRUE;
3127                             suppressKibitz = gameMode != IcsObserving ? 2 :
3128                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3129                         }
3130                         continue;
3131                 } else
3132                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3133                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3134                          && atoi(star_match[0])) {
3135                     // suppress the acknowledgements of our own autoKibitz
3136                     char *p;
3137                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3139                     SendToPlayer(star_match[0], strlen(star_match[0]));
3140                     if(looking_at(buf, &i, "*% ")) // eat prompt
3141                         suppressKibitz = FALSE;
3142                     next_out = i;
3143                     continue;
3144                 }
3145             } // [HGM] kibitz: end of patch
3146
3147             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3148
3149             // [HGM] chat: intercept tells by users for which we have an open chat window
3150             channel = -1;
3151             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3152                                            looking_at(buf, &i, "* whispers:") ||
3153                                            looking_at(buf, &i, "* kibitzes:") ||
3154                                            looking_at(buf, &i, "* shouts:") ||
3155                                            looking_at(buf, &i, "* c-shouts:") ||
3156                                            looking_at(buf, &i, "--> * ") ||
3157                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3159                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3160                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3161                 int p;
3162                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3163                 chattingPartner = -1;
3164
3165                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3166                 for(p=0; p<MAX_CHAT; p++) {
3167                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3168                     talker[0] = '['; strcat(talker, "] ");
3169                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3170                     chattingPartner = p; break;
3171                     }
3172                 } else
3173                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3174                 for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("kibitzes", chatPartner[p])) {
3176                         talker[0] = '['; strcat(talker, "] ");
3177                         chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("whispers", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3188                   if(buf[i-8] == '-' && buf[i-3] == 't')
3189                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3190                     if(!strcmp("c-shouts", chatPartner[p])) {
3191                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3192                         chattingPartner = p; break;
3193                     }
3194                   }
3195                   if(chattingPartner < 0)
3196                   for(p=0; p<MAX_CHAT; p++) {
3197                     if(!strcmp("shouts", chatPartner[p])) {
3198                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3199                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3200                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3201                         chattingPartner = p; break;
3202                     }
3203                   }
3204                 }
3205                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3206                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3207                     talker[0] = 0; Colorize(ColorTell, FALSE);
3208                     chattingPartner = p; break;
3209                 }
3210                 if(chattingPartner<0) i = oldi; else {
3211                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3212                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     started = STARTED_COMMENT;
3215                     parse_pos = 0; parse[0] = NULLCHAR;
3216                     savingComment = 3 + chattingPartner; // counts as TRUE
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220             } // [HGM] chat: end of patch
3221
3222           backup = i;
3223             if (appData.zippyTalk || appData.zippyPlay) {
3224                 /* [DM] Backup address for color zippy lines */
3225 #if ZIPPY
3226                if (loggedOn == TRUE)
3227                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3228                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3229 #endif
3230             } // [DM] 'else { ' deleted
3231                 if (
3232                     /* Regular tells and says */
3233                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3234                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3235                     looking_at(buf, &i, "* says: ") ||
3236                     /* Don't color "message" or "messages" output */
3237                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3238                     looking_at(buf, &i, "*. * at *:*: ") ||
3239                     looking_at(buf, &i, "--* (*:*): ") ||
3240                     /* Message notifications (same color as tells) */
3241                     looking_at(buf, &i, "* has left a message ") ||
3242                     looking_at(buf, &i, "* just sent you a message:\n") ||
3243                     /* Whispers and kibitzes */
3244                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3245                     looking_at(buf, &i, "* kibitzes: ") ||
3246                     /* Channel tells */
3247                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3248
3249                   if (tkind == 1 && strchr(star_match[0], ':')) {
3250                       /* Avoid "tells you:" spoofs in channels */
3251                      tkind = 3;
3252                   }
3253                   if (star_match[0][0] == NULLCHAR ||
3254                       strchr(star_match[0], ' ') ||
3255                       (tkind == 3 && strchr(star_match[1], ' '))) {
3256                     /* Reject bogus matches */
3257                     i = oldi;
3258                   } else {
3259                     if (appData.colorize) {
3260                       if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                       }
3264                       switch (tkind) {
3265                       case 1:
3266                         Colorize(ColorTell, FALSE);
3267                         curColor = ColorTell;
3268                         break;
3269                       case 2:
3270                         Colorize(ColorKibitz, FALSE);
3271                         curColor = ColorKibitz;
3272                         break;
3273                       case 3:
3274                         p = strrchr(star_match[1], '(');
3275                         if (p == NULL) {
3276                           p = star_match[1];
3277                         } else {
3278                           p++;
3279                         }
3280                         if (atoi(p) == 1) {
3281                           Colorize(ColorChannel1, FALSE);
3282                           curColor = ColorChannel1;
3283                         } else {
3284                           Colorize(ColorChannel, FALSE);
3285                           curColor = ColorChannel;
3286                         }
3287                         break;
3288                       case 5:
3289                         curColor = ColorNormal;
3290                         break;
3291                       }
3292                     }
3293                     if (started == STARTED_NONE && appData.autoComment &&
3294                         (gameMode == IcsObserving ||
3295                          gameMode == IcsPlayingWhite ||
3296                          gameMode == IcsPlayingBlack)) {
3297                       parse_pos = i - oldi;
3298                       memcpy(parse, &buf[oldi], parse_pos);
3299                       parse[parse_pos] = NULLCHAR;
3300                       started = STARTED_COMMENT;
3301                       savingComment = TRUE;
3302                     } else {
3303                       started = STARTED_CHATTER;
3304                       savingComment = FALSE;
3305                     }
3306                     loggedOn = TRUE;
3307                     continue;
3308                   }
3309                 }
3310
3311                 if (looking_at(buf, &i, "* s-shouts: ") ||
3312                     looking_at(buf, &i, "* c-shouts: ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorSShout, FALSE);
3319                         curColor = ColorSShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at(buf, &i, "--->")) {
3327                     loggedOn = TRUE;
3328                     continue;
3329                 }
3330
3331                 if (looking_at(buf, &i, "* shouts: ") ||
3332                     looking_at(buf, &i, "--> ")) {
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorShout, FALSE);
3339                         curColor = ColorShout;
3340                     }
3341                     loggedOn = TRUE;
3342                     started = STARTED_CHATTER;
3343                     continue;
3344                 }
3345
3346                 if (looking_at( buf, &i, "Challenge:")) {
3347                     if (appData.colorize) {
3348                         if (oldi > next_out) {
3349                             SendToPlayer(&buf[next_out], oldi - next_out);
3350                             next_out = oldi;
3351                         }
3352                         Colorize(ColorChallenge, FALSE);
3353                         curColor = ColorChallenge;
3354                     }
3355                     loggedOn = TRUE;
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "* offers you") ||
3360                     looking_at(buf, &i, "* offers to be") ||
3361                     looking_at(buf, &i, "* would like to") ||
3362                     looking_at(buf, &i, "* requests to") ||
3363                     looking_at(buf, &i, "Your opponent offers") ||
3364                     looking_at(buf, &i, "Your opponent requests")) {
3365
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorRequest, FALSE);
3372                         curColor = ColorRequest;
3373                     }
3374                     continue;
3375                 }
3376
3377                 if (looking_at(buf, &i, "* (*) seeking")) {
3378                     if (appData.colorize) {
3379                         if (oldi > next_out) {
3380                             SendToPlayer(&buf[next_out], oldi - next_out);
3381                             next_out = oldi;
3382                         }
3383                         Colorize(ColorSeek, FALSE);
3384                         curColor = ColorSeek;
3385                     }
3386                     continue;
3387             }
3388
3389           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3390
3391             if (looking_at(buf, &i, "\\   ")) {
3392                 if (prevColor != ColorNormal) {
3393                     if (oldi > next_out) {
3394                         SendToPlayer(&buf[next_out], oldi - next_out);
3395                         next_out = oldi;
3396                     }
3397                     Colorize(prevColor, TRUE);
3398                     curColor = prevColor;
3399                 }
3400                 if (savingComment) {
3401                     parse_pos = i - oldi;
3402                     memcpy(parse, &buf[oldi], parse_pos);
3403                     parse[parse_pos] = NULLCHAR;
3404                     started = STARTED_COMMENT;
3405                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3406                         chattingPartner = savingComment - 3; // kludge to remember the box
3407                 } else {
3408                     started = STARTED_CHATTER;
3409                 }
3410                 continue;
3411             }
3412
3413             if (looking_at(buf, &i, "Black Strength :") ||
3414                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3415                 looking_at(buf, &i, "<10>") ||
3416                 looking_at(buf, &i, "#@#")) {
3417                 /* Wrong board style */
3418                 loggedOn = TRUE;
3419                 SendToICS(ics_prefix);
3420                 SendToICS("set style 12\n");
3421                 SendToICS(ics_prefix);
3422                 SendToICS("refresh\n");
3423                 continue;
3424             }
3425
3426             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3427                 ICSInitScript();
3428                 have_sent_ICS_logon = 1;
3429                 continue;
3430             }
3431
3432             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3433                 (looking_at(buf, &i, "\n<12> ") ||
3434                  looking_at(buf, &i, "<12> "))) {
3435                 loggedOn = TRUE;
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_BOARD;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3446                 looking_at(buf, &i, "<b1> ")) {
3447                 if (oldi > next_out) {
3448                     SendToPlayer(&buf[next_out], oldi - next_out);
3449                 }
3450                 next_out = i;
3451                 started = STARTED_HOLDINGS;
3452                 parse_pos = 0;
3453                 continue;
3454             }
3455
3456             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3457                 loggedOn = TRUE;
3458                 /* Header for a move list -- first line */
3459
3460                 switch (ics_getting_history) {
3461                   case H_FALSE:
3462                     switch (gameMode) {
3463                       case IcsIdle:
3464                       case BeginningOfGame:
3465                         /* User typed "moves" or "oldmoves" while we
3466                            were idle.  Pretend we asked for these
3467                            moves and soak them up so user can step
3468                            through them and/or save them.
3469                            */
3470                         Reset(FALSE, TRUE);
3471                         gameMode = IcsObserving;
3472                         ModeHighlight();
3473                         ics_gamenum = -1;
3474                         ics_getting_history = H_GOT_UNREQ_HEADER;
3475                         break;
3476                       case EditGame: /*?*/
3477                       case EditPosition: /*?*/
3478                         /* Should above feature work in these modes too? */
3479                         /* For now it doesn't */
3480                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3481                         break;
3482                       default:
3483                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3484                         break;
3485                     }
3486                     break;
3487                   case H_REQUESTED:
3488                     /* Is this the right one? */
3489                     if (gameInfo.white && gameInfo.black &&
3490                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3491                         strcmp(gameInfo.black, star_match[2]) == 0) {
3492                         /* All is well */
3493                         ics_getting_history = H_GOT_REQ_HEADER;
3494                     }
3495                     break;
3496                   case H_GOT_REQ_HEADER:
3497                   case H_GOT_UNREQ_HEADER:
3498                   case H_GOT_UNWANTED_HEADER:
3499                   case H_GETTING_MOVES:
3500                     /* Should not happen */
3501                     DisplayError(_("Error gathering move list: two headers"), 0);
3502                     ics_getting_history = H_FALSE;
3503                     break;
3504                 }
3505
3506                 /* Save player ratings into gameInfo if needed */
3507                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3508                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3509                     (gameInfo.whiteRating == -1 ||
3510                      gameInfo.blackRating == -1)) {
3511
3512                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3513                     gameInfo.blackRating = string_to_rating(star_match[3]);
3514                     if (appData.debugMode)
3515                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3516                               gameInfo.whiteRating, gameInfo.blackRating);
3517                 }
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i,
3522               "* * match, initial time: * minute*, increment: * second")) {
3523                 /* Header for a move list -- second line */
3524                 /* Initial board will follow if this is a wild game */
3525                 if (gameInfo.event != NULL) free(gameInfo.event);
3526                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3527                 gameInfo.event = StrSave(str);
3528                 /* [HGM] we switched variant. Translate boards if needed. */
3529                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3530                 continue;
3531             }
3532
3533             if (looking_at(buf, &i, "Move  ")) {
3534                 /* Beginning of a move list */
3535                 switch (ics_getting_history) {
3536                   case H_FALSE:
3537                     /* Normally should not happen */
3538                     /* Maybe user hit reset while we were parsing */
3539                     break;
3540                   case H_REQUESTED:
3541                     /* Happens if we are ignoring a move list that is not
3542                      * the one we just requested.  Common if the user
3543                      * tries to observe two games without turning off
3544                      * getMoveList */
3545                     break;
3546                   case H_GETTING_MOVES:
3547                     /* Should not happen */
3548                     DisplayError(_("Error gathering move list: nested"), 0);
3549                     ics_getting_history = H_FALSE;
3550                     break;
3551                   case H_GOT_REQ_HEADER:
3552                     ics_getting_history = H_GETTING_MOVES;
3553                     started = STARTED_MOVES;
3554                     parse_pos = 0;
3555                     if (oldi > next_out) {
3556                         SendToPlayer(&buf[next_out], oldi - next_out);
3557                     }
3558                     break;
3559                   case H_GOT_UNREQ_HEADER:
3560                     ics_getting_history = H_GETTING_MOVES;
3561                     started = STARTED_MOVES_NOHIDE;
3562                     parse_pos = 0;
3563                     break;
3564                   case H_GOT_UNWANTED_HEADER:
3565                     ics_getting_history = H_FALSE;
3566                     break;
3567                 }
3568                 continue;
3569             }
3570
3571             if (looking_at(buf, &i, "% ") ||
3572                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3573                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3574                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3575                     soughtPending = FALSE;
3576                     seekGraphUp = TRUE;
3577                     DrawSeekGraph();
3578                 }
3579                 if(suppressKibitz) next_out = i;
3580                 savingComment = FALSE;
3581                 suppressKibitz = 0;
3582                 switch (started) {
3583                   case STARTED_MOVES:
3584                   case STARTED_MOVES_NOHIDE:
3585                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3586                     parse[parse_pos + i - oldi] = NULLCHAR;
3587                     ParseGameHistory(parse);
3588 #if ZIPPY
3589                     if (appData.zippyPlay && first.initDone) {
3590                         FeedMovesToProgram(&first, forwardMostMove);
3591                         if (gameMode == IcsPlayingWhite) {
3592                             if (WhiteOnMove(forwardMostMove)) {
3593                                 if (first.sendTime) {
3594                                   if (first.useColors) {
3595                                     SendToProgram("black\n", &first);
3596                                   }
3597                                   SendTimeRemaining(&first, TRUE);
3598                                 }
3599                                 if (first.useColors) {
3600                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3601                                 }
3602                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3603                                 first.maybeThinking = TRUE;
3604                             } else {
3605                                 if (first.usePlayother) {
3606                                   if (first.sendTime) {
3607                                     SendTimeRemaining(&first, TRUE);
3608                                   }
3609                                   SendToProgram("playother\n", &first);
3610                                   firstMove = FALSE;
3611                                 } else {
3612                                   firstMove = TRUE;
3613                                 }
3614                             }
3615                         } else if (gameMode == IcsPlayingBlack) {
3616                             if (!WhiteOnMove(forwardMostMove)) {
3617                                 if (first.sendTime) {
3618                                   if (first.useColors) {
3619                                     SendToProgram("white\n", &first);
3620                                   }
3621                                   SendTimeRemaining(&first, FALSE);
3622                                 }
3623                                 if (first.useColors) {
3624                                   SendToProgram("black\n", &first);
3625                                 }
3626                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3627                                 first.maybeThinking = TRUE;
3628                             } else {
3629                                 if (first.usePlayother) {
3630                                   if (first.sendTime) {
3631                                     SendTimeRemaining(&first, FALSE);
3632                                   }
3633                                   SendToProgram("playother\n", &first);
3634                                   firstMove = FALSE;
3635                                 } else {
3636                                   firstMove = TRUE;
3637                                 }
3638                             }
3639                         }
3640                     }
3641 #endif
3642                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3643                         /* Moves came from oldmoves or moves command
3644                            while we weren't doing anything else.
3645                            */
3646                         currentMove = forwardMostMove;
3647                         ClearHighlights();/*!!could figure this out*/
3648                         flipView = appData.flipView;
3649                         DrawPosition(TRUE, boards[currentMove]);
3650                         DisplayBothClocks();
3651                         snprintf(str, MSG_SIZ, "%s %s %s",
3652                                 gameInfo.white, _("vs."),  gameInfo.black);
3653                         DisplayTitle(str);
3654                         gameMode = IcsIdle;
3655                     } else {
3656                         /* Moves were history of an active game */
3657                         if (gameInfo.resultDetails != NULL) {
3658                             free(gameInfo.resultDetails);
3659                             gameInfo.resultDetails = NULL;
3660                         }
3661                     }
3662                     HistorySet(parseList, backwardMostMove,
3663                                forwardMostMove, currentMove-1);
3664                     DisplayMove(currentMove - 1);
3665                     if (started == STARTED_MOVES) next_out = i;
3666                     started = STARTED_NONE;
3667                     ics_getting_history = H_FALSE;
3668                     break;
3669
3670                   case STARTED_OBSERVE:
3671                     started = STARTED_NONE;
3672                     SendToICS(ics_prefix);
3673                     SendToICS("refresh\n");
3674                     break;
3675
3676                   default:
3677                     break;
3678                 }
3679                 if(bookHit) { // [HGM] book: simulate book reply
3680                     static char bookMove[MSG_SIZ]; // a bit generous?
3681
3682                     programStats.nodes = programStats.depth = programStats.time =
3683                     programStats.score = programStats.got_only_move = 0;
3684                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3685
3686                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3687                     strcat(bookMove, bookHit);
3688                     HandleMachineMove(bookMove, &first);
3689                 }
3690                 continue;
3691             }
3692
3693             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3694                  started == STARTED_HOLDINGS ||
3695                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3696                 /* Accumulate characters in move list or board */
3697                 parse[parse_pos++] = buf[i];
3698             }
3699
3700             /* Start of game messages.  Mostly we detect start of game
3701                when the first board image arrives.  On some versions
3702                of the ICS, though, we need to do a "refresh" after starting
3703                to observe in order to get the current board right away. */
3704             if (looking_at(buf, &i, "Adding game * to observation list")) {
3705                 started = STARTED_OBSERVE;
3706                 continue;
3707             }
3708
3709             /* Handle auto-observe */
3710             if (appData.autoObserve &&
3711                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3712                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3713                 char *player;
3714                 /* Choose the player that was highlighted, if any. */
3715                 if (star_match[0][0] == '\033' ||
3716                     star_match[1][0] != '\033') {
3717                     player = star_match[0];
3718                 } else {
3719                     player = star_match[2];
3720                 }
3721                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3722                         ics_prefix, StripHighlightAndTitle(player));
3723                 SendToICS(str);
3724
3725                 /* Save ratings from notify string */
3726                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3727                 player1Rating = string_to_rating(star_match[1]);
3728                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3729                 player2Rating = string_to_rating(star_match[3]);
3730
3731                 if (appData.debugMode)
3732                   fprintf(debugFP,
3733                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3734                           player1Name, player1Rating,
3735                           player2Name, player2Rating);
3736
3737                 continue;
3738             }
3739
3740             /* Deal with automatic examine mode after a game,
3741                and with IcsObserving -> IcsExamining transition */
3742             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3743                 looking_at(buf, &i, "has made you an examiner of game *")) {
3744
3745                 int gamenum = atoi(star_match[0]);
3746                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3747                     gamenum == ics_gamenum) {
3748                     /* We were already playing or observing this game;
3749                        no need to refetch history */
3750                     gameMode = IcsExamining;
3751                     if (pausing) {
3752                         pauseExamForwardMostMove = forwardMostMove;
3753                     } else if (currentMove < forwardMostMove) {
3754                         ForwardInner(forwardMostMove);
3755                     }
3756                 } else {
3757                     /* I don't think this case really can happen */
3758                     SendToICS(ics_prefix);
3759                     SendToICS("refresh\n");
3760                 }
3761                 continue;
3762             }
3763
3764             /* Error messages */
3765 //          if (ics_user_moved) {
3766             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3767                 if (looking_at(buf, &i, "Illegal move") ||
3768                     looking_at(buf, &i, "Not a legal move") ||
3769                     looking_at(buf, &i, "Your king is in check") ||
3770                     looking_at(buf, &i, "It isn't your turn") ||
3771                     looking_at(buf, &i, "It is not your move")) {
3772                     /* Illegal move */
3773                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3774                         currentMove = forwardMostMove-1;
3775                         DisplayMove(currentMove - 1); /* before DMError */
3776                         DrawPosition(FALSE, boards[currentMove]);
3777                         SwitchClocks(forwardMostMove-1); // [HGM] race
3778                         DisplayBothClocks();
3779                     }
3780                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3781                     ics_user_moved = 0;
3782                     continue;
3783                 }
3784             }
3785
3786             if (looking_at(buf, &i, "still have time") ||
3787                 looking_at(buf, &i, "not out of time") ||
3788                 looking_at(buf, &i, "either player is out of time") ||
3789                 looking_at(buf, &i, "has timeseal; checking")) {
3790                 /* We must have called his flag a little too soon */
3791                 whiteFlag = blackFlag = FALSE;
3792                 continue;
3793             }
3794
3795             if (looking_at(buf, &i, "added * seconds to") ||
3796                 looking_at(buf, &i, "seconds were added to")) {
3797                 /* Update the clocks */
3798                 SendToICS(ics_prefix);
3799                 SendToICS("refresh\n");
3800                 continue;
3801             }
3802
3803             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3804                 ics_clock_paused = TRUE;
3805                 StopClocks();
3806                 continue;
3807             }
3808
3809             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3810                 ics_clock_paused = FALSE;
3811                 StartClocks();
3812                 continue;
3813             }
3814
3815             /* Grab player ratings from the Creating: message.
3816                Note we have to check for the special case when
3817                the ICS inserts things like [white] or [black]. */
3818             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3819                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3820                 /* star_matches:
3821                    0    player 1 name (not necessarily white)
3822                    1    player 1 rating
3823                    2    empty, white, or black (IGNORED)
3824                    3    player 2 name (not necessarily black)
3825                    4    player 2 rating
3826
3827                    The names/ratings are sorted out when the game
3828                    actually starts (below).
3829                 */
3830                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3831                 player1Rating = string_to_rating(star_match[1]);
3832                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3833                 player2Rating = string_to_rating(star_match[4]);
3834
3835                 if (appData.debugMode)
3836                   fprintf(debugFP,
3837                           "Ratings from 'Creating:' %s %d, %s %d\n",
3838                           player1Name, player1Rating,
3839                           player2Name, player2Rating);
3840
3841                 continue;
3842             }
3843
3844             /* Improved generic start/end-of-game messages */
3845             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3846                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3847                 /* If tkind == 0: */
3848                 /* star_match[0] is the game number */
3849                 /*           [1] is the white player's name */
3850                 /*           [2] is the black player's name */
3851                 /* For end-of-game: */
3852                 /*           [3] is the reason for the game end */
3853                 /*           [4] is a PGN end game-token, preceded by " " */
3854                 /* For start-of-game: */
3855                 /*           [3] begins with "Creating" or "Continuing" */
3856                 /*           [4] is " *" or empty (don't care). */
3857                 int gamenum = atoi(star_match[0]);
3858                 char *whitename, *blackname, *why, *endtoken;
3859                 ChessMove endtype = EndOfFile;
3860
3861                 if (tkind == 0) {
3862                   whitename = star_match[1];
3863                   blackname = star_match[2];
3864                   why = star_match[3];
3865                   endtoken = star_match[4];
3866                 } else {
3867                   whitename = star_match[1];
3868                   blackname = star_match[3];
3869                   why = star_match[5];
3870                   endtoken = star_match[6];
3871                 }
3872
3873                 /* Game start messages */
3874                 if (strncmp(why, "Creating ", 9) == 0 ||
3875                     strncmp(why, "Continuing ", 11) == 0) {
3876                     gs_gamenum = gamenum;
3877                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3878                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3879                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3880 #if ZIPPY
3881                     if (appData.zippyPlay) {
3882                         ZippyGameStart(whitename, blackname);
3883                     }
3884 #endif /*ZIPPY*/
3885                     partnerBoardValid = FALSE; // [HGM] bughouse
3886                     continue;
3887                 }
3888
3889                 /* Game end messages */
3890                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3891                     ics_gamenum != gamenum) {
3892                     continue;
3893                 }
3894                 while (endtoken[0] == ' ') endtoken++;
3895                 switch (endtoken[0]) {
3896                   case '*':
3897                   default:
3898                     endtype = GameUnfinished;
3899                     break;
3900                   case '0':
3901                     endtype = BlackWins;
3902                     break;
3903                   case '1':
3904                     if (endtoken[1] == '/')
3905                       endtype = GameIsDrawn;
3906                     else
3907                       endtype = WhiteWins;
3908                     break;
3909                 }
3910                 GameEnds(endtype, why, GE_ICS);
3911 #if ZIPPY
3912                 if (appData.zippyPlay && first.initDone) {
3913                     ZippyGameEnd(endtype, why);
3914                     if (first.pr == NoProc) {
3915                       /* Start the next process early so that we'll
3916                          be ready for the next challenge */
3917                       StartChessProgram(&first);
3918                     }
3919                     /* Send "new" early, in case this command takes
3920                        a long time to finish, so that we'll be ready
3921                        for the next challenge. */
3922                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3923                     Reset(TRUE, TRUE);
3924                 }
3925 #endif /*ZIPPY*/
3926                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3927                 continue;
3928             }
3929
3930             if (looking_at(buf, &i, "Removing game * from observation") ||
3931                 looking_at(buf, &i, "no longer observing game *") ||
3932                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3933                 if (gameMode == IcsObserving &&
3934                     atoi(star_match[0]) == ics_gamenum)
3935                   {
3936                       /* icsEngineAnalyze */
3937                       if (appData.icsEngineAnalyze) {
3938                             ExitAnalyzeMode();
3939                             ModeHighlight();
3940                       }
3941                       StopClocks();
3942                       gameMode = IcsIdle;
3943                       ics_gamenum = -1;
3944                       ics_user_moved = FALSE;
3945                   }
3946                 continue;
3947             }
3948
3949             if (looking_at(buf, &i, "no longer examining game *")) {
3950                 if (gameMode == IcsExamining &&
3951                     atoi(star_match[0]) == ics_gamenum)
3952                   {
3953                       gameMode = IcsIdle;
3954                       ics_gamenum = -1;
3955                       ics_user_moved = FALSE;
3956                   }
3957                 continue;
3958             }
3959
3960             /* Advance leftover_start past any newlines we find,
3961                so only partial lines can get reparsed */
3962             if (looking_at(buf, &i, "\n")) {
3963                 prevColor = curColor;
3964                 if (curColor != ColorNormal) {
3965                     if (oldi > next_out) {
3966                         SendToPlayer(&buf[next_out], oldi - next_out);
3967                         next_out = oldi;
3968                     }
3969                     Colorize(ColorNormal, FALSE);
3970                     curColor = ColorNormal;
3971                 }
3972                 if (started == STARTED_BOARD) {
3973                     started = STARTED_NONE;
3974                     parse[parse_pos] = NULLCHAR;
3975                     ParseBoard12(parse);
3976                     ics_user_moved = 0;
3977
3978                     /* Send premove here */
3979                     if (appData.premove) {
3980                       char str[MSG_SIZ];
3981                       if (currentMove == 0 &&
3982                           gameMode == IcsPlayingWhite &&
3983                           appData.premoveWhite) {
3984                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                         SendToICS(str);
3988                       } else if (currentMove == 1 &&
3989                                  gameMode == IcsPlayingBlack &&
3990                                  appData.premoveBlack) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (gotPremove) {
3996                         gotPremove = 0;
3997                         ClearPremoveHighlights();
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                           UserMoveEvent(premoveFromX, premoveFromY,
4001                                         premoveToX, premoveToY,
4002                                         premovePromoChar);
4003                       }
4004                     }
4005
4006                     /* Usually suppress following prompt */
4007                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4008                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4009                         if (looking_at(buf, &i, "*% ")) {
4010                             savingComment = FALSE;
4011                             suppressKibitz = 0;
4012                         }
4013                     }
4014                     next_out = i;
4015                 } else if (started == STARTED_HOLDINGS) {
4016                     int gamenum;
4017                     char new_piece[MSG_SIZ];
4018                     started = STARTED_NONE;
4019                     parse[parse_pos] = NULLCHAR;
4020                     if (appData.debugMode)
4021                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4022                                                         parse, currentMove);
4023                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4024                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4025                         if (gameInfo.variant == VariantNormal) {
4026                           /* [HGM] We seem to switch variant during a game!
4027                            * Presumably no holdings were displayed, so we have
4028                            * to move the position two files to the right to
4029                            * create room for them!
4030                            */
4031                           VariantClass newVariant;
4032                           switch(gameInfo.boardWidth) { // base guess on board width
4033                                 case 9:  newVariant = VariantShogi; break;
4034                                 case 10: newVariant = VariantGreat; break;
4035                                 default: newVariant = VariantCrazyhouse; break;
4036                           }
4037                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4038                           /* Get a move list just to see the header, which
4039                              will tell us whether this is really bug or zh */
4040                           if (ics_getting_history == H_FALSE) {
4041                             ics_getting_history = H_REQUESTED;
4042                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4043                             SendToICS(str);
4044                           }
4045                         }
4046                         new_piece[0] = NULLCHAR;
4047                         sscanf(parse, "game %d white [%s black [%s <- %s",
4048                                &gamenum, white_holding, black_holding,
4049                                new_piece);
4050                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4051                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4052                         /* [HGM] copy holdings to board holdings area */
4053                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4054                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4055                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4056 #if ZIPPY
4057                         if (appData.zippyPlay && first.initDone) {
4058                             ZippyHoldings(white_holding, black_holding,
4059                                           new_piece);
4060                         }
4061 #endif /*ZIPPY*/
4062                         if (tinyLayout || smallLayout) {
4063                             char wh[16], bh[16];
4064                             PackHolding(wh, white_holding);
4065                             PackHolding(bh, black_holding);
4066                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4067                                     gameInfo.white, gameInfo.black);
4068                         } else {
4069                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4070                                     gameInfo.white, white_holding, _("vs."),
4071                                     gameInfo.black, black_holding);
4072                         }
4073                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4074                         DrawPosition(FALSE, boards[currentMove]);
4075                         DisplayTitle(str);
4076                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4077                         sscanf(parse, "game %d white [%s black [%s <- %s",
4078                                &gamenum, white_holding, black_holding,
4079                                new_piece);
4080                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4081                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4082                         /* [HGM] copy holdings to partner-board holdings area */
4083                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4084                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4085                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4086                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4087                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4088                       }
4089                     }
4090                     /* Suppress following prompt */
4091                     if (looking_at(buf, &i, "*% ")) {
4092                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4093                         savingComment = FALSE;
4094                         suppressKibitz = 0;
4095                     }
4096                     next_out = i;
4097                 }
4098                 continue;
4099             }
4100
4101             i++;                /* skip unparsed character and loop back */
4102         }
4103
4104         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4105 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4106 //          SendToPlayer(&buf[next_out], i - next_out);
4107             started != STARTED_HOLDINGS && leftover_start > next_out) {
4108             SendToPlayer(&buf[next_out], leftover_start - next_out);
4109             next_out = i;
4110         }
4111
4112         leftover_len = buf_len - leftover_start;
4113         /* if buffer ends with something we couldn't parse,
4114            reparse it after appending the next read */
4115
4116     } else if (count == 0) {
4117         RemoveInputSource(isr);
4118         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4119     } else {
4120         DisplayFatalError(_("Error reading from ICS"), error, 1);
4121     }
4122 }
4123
4124
4125 /* Board style 12 looks like this:
4126
4127    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4128
4129  * The "<12> " is stripped before it gets to this routine.  The two
4130  * trailing 0's (flip state and clock ticking) are later addition, and
4131  * some chess servers may not have them, or may have only the first.
4132  * Additional trailing fields may be added in the future.
4133  */
4134
4135 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4136
4137 #define RELATION_OBSERVING_PLAYED    0
4138 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4139 #define RELATION_PLAYING_MYMOVE      1
4140 #define RELATION_PLAYING_NOTMYMOVE  -1
4141 #define RELATION_EXAMINING           2
4142 #define RELATION_ISOLATED_BOARD     -3
4143 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4144
4145 void
4146 ParseBoard12 (char *string)
4147 {
4148     GameMode newGameMode;
4149     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4150     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4151     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4152     char to_play, board_chars[200];
4153     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4154     char black[32], white[32];
4155     Board board;
4156     int prevMove = currentMove;
4157     int ticking = 2;
4158     ChessMove moveType;
4159     int fromX, fromY, toX, toY;
4160     char promoChar;
4161     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4162     char *bookHit = NULL; // [HGM] book
4163     Boolean weird = FALSE, reqFlag = FALSE;
4164
4165     fromX = fromY = toX = toY = -1;
4166
4167     newGame = FALSE;
4168
4169     if (appData.debugMode)
4170       fprintf(debugFP, _("Parsing board: %s\n"), string);
4171
4172     move_str[0] = NULLCHAR;
4173     elapsed_time[0] = NULLCHAR;
4174     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4175         int  i = 0, j;
4176         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4177             if(string[i] == ' ') { ranks++; files = 0; }
4178             else files++;
4179             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4180             i++;
4181         }
4182         for(j = 0; j <i; j++) board_chars[j] = string[j];
4183         board_chars[i] = '\0';
4184         string += i + 1;
4185     }
4186     n = sscanf(string, PATTERN, &to_play, &double_push,
4187                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4188                &gamenum, white, black, &relation, &basetime, &increment,
4189                &white_stren, &black_stren, &white_time, &black_time,
4190                &moveNum, str, elapsed_time, move_str, &ics_flip,
4191                &ticking);
4192
4193     if (n < 21) {
4194         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4195         DisplayError(str, 0);
4196         return;
4197     }
4198
4199     /* Convert the move number to internal form */
4200     moveNum = (moveNum - 1) * 2;
4201     if (to_play == 'B') moveNum++;
4202     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4203       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4204                         0, 1);
4205       return;
4206     }
4207
4208     switch (relation) {
4209       case RELATION_OBSERVING_PLAYED:
4210       case RELATION_OBSERVING_STATIC:
4211         if (gamenum == -1) {
4212             /* Old ICC buglet */
4213             relation = RELATION_OBSERVING_STATIC;
4214         }
4215         newGameMode = IcsObserving;
4216         break;
4217       case RELATION_PLAYING_MYMOVE:
4218       case RELATION_PLAYING_NOTMYMOVE:
4219         newGameMode =
4220           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4221             IcsPlayingWhite : IcsPlayingBlack;
4222         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4223         break;
4224       case RELATION_EXAMINING:
4225         newGameMode = IcsExamining;
4226         break;
4227       case RELATION_ISOLATED_BOARD:
4228       default:
4229         /* Just display this board.  If user was doing something else,
4230            we will forget about it until the next board comes. */
4231         newGameMode = IcsIdle;
4232         break;
4233       case RELATION_STARTING_POSITION:
4234         newGameMode = gameMode;
4235         break;
4236     }
4237
4238     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4239          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4240       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4241       char *toSqr;
4242       for (k = 0; k < ranks; k++) {
4243         for (j = 0; j < files; j++)
4244           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4245         if(gameInfo.holdingsWidth > 1) {
4246              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4247              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4248         }
4249       }
4250       CopyBoard(partnerBoard, board);
4251       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4252         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4253         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4254       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4255       if(toSqr = strchr(str, '-')) {
4256         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4257         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4258       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4259       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4260       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4261       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4262       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4263       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4264                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4265       DisplayMessage(partnerStatus, "");
4266         partnerBoardValid = TRUE;
4267       return;
4268     }
4269
4270     /* Modify behavior for initial board display on move listing
4271        of wild games.
4272        */
4273     switch (ics_getting_history) {
4274       case H_FALSE:
4275       case H_REQUESTED:
4276         break;
4277       case H_GOT_REQ_HEADER:
4278       case H_GOT_UNREQ_HEADER:
4279         /* This is the initial position of the current game */
4280         gamenum = ics_gamenum;
4281         moveNum = 0;            /* old ICS bug workaround */
4282         if (to_play == 'B') {
4283           startedFromSetupPosition = TRUE;
4284           blackPlaysFirst = TRUE;
4285           moveNum = 1;
4286           if (forwardMostMove == 0) forwardMostMove = 1;
4287           if (backwardMostMove == 0) backwardMostMove = 1;
4288           if (currentMove == 0) currentMove = 1;
4289         }
4290         newGameMode = gameMode;
4291         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4292         break;
4293       case H_GOT_UNWANTED_HEADER:
4294         /* This is an initial board that we don't want */
4295         return;
4296       case H_GETTING_MOVES:
4297         /* Should not happen */
4298         DisplayError(_("Error gathering move list: extra board"), 0);
4299         ics_getting_history = H_FALSE;
4300         return;
4301     }
4302
4303    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4304                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4305      /* [HGM] We seem to have switched variant unexpectedly
4306       * Try to guess new variant from board size
4307       */
4308           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4309           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4310           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4311           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4312           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4313           if(!weird) newVariant = VariantNormal;
4314           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4315           /* Get a move list just to see the header, which
4316              will tell us whether this is really bug or zh */
4317           if (ics_getting_history == H_FALSE) {
4318             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321           }
4322     }
4323
4324     /* Take action if this is the first board of a new game, or of a
4325        different game than is currently being displayed.  */
4326     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4327         relation == RELATION_ISOLATED_BOARD) {
4328
4329         /* Forget the old game and get the history (if any) of the new one */
4330         if (gameMode != BeginningOfGame) {
4331           Reset(TRUE, TRUE);
4332         }
4333         newGame = TRUE;
4334         if (appData.autoRaiseBoard) BoardToTop();
4335         prevMove = -3;
4336         if (gamenum == -1) {
4337             newGameMode = IcsIdle;
4338         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4339                    appData.getMoveList && !reqFlag) {
4340             /* Need to get game history */
4341             ics_getting_history = H_REQUESTED;
4342             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4343             SendToICS(str);
4344         }
4345
4346         /* Initially flip the board to have black on the bottom if playing
4347            black or if the ICS flip flag is set, but let the user change
4348            it with the Flip View button. */
4349         flipView = appData.autoFlipView ?
4350           (newGameMode == IcsPlayingBlack) || ics_flip :
4351           appData.flipView;
4352
4353         /* Done with values from previous mode; copy in new ones */
4354         gameMode = newGameMode;
4355         ModeHighlight();
4356         ics_gamenum = gamenum;
4357         if (gamenum == gs_gamenum) {
4358             int klen = strlen(gs_kind);
4359             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4360             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4361             gameInfo.event = StrSave(str);
4362         } else {
4363             gameInfo.event = StrSave("ICS game");
4364         }
4365         gameInfo.site = StrSave(appData.icsHost);
4366         gameInfo.date = PGNDate();
4367         gameInfo.round = StrSave("-");
4368         gameInfo.white = StrSave(white);
4369         gameInfo.black = StrSave(black);
4370         timeControl = basetime * 60 * 1000;
4371         timeControl_2 = 0;
4372         timeIncrement = increment * 1000;
4373         movesPerSession = 0;
4374         gameInfo.timeControl = TimeControlTagValue();
4375         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4376   if (appData.debugMode) {
4377     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4378     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4379     setbuf(debugFP, NULL);
4380   }
4381
4382         gameInfo.outOfBook = NULL;
4383
4384         /* Do we have the ratings? */
4385         if (strcmp(player1Name, white) == 0 &&
4386             strcmp(player2Name, black) == 0) {
4387             if (appData.debugMode)
4388               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4389                       player1Rating, player2Rating);
4390             gameInfo.whiteRating = player1Rating;
4391             gameInfo.blackRating = player2Rating;
4392         } else if (strcmp(player2Name, white) == 0 &&
4393                    strcmp(player1Name, black) == 0) {
4394             if (appData.debugMode)
4395               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4396                       player2Rating, player1Rating);
4397             gameInfo.whiteRating = player2Rating;
4398             gameInfo.blackRating = player1Rating;
4399         }
4400         player1Name[0] = player2Name[0] = NULLCHAR;
4401
4402         /* Silence shouts if requested */
4403         if (appData.quietPlay &&
4404             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4405             SendToICS(ics_prefix);
4406             SendToICS("set shout 0\n");
4407         }
4408     }
4409
4410     /* Deal with midgame name changes */
4411     if (!newGame) {
4412         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4413             if (gameInfo.white) free(gameInfo.white);
4414             gameInfo.white = StrSave(white);
4415         }
4416         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4417             if (gameInfo.black) free(gameInfo.black);
4418             gameInfo.black = StrSave(black);
4419         }
4420     }
4421
4422     /* Throw away game result if anything actually changes in examine mode */
4423     if (gameMode == IcsExamining && !newGame) {
4424         gameInfo.result = GameUnfinished;
4425         if (gameInfo.resultDetails != NULL) {
4426             free(gameInfo.resultDetails);
4427             gameInfo.resultDetails = NULL;
4428         }
4429     }
4430
4431     /* In pausing && IcsExamining mode, we ignore boards coming
4432        in if they are in a different variation than we are. */
4433     if (pauseExamInvalid) return;
4434     if (pausing && gameMode == IcsExamining) {
4435         if (moveNum <= pauseExamForwardMostMove) {
4436             pauseExamInvalid = TRUE;
4437             forwardMostMove = pauseExamForwardMostMove;
4438             return;
4439         }
4440     }
4441
4442   if (appData.debugMode) {
4443     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4444   }
4445     /* Parse the board */
4446     for (k = 0; k < ranks; k++) {
4447       for (j = 0; j < files; j++)
4448         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4449       if(gameInfo.holdingsWidth > 1) {
4450            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4451            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4452       }
4453     }
4454     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4455       board[5][BOARD_RGHT+1] = WhiteAngel;
4456       board[6][BOARD_RGHT+1] = WhiteMarshall;
4457       board[1][0] = BlackMarshall;
4458       board[2][0] = BlackAngel;
4459       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4460     }
4461     CopyBoard(boards[moveNum], board);
4462     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4463     if (moveNum == 0) {
4464         startedFromSetupPosition =
4465           !CompareBoards(board, initialPosition);
4466         if(startedFromSetupPosition)
4467             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4468     }
4469
4470     /* [HGM] Set castling rights. Take the outermost Rooks,
4471        to make it also work for FRC opening positions. Note that board12
4472        is really defective for later FRC positions, as it has no way to
4473        indicate which Rook can castle if they are on the same side of King.
4474        For the initial position we grant rights to the outermost Rooks,
4475        and remember thos rights, and we then copy them on positions
4476        later in an FRC game. This means WB might not recognize castlings with
4477        Rooks that have moved back to their original position as illegal,
4478        but in ICS mode that is not its job anyway.
4479     */
4480     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4481     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4482
4483         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4484             if(board[0][i] == WhiteRook) j = i;
4485         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4486         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4487             if(board[0][i] == WhiteRook) j = i;
4488         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4489         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4490             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4491         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4492         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4493             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4494         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495
4496         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4497         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4498         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4499             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4500         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4501             if(board[BOARD_HEIGHT-1][k] == bKing)
4502                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4503         if(gameInfo.variant == VariantTwoKings) {
4504             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4505             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4506             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4507         }
4508     } else { int r;
4509         r = boards[moveNum][CASTLING][0] = initialRights[0];
4510         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4511         r = boards[moveNum][CASTLING][1] = initialRights[1];
4512         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4513         r = boards[moveNum][CASTLING][3] = initialRights[3];
4514         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4515         r = boards[moveNum][CASTLING][4] = initialRights[4];
4516         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4517         /* wildcastle kludge: always assume King has rights */
4518         r = boards[moveNum][CASTLING][2] = initialRights[2];
4519         r = boards[moveNum][CASTLING][5] = initialRights[5];
4520     }
4521     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4522     boards[moveNum][EP_STATUS] = EP_NONE;
4523     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4524     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4525     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4526
4527
4528     if (ics_getting_history == H_GOT_REQ_HEADER ||
4529         ics_getting_history == H_GOT_UNREQ_HEADER) {
4530         /* This was an initial position from a move list, not
4531            the current position */
4532         return;
4533     }
4534
4535     /* Update currentMove and known move number limits */
4536     newMove = newGame || moveNum > forwardMostMove;
4537
4538     if (newGame) {
4539         forwardMostMove = backwardMostMove = currentMove = moveNum;
4540         if (gameMode == IcsExamining && moveNum == 0) {
4541           /* Workaround for ICS limitation: we are not told the wild
4542              type when starting to examine a game.  But if we ask for
4543              the move list, the move list header will tell us */
4544             ics_getting_history = H_REQUESTED;
4545             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4546             SendToICS(str);
4547         }
4548     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4549                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4550 #if ZIPPY
4551         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4552         /* [HGM] applied this also to an engine that is silently watching        */
4553         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4554             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4555             gameInfo.variant == currentlyInitializedVariant) {
4556           takeback = forwardMostMove - moveNum;
4557           for (i = 0; i < takeback; i++) {
4558             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4559             SendToProgram("undo\n", &first);
4560           }
4561         }
4562 #endif
4563
4564         forwardMostMove = moveNum;
4565         if (!pausing || currentMove > forwardMostMove)
4566           currentMove = forwardMostMove;
4567     } else {
4568         /* New part of history that is not contiguous with old part */
4569         if (pausing && gameMode == IcsExamining) {
4570             pauseExamInvalid = TRUE;
4571             forwardMostMove = pauseExamForwardMostMove;
4572             return;
4573         }
4574         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4575 #if ZIPPY
4576             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4577                 // [HGM] when we will receive the move list we now request, it will be
4578                 // fed to the engine from the first move on. So if the engine is not
4579                 // in the initial position now, bring it there.
4580                 InitChessProgram(&first, 0);
4581             }
4582 #endif
4583             ics_getting_history = H_REQUESTED;
4584             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4585             SendToICS(str);
4586         }
4587         forwardMostMove = backwardMostMove = currentMove = moveNum;
4588     }
4589
4590     /* Update the clocks */
4591     if (strchr(elapsed_time, '.')) {
4592       /* Time is in ms */
4593       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4594       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4595     } else {
4596       /* Time is in seconds */
4597       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4598       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4599     }
4600
4601
4602 #if ZIPPY
4603     if (appData.zippyPlay && newGame &&
4604         gameMode != IcsObserving && gameMode != IcsIdle &&
4605         gameMode != IcsExamining)
4606       ZippyFirstBoard(moveNum, basetime, increment);
4607 #endif
4608
4609     /* Put the move on the move list, first converting
4610        to canonical algebraic form. */
4611     if (moveNum > 0) {
4612   if (appData.debugMode) {
4613     if (appData.debugMode) { int f = forwardMostMove;
4614         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4615                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4616                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4617     }
4618     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4619     fprintf(debugFP, "moveNum = %d\n", moveNum);
4620     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4621     setbuf(debugFP, NULL);
4622   }
4623         if (moveNum <= backwardMostMove) {
4624             /* We don't know what the board looked like before
4625                this move.  Punt. */
4626           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4627             strcat(parseList[moveNum - 1], " ");
4628             strcat(parseList[moveNum - 1], elapsed_time);
4629             moveList[moveNum - 1][0] = NULLCHAR;
4630         } else if (strcmp(move_str, "none") == 0) {
4631             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4632             /* Again, we don't know what the board looked like;
4633                this is really the start of the game. */
4634             parseList[moveNum - 1][0] = NULLCHAR;
4635             moveList[moveNum - 1][0] = NULLCHAR;
4636             backwardMostMove = moveNum;
4637             startedFromSetupPosition = TRUE;
4638             fromX = fromY = toX = toY = -1;
4639         } else {
4640           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4641           //                 So we parse the long-algebraic move string in stead of the SAN move
4642           int valid; char buf[MSG_SIZ], *prom;
4643
4644           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4645                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4646           // str looks something like "Q/a1-a2"; kill the slash
4647           if(str[1] == '/')
4648             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4649           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4650           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4651                 strcat(buf, prom); // long move lacks promo specification!
4652           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4653                 if(appData.debugMode)
4654                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4655                 safeStrCpy(move_str, buf, MSG_SIZ);
4656           }
4657           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4658                                 &fromX, &fromY, &toX, &toY, &promoChar)
4659                || ParseOneMove(buf, moveNum - 1, &moveType,
4660                                 &fromX, &fromY, &toX, &toY, &promoChar);
4661           // end of long SAN patch
4662           if (valid) {
4663             (void) CoordsToAlgebraic(boards[moveNum - 1],
4664                                      PosFlags(moveNum - 1),
4665                                      fromY, fromX, toY, toX, promoChar,
4666                                      parseList[moveNum-1]);
4667             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4668               case MT_NONE:
4669               case MT_STALEMATE:
4670               default:
4671                 break;
4672               case MT_CHECK:
4673                 if(gameInfo.variant != VariantShogi)
4674                     strcat(parseList[moveNum - 1], "+");
4675                 break;
4676               case MT_CHECKMATE:
4677               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4678                 strcat(parseList[moveNum - 1], "#");
4679                 break;
4680             }
4681             strcat(parseList[moveNum - 1], " ");
4682             strcat(parseList[moveNum - 1], elapsed_time);
4683             /* currentMoveString is set as a side-effect of ParseOneMove */
4684             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4685             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4686             strcat(moveList[moveNum - 1], "\n");
4687
4688             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4689                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4690               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4691                 ChessSquare old, new = boards[moveNum][k][j];
4692                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4693                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4694                   if(old == new) continue;
4695                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4696                   else if(new == WhiteWazir || new == BlackWazir) {
4697                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4698                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4699                       else boards[moveNum][k][j] = old; // preserve type of Gold
4700                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4701                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4702               }
4703           } else {
4704             /* Move from ICS was illegal!?  Punt. */
4705             if (appData.debugMode) {
4706               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4707               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4708             }
4709             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4710             strcat(parseList[moveNum - 1], " ");
4711             strcat(parseList[moveNum - 1], elapsed_time);
4712             moveList[moveNum - 1][0] = NULLCHAR;
4713             fromX = fromY = toX = toY = -1;
4714           }
4715         }
4716   if (appData.debugMode) {
4717     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4718     setbuf(debugFP, NULL);
4719   }
4720
4721 #if ZIPPY
4722         /* Send move to chess program (BEFORE animating it). */
4723         if (appData.zippyPlay && !newGame && newMove &&
4724            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4725
4726             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4727                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4728                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4729                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4730                             move_str);
4731                     DisplayError(str, 0);
4732                 } else {
4733                     if (first.sendTime) {
4734                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4735                     }
4736                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4737                     if (firstMove && !bookHit) {
4738                         firstMove = FALSE;
4739                         if (first.useColors) {
4740                           SendToProgram(gameMode == IcsPlayingWhite ?
4741                                         "white\ngo\n" :
4742                                         "black\ngo\n", &first);
4743                         } else {
4744                           SendToProgram("go\n", &first);
4745                         }
4746                         first.maybeThinking = TRUE;
4747                     }
4748                 }
4749             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4750               if (moveList[moveNum - 1][0] == NULLCHAR) {
4751                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4752                 DisplayError(str, 0);
4753               } else {
4754                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4755                 SendMoveToProgram(moveNum - 1, &first);
4756               }
4757             }
4758         }
4759 #endif
4760     }
4761
4762     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4763         /* If move comes from a remote source, animate it.  If it
4764            isn't remote, it will have already been animated. */
4765         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4766             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4767         }
4768         if (!pausing && appData.highlightLastMove) {
4769             SetHighlights(fromX, fromY, toX, toY);
4770         }
4771     }
4772
4773     /* Start the clocks */
4774     whiteFlag = blackFlag = FALSE;
4775     appData.clockMode = !(basetime == 0 && increment == 0);
4776     if (ticking == 0) {
4777       ics_clock_paused = TRUE;
4778       StopClocks();
4779     } else if (ticking == 1) {
4780       ics_clock_paused = FALSE;
4781     }
4782     if (gameMode == IcsIdle ||
4783         relation == RELATION_OBSERVING_STATIC ||
4784         relation == RELATION_EXAMINING ||
4785         ics_clock_paused)
4786       DisplayBothClocks();
4787     else
4788       StartClocks();
4789
4790     /* Display opponents and material strengths */
4791     if (gameInfo.variant != VariantBughouse &&
4792         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4793         if (tinyLayout || smallLayout) {
4794             if(gameInfo.variant == VariantNormal)
4795               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4796                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4797                     basetime, increment);
4798             else
4799               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4800                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4801                     basetime, increment, (int) gameInfo.variant);
4802         } else {
4803             if(gameInfo.variant == VariantNormal)
4804               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4805                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4806                     basetime, increment);
4807             else
4808               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4809                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4810                     basetime, increment, VariantName(gameInfo.variant));
4811         }
4812         DisplayTitle(str);
4813   if (appData.debugMode) {
4814     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4815   }
4816     }
4817
4818
4819     /* Display the board */
4820     if (!pausing && !appData.noGUI) {
4821
4822       if (appData.premove)
4823           if (!gotPremove ||
4824              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4825              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4826               ClearPremoveHighlights();
4827
4828       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4829         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4830       DrawPosition(j, boards[currentMove]);
4831
4832       DisplayMove(moveNum - 1);
4833       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4834             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4835               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4836         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4837       }
4838     }
4839
4840     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4841 #if ZIPPY
4842     if(bookHit) { // [HGM] book: simulate book reply
4843         static char bookMove[MSG_SIZ]; // a bit generous?
4844
4845         programStats.nodes = programStats.depth = programStats.time =
4846         programStats.score = programStats.got_only_move = 0;
4847         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4848
4849         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4850         strcat(bookMove, bookHit);
4851         HandleMachineMove(bookMove, &first);
4852     }
4853 #endif
4854 }
4855
4856 void
4857 GetMoveListEvent ()
4858 {
4859     char buf[MSG_SIZ];
4860     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4861         ics_getting_history = H_REQUESTED;
4862         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4863         SendToICS(buf);
4864     }
4865 }
4866
4867 void
4868 AnalysisPeriodicEvent (int force)
4869 {
4870     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4871          && !force) || !appData.periodicUpdates)
4872       return;
4873
4874     /* Send . command to Crafty to collect stats */
4875     SendToProgram(".\n", &first);
4876
4877     /* Don't send another until we get a response (this makes
4878        us stop sending to old Crafty's which don't understand
4879        the "." command (sending illegal cmds resets node count & time,
4880        which looks bad)) */
4881     programStats.ok_to_send = 0;
4882 }
4883
4884 void
4885 ics_update_width (int new_width)
4886 {
4887         ics_printf("set width %d\n", new_width);
4888 }
4889
4890 void
4891 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4892 {
4893     char buf[MSG_SIZ];
4894
4895     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4896         // null move in variant where engine does not understand it (for analysis purposes)
4897         SendBoard(cps, moveNum + 1); // send position after move in stead.
4898         return;
4899     }
4900     if (cps->useUsermove) {
4901       SendToProgram("usermove ", cps);
4902     }
4903     if (cps->useSAN) {
4904       char *space;
4905       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4906         int len = space - parseList[moveNum];
4907         memcpy(buf, parseList[moveNum], len);
4908         buf[len++] = '\n';
4909         buf[len] = NULLCHAR;
4910       } else {
4911         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4912       }
4913       SendToProgram(buf, cps);
4914     } else {
4915       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4916         AlphaRank(moveList[moveNum], 4);
4917         SendToProgram(moveList[moveNum], cps);
4918         AlphaRank(moveList[moveNum], 4); // and back
4919       } else
4920       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4921        * the engine. It would be nice to have a better way to identify castle
4922        * moves here. */
4923       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4924                                                                          && cps->useOOCastle) {
4925         int fromX = moveList[moveNum][0] - AAA;
4926         int fromY = moveList[moveNum][1] - ONE;
4927         int toX = moveList[moveNum][2] - AAA;
4928         int toY = moveList[moveNum][3] - ONE;
4929         if((boards[moveNum][fromY][fromX] == WhiteKing
4930             && boards[moveNum][toY][toX] == WhiteRook)
4931            || (boards[moveNum][fromY][fromX] == BlackKing
4932                && boards[moveNum][toY][toX] == BlackRook)) {
4933           if(toX > fromX) SendToProgram("O-O\n", cps);
4934           else SendToProgram("O-O-O\n", cps);
4935         }
4936         else SendToProgram(moveList[moveNum], cps);
4937       } else
4938       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4939         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4940           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4941           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4942                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4943         } else
4944           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4945                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4946         SendToProgram(buf, cps);
4947       }
4948       else SendToProgram(moveList[moveNum], cps);
4949       /* End of additions by Tord */
4950     }
4951
4952     /* [HGM] setting up the opening has brought engine in force mode! */
4953     /*       Send 'go' if we are in a mode where machine should play. */
4954     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4955         (gameMode == TwoMachinesPlay   ||
4956 #if ZIPPY
4957          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4958 #endif
4959          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4960         SendToProgram("go\n", cps);
4961   if (appData.debugMode) {
4962     fprintf(debugFP, "(extra)\n");
4963   }
4964     }
4965     setboardSpoiledMachineBlack = 0;
4966 }
4967
4968 void
4969 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4970 {
4971     char user_move[MSG_SIZ];
4972     char suffix[4];
4973
4974     if(gameInfo.variant == VariantSChess && promoChar) {
4975         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4976         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4977     } else suffix[0] = NULLCHAR;
4978
4979     switch (moveType) {
4980       default:
4981         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4982                 (int)moveType, fromX, fromY, toX, toY);
4983         DisplayError(user_move + strlen("say "), 0);
4984         break;
4985       case WhiteKingSideCastle:
4986       case BlackKingSideCastle:
4987       case WhiteQueenSideCastleWild:
4988       case BlackQueenSideCastleWild:
4989       /* PUSH Fabien */
4990       case WhiteHSideCastleFR:
4991       case BlackHSideCastleFR:
4992       /* POP Fabien */
4993         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4994         break;
4995       case WhiteQueenSideCastle:
4996       case BlackQueenSideCastle:
4997       case WhiteKingSideCastleWild:
4998       case BlackKingSideCastleWild:
4999       /* PUSH Fabien */
5000       case WhiteASideCastleFR:
5001       case BlackASideCastleFR:
5002       /* POP Fabien */
5003         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5004         break;
5005       case WhiteNonPromotion:
5006       case BlackNonPromotion:
5007         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5008         break;
5009       case WhitePromotion:
5010       case BlackPromotion:
5011         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5012           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5013                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5014                 PieceToChar(WhiteFerz));
5015         else if(gameInfo.variant == VariantGreat)
5016           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5017                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5018                 PieceToChar(WhiteMan));
5019         else
5020           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5021                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5022                 promoChar);
5023         break;
5024       case WhiteDrop:
5025       case BlackDrop:
5026       drop:
5027         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5028                  ToUpper(PieceToChar((ChessSquare) fromX)),
5029                  AAA + toX, ONE + toY);
5030         break;
5031       case IllegalMove:  /* could be a variant we don't quite understand */
5032         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5033       case NormalMove:
5034       case WhiteCapturesEnPassant:
5035       case BlackCapturesEnPassant:
5036         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5037                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5038         break;
5039     }
5040     SendToICS(user_move);
5041     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5042         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5043 }
5044
5045 void
5046 UploadGameEvent ()
5047 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5048     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5049     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5050     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5051       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5052       return;
5053     }
5054     if(gameMode != IcsExamining) { // is this ever not the case?
5055         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5056
5057         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5058           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5059         } else { // on FICS we must first go to general examine mode
5060           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5061         }
5062         if(gameInfo.variant != VariantNormal) {
5063             // try figure out wild number, as xboard names are not always valid on ICS
5064             for(i=1; i<=36; i++) {
5065               snprintf(buf, MSG_SIZ, "wild/%d", i);
5066                 if(StringToVariant(buf) == gameInfo.variant) break;
5067             }
5068             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5069             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5070             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5071         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5072         SendToICS(ics_prefix);
5073         SendToICS(buf);
5074         if(startedFromSetupPosition || backwardMostMove != 0) {
5075           fen = PositionToFEN(backwardMostMove, NULL);
5076           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5077             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5078             SendToICS(buf);
5079           } else { // FICS: everything has to set by separate bsetup commands
5080             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5081             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5082             SendToICS(buf);
5083             if(!WhiteOnMove(backwardMostMove)) {
5084                 SendToICS("bsetup tomove black\n");
5085             }
5086             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5087             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5088             SendToICS(buf);
5089             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5090             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5091             SendToICS(buf);
5092             i = boards[backwardMostMove][EP_STATUS];
5093             if(i >= 0) { // set e.p.
5094               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5095                 SendToICS(buf);
5096             }
5097             bsetup++;
5098           }
5099         }
5100       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5101             SendToICS("bsetup done\n"); // switch to normal examining.
5102     }
5103     for(i = backwardMostMove; i<last; i++) {
5104         char buf[20];
5105         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5106         SendToICS(buf);
5107     }
5108     SendToICS(ics_prefix);
5109     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5110 }
5111
5112 void
5113 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5114 {
5115     if (rf == DROP_RANK) {
5116       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5117       sprintf(move, "%c@%c%c\n",
5118                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5119     } else {
5120         if (promoChar == 'x' || promoChar == NULLCHAR) {
5121           sprintf(move, "%c%c%c%c\n",
5122                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5123         } else {
5124             sprintf(move, "%c%c%c%c%c\n",
5125                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5126         }
5127     }
5128 }
5129
5130 void
5131 ProcessICSInitScript (FILE *f)
5132 {
5133     char buf[MSG_SIZ];
5134
5135     while (fgets(buf, MSG_SIZ, f)) {
5136         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5137     }
5138
5139     fclose(f);
5140 }
5141
5142
5143 static int lastX, lastY, selectFlag, dragging;
5144
5145 void
5146 Sweep (int step)
5147 {
5148     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5149     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5150     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5151     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5152     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5153     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5154     do {
5155         promoSweep -= step;
5156         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5157         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5158         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5159         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5162             appData.testLegality && (promoSweep == king ||
5163             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5164     ChangeDragPiece(promoSweep);
5165 }
5166
5167 int
5168 PromoScroll (int x, int y)
5169 {
5170   int step = 0;
5171
5172   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5173   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5174   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5175   if(!step) return FALSE;
5176   lastX = x; lastY = y;
5177   if((promoSweep < BlackPawn) == flipView) step = -step;
5178   if(step > 0) selectFlag = 1;
5179   if(!selectFlag) Sweep(step);
5180   return FALSE;
5181 }
5182
5183 void
5184 NextPiece (int step)
5185 {
5186     ChessSquare piece = boards[currentMove][toY][toX];
5187     do {
5188         pieceSweep -= step;
5189         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5190         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5191         if(!step) step = -1;
5192     } while(PieceToChar(pieceSweep) == '.');
5193     boards[currentMove][toY][toX] = pieceSweep;
5194     DrawPosition(FALSE, boards[currentMove]);
5195     boards[currentMove][toY][toX] = piece;
5196 }
5197 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5198 void
5199 AlphaRank (char *move, int n)
5200 {
5201 //    char *p = move, c; int x, y;
5202
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5205     }
5206
5207     if(move[1]=='*' &&
5208        move[2]>='0' && move[2]<='9' &&
5209        move[3]>='a' && move[3]<='x'    ) {
5210         move[1] = '@';
5211         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5212         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5213     } else
5214     if(move[0]>='0' && move[0]<='9' &&
5215        move[1]>='a' && move[1]<='x' &&
5216        move[2]>='0' && move[2]<='9' &&
5217        move[3]>='a' && move[3]<='x'    ) {
5218         /* input move, Shogi -> normal */
5219         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5220         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5221         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5222         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5223     } else
5224     if(move[1]=='@' &&
5225        move[3]>='0' && move[3]<='9' &&
5226        move[2]>='a' && move[2]<='x'    ) {
5227         move[1] = '*';
5228         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5229         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5230     } else
5231     if(
5232        move[0]>='a' && move[0]<='x' &&
5233        move[3]>='0' && move[3]<='9' &&
5234        move[2]>='a' && move[2]<='x'    ) {
5235          /* output move, normal -> Shogi */
5236         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5237         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5238         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5239         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5240         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5241     }
5242     if (appData.debugMode) {
5243         fprintf(debugFP, "   out = '%s'\n", move);
5244     }
5245 }
5246
5247 char yy_textstr[8000];
5248
5249 /* Parser for moves from gnuchess, ICS, or user typein box */
5250 Boolean
5251 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5252 {
5253     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5254
5255     switch (*moveType) {
5256       case WhitePromotion:
5257       case BlackPromotion:
5258       case WhiteNonPromotion:
5259       case BlackNonPromotion:
5260       case NormalMove:
5261       case WhiteCapturesEnPassant:
5262       case BlackCapturesEnPassant:
5263       case WhiteKingSideCastle:
5264       case WhiteQueenSideCastle:
5265       case BlackKingSideCastle:
5266       case BlackQueenSideCastle:
5267       case WhiteKingSideCastleWild:
5268       case WhiteQueenSideCastleWild:
5269       case BlackKingSideCastleWild:
5270       case BlackQueenSideCastleWild:
5271       /* Code added by Tord: */
5272       case WhiteHSideCastleFR:
5273       case WhiteASideCastleFR:
5274       case BlackHSideCastleFR:
5275       case BlackASideCastleFR:
5276       /* End of code added by Tord */
5277       case IllegalMove:         /* bug or odd chess variant */
5278         *fromX = currentMoveString[0] - AAA;
5279         *fromY = currentMoveString[1] - ONE;
5280         *toX = currentMoveString[2] - AAA;
5281         *toY = currentMoveString[3] - ONE;
5282         *promoChar = currentMoveString[4];
5283         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5284             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5287     }
5288             *fromX = *fromY = *toX = *toY = 0;
5289             return FALSE;
5290         }
5291         if (appData.testLegality) {
5292           return (*moveType != IllegalMove);
5293         } else {
5294           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5295                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5296         }
5297
5298       case WhiteDrop:
5299       case BlackDrop:
5300         *fromX = *moveType == WhiteDrop ?
5301           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5302           (int) CharToPiece(ToLower(currentMoveString[0]));
5303         *fromY = DROP_RANK;
5304         *toX = currentMoveString[2] - AAA;
5305         *toY = currentMoveString[3] - ONE;
5306         *promoChar = NULLCHAR;
5307         return TRUE;
5308
5309       case AmbiguousMove:
5310       case ImpossibleMove:
5311       case EndOfFile:
5312       case ElapsedTime:
5313       case Comment:
5314       case PGNTag:
5315       case NAG:
5316       case WhiteWins:
5317       case BlackWins:
5318       case GameIsDrawn:
5319       default:
5320     if (appData.debugMode) {
5321         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5322     }
5323         /* bug? */
5324         *fromX = *fromY = *toX = *toY = 0;
5325         *promoChar = NULLCHAR;
5326         return FALSE;
5327     }
5328 }
5329
5330 Boolean pushed = FALSE;
5331 char *lastParseAttempt;
5332
5333 void
5334 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5335 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5336   int fromX, fromY, toX, toY; char promoChar;
5337   ChessMove moveType;
5338   Boolean valid;
5339   int nr = 0;
5340
5341   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5342     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5343     pushed = TRUE;
5344   }
5345   endPV = forwardMostMove;
5346   do {
5347     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5348     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5349     lastParseAttempt = pv;
5350     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5351     if(!valid && nr == 0 &&
5352        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5353         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5354         // Hande case where played move is different from leading PV move
5355         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5356         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5357         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5358         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5359           endPV += 2; // if position different, keep this
5360           moveList[endPV-1][0] = fromX + AAA;
5361           moveList[endPV-1][1] = fromY + ONE;
5362           moveList[endPV-1][2] = toX + AAA;
5363           moveList[endPV-1][3] = toY + ONE;
5364           parseList[endPV-1][0] = NULLCHAR;
5365           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5366         }
5367       }
5368     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5369     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5370     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5371     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5372         valid++; // allow comments in PV
5373         continue;
5374     }
5375     nr++;
5376     if(endPV+1 > framePtr) break; // no space, truncate
5377     if(!valid) break;
5378     endPV++;
5379     CopyBoard(boards[endPV], boards[endPV-1]);
5380     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5381     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5382     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5383     CoordsToAlgebraic(boards[endPV - 1],
5384                              PosFlags(endPV - 1),
5385                              fromY, fromX, toY, toX, promoChar,
5386                              parseList[endPV - 1]);
5387   } while(valid);
5388   if(atEnd == 2) return; // used hidden, for PV conversion
5389   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5390   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5391   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5392                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5393   DrawPosition(TRUE, boards[currentMove]);
5394 }
5395
5396 int
5397 MultiPV (ChessProgramState *cps)
5398 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5399         int i;
5400         for(i=0; i<cps->nrOptions; i++)
5401             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5402                 return i;
5403         return -1;
5404 }
5405
5406 Boolean
5407 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5408 {
5409         int startPV, multi, lineStart, origIndex = index;
5410         char *p, buf2[MSG_SIZ];
5411
5412         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5413         lastX = x; lastY = y;
5414         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5415         lineStart = startPV = index;
5416         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5417         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5418         index = startPV;
5419         do{ while(buf[index] && buf[index] != '\n') index++;
5420         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5421         buf[index] = 0;
5422         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5423                 int n = first.option[multi].value;
5424                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5425                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5426                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5427                 first.option[multi].value = n;
5428                 *start = *end = 0;
5429                 return FALSE;
5430         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5431                 ExcludeClick(origIndex - lineStart);
5432                 return FALSE;
5433         }
5434         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5435         *start = startPV; *end = index-1;
5436         return TRUE;
5437 }
5438
5439 char *
5440 PvToSAN (char *pv)
5441 {
5442         static char buf[10*MSG_SIZ];
5443         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5444         *buf = NULLCHAR;
5445         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5446         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5447         for(i = forwardMostMove; i<endPV; i++){
5448             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5449             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5450             k += strlen(buf+k);
5451         }
5452         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5453         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5454         endPV = savedEnd;
5455         return buf;
5456 }
5457
5458 Boolean
5459 LoadPV (int x, int y)
5460 { // called on right mouse click to load PV
5461   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5462   lastX = x; lastY = y;
5463   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5464   return TRUE;
5465 }
5466
5467 void
5468 UnLoadPV ()
5469 {
5470   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5471   if(endPV < 0) return;
5472   if(appData.autoCopyPV) CopyFENToClipboard();
5473   endPV = -1;
5474   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5475         Boolean saveAnimate = appData.animate;
5476         if(pushed) {
5477             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5478                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5479             } else storedGames--; // abandon shelved tail of original game
5480         }
5481         pushed = FALSE;
5482         forwardMostMove = currentMove;
5483         currentMove = oldFMM;
5484         appData.animate = FALSE;
5485         ToNrEvent(forwardMostMove);
5486         appData.animate = saveAnimate;
5487   }
5488   currentMove = forwardMostMove;
5489   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5490   ClearPremoveHighlights();
5491   DrawPosition(TRUE, boards[currentMove]);
5492 }
5493
5494 void
5495 MovePV (int x, int y, int h)
5496 { // step through PV based on mouse coordinates (called on mouse move)
5497   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5498
5499   // we must somehow check if right button is still down (might be released off board!)
5500   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5501   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5502   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5503   if(!step) return;
5504   lastX = x; lastY = y;
5505
5506   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5507   if(endPV < 0) return;
5508   if(y < margin) step = 1; else
5509   if(y > h - margin) step = -1;
5510   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5511   currentMove += step;
5512   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5513   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5514                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5515   DrawPosition(FALSE, boards[currentMove]);
5516 }
5517
5518
5519 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5520 // All positions will have equal probability, but the current method will not provide a unique
5521 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5522 #define DARK 1
5523 #define LITE 2
5524 #define ANY 3
5525
5526 int squaresLeft[4];
5527 int piecesLeft[(int)BlackPawn];
5528 int seed, nrOfShuffles;
5529
5530 void
5531 GetPositionNumber ()
5532 {       // sets global variable seed
5533         int i;
5534
5535         seed = appData.defaultFrcPosition;
5536         if(seed < 0) { // randomize based on time for negative FRC position numbers
5537                 for(i=0; i<50; i++) seed += random();
5538                 seed = random() ^ random() >> 8 ^ random() << 8;
5539                 if(seed<0) seed = -seed;
5540         }
5541 }
5542
5543 int
5544 put (Board board, int pieceType, int rank, int n, int shade)
5545 // put the piece on the (n-1)-th empty squares of the given shade
5546 {
5547         int i;
5548
5549         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5550                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5551                         board[rank][i] = (ChessSquare) pieceType;
5552                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5553                         squaresLeft[ANY]--;
5554                         piecesLeft[pieceType]--;
5555                         return i;
5556                 }
5557         }
5558         return -1;
5559 }
5560
5561
5562 void
5563 AddOnePiece (Board board, int pieceType, int rank, int shade)
5564 // calculate where the next piece goes, (any empty square), and put it there
5565 {
5566         int i;
5567
5568         i = seed % squaresLeft[shade];
5569         nrOfShuffles *= squaresLeft[shade];
5570         seed /= squaresLeft[shade];
5571         put(board, pieceType, rank, i, shade);
5572 }
5573
5574 void
5575 AddTwoPieces (Board board, int pieceType, int rank)
5576 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5577 {
5578         int i, n=squaresLeft[ANY], j=n-1, k;
5579
5580         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5581         i = seed % k;  // pick one
5582         nrOfShuffles *= k;
5583         seed /= k;
5584         while(i >= j) i -= j--;
5585         j = n - 1 - j; i += j;
5586         put(board, pieceType, rank, j, ANY);
5587         put(board, pieceType, rank, i, ANY);
5588 }
5589
5590 void
5591 SetUpShuffle (Board board, int number)
5592 {
5593         int i, p, first=1;
5594
5595         GetPositionNumber(); nrOfShuffles = 1;
5596
5597         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5598         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5599         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5600
5601         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5602
5603         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5604             p = (int) board[0][i];
5605             if(p < (int) BlackPawn) piecesLeft[p] ++;
5606             board[0][i] = EmptySquare;
5607         }
5608
5609         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5610             // shuffles restricted to allow normal castling put KRR first
5611             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5612                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5613             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5614                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5615             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5616                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5617             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5618                 put(board, WhiteRook, 0, 0, ANY);
5619             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5620         }
5621
5622         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5623             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5624             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5625                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5626                 while(piecesLeft[p] >= 2) {
5627                     AddOnePiece(board, p, 0, LITE);
5628                     AddOnePiece(board, p, 0, DARK);
5629                 }
5630                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5631             }
5632
5633         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5634             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5635             // but we leave King and Rooks for last, to possibly obey FRC restriction
5636             if(p == (int)WhiteRook) continue;
5637             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5638             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5639         }
5640
5641         // now everything is placed, except perhaps King (Unicorn) and Rooks
5642
5643         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5644             // Last King gets castling rights
5645             while(piecesLeft[(int)WhiteUnicorn]) {
5646                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5647                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5648             }
5649
5650             while(piecesLeft[(int)WhiteKing]) {
5651                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5652                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5653             }
5654
5655
5656         } else {
5657             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5658             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5659         }
5660
5661         // Only Rooks can be left; simply place them all
5662         while(piecesLeft[(int)WhiteRook]) {
5663                 i = put(board, WhiteRook, 0, 0, ANY);
5664                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5665                         if(first) {
5666                                 first=0;
5667                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5668                         }
5669                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5670                 }
5671         }
5672         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5673             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5674         }
5675
5676         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5677 }
5678
5679 int
5680 SetCharTable (char *table, const char * map)
5681 /* [HGM] moved here from winboard.c because of its general usefulness */
5682 /*       Basically a safe strcpy that uses the last character as King */
5683 {
5684     int result = FALSE; int NrPieces;
5685
5686     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5687                     && NrPieces >= 12 && !(NrPieces&1)) {
5688         int i; /* [HGM] Accept even length from 12 to 34 */
5689
5690         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5691         for( i=0; i<NrPieces/2-1; i++ ) {
5692             table[i] = map[i];
5693             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5694         }
5695         table[(int) WhiteKing]  = map[NrPieces/2-1];
5696         table[(int) BlackKing]  = map[NrPieces-1];
5697
5698         result = TRUE;
5699     }
5700
5701     return result;
5702 }
5703
5704 void
5705 Prelude (Board board)
5706 {       // [HGM] superchess: random selection of exo-pieces
5707         int i, j, k; ChessSquare p;
5708         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5709
5710         GetPositionNumber(); // use FRC position number
5711
5712         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5713             SetCharTable(pieceToChar, appData.pieceToCharTable);
5714             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5715                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5716         }
5717
5718         j = seed%4;                 seed /= 4;
5719         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5720         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5721         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5722         j = seed%3 + (seed%3 >= j); seed /= 3;
5723         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5724         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5725         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5726         j = seed%3;                 seed /= 3;
5727         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5728         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5729         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5730         j = seed%2 + (seed%2 >= j); seed /= 2;
5731         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5735         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5736         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5737         put(board, exoPieces[0],    0, 0, ANY);
5738         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5739 }
5740
5741 void
5742 InitPosition (int redraw)
5743 {
5744     ChessSquare (* pieces)[BOARD_FILES];
5745     int i, j, pawnRow, overrule,
5746     oldx = gameInfo.boardWidth,
5747     oldy = gameInfo.boardHeight,
5748     oldh = gameInfo.holdingsWidth;
5749     static int oldv;
5750
5751     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5752
5753     /* [AS] Initialize pv info list [HGM] and game status */
5754     {
5755         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5756             pvInfoList[i].depth = 0;
5757             boards[i][EP_STATUS] = EP_NONE;
5758             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5759         }
5760
5761         initialRulePlies = 0; /* 50-move counter start */
5762
5763         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5764         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5765     }
5766
5767
5768     /* [HGM] logic here is completely changed. In stead of full positions */
5769     /* the initialized data only consist of the two backranks. The switch */
5770     /* selects which one we will use, which is than copied to the Board   */
5771     /* initialPosition, which for the rest is initialized by Pawns and    */
5772     /* empty squares. This initial position is then copied to boards[0],  */
5773     /* possibly after shuffling, so that it remains available.            */
5774
5775     gameInfo.holdingsWidth = 0; /* default board sizes */
5776     gameInfo.boardWidth    = 8;
5777     gameInfo.boardHeight   = 8;
5778     gameInfo.holdingsSize  = 0;
5779     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5780     for(i=0; i<BOARD_FILES-2; i++)
5781       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5782     initialPosition[EP_STATUS] = EP_NONE;
5783     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5784     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5785          SetCharTable(pieceNickName, appData.pieceNickNames);
5786     else SetCharTable(pieceNickName, "............");
5787     pieces = FIDEArray;
5788
5789     switch (gameInfo.variant) {
5790     case VariantFischeRandom:
5791       shuffleOpenings = TRUE;
5792     default:
5793       break;
5794     case VariantShatranj:
5795       pieces = ShatranjArray;
5796       nrCastlingRights = 0;
5797       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5798       break;
5799     case VariantMakruk:
5800       pieces = makrukArray;
5801       nrCastlingRights = 0;
5802       startedFromSetupPosition = TRUE;
5803       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5804       break;
5805     case VariantTwoKings:
5806       pieces = twoKingsArray;
5807       break;
5808     case VariantGrand:
5809       pieces = GrandArray;
5810       nrCastlingRights = 0;
5811       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5812       gameInfo.boardWidth = 10;
5813       gameInfo.boardHeight = 10;
5814       gameInfo.holdingsSize = 7;
5815       break;
5816     case VariantCapaRandom:
5817       shuffleOpenings = TRUE;
5818     case VariantCapablanca:
5819       pieces = CapablancaArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5822       break;
5823     case VariantGothic:
5824       pieces = GothicArray;
5825       gameInfo.boardWidth = 10;
5826       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5827       break;
5828     case VariantSChess:
5829       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5830       gameInfo.holdingsSize = 7;
5831       break;
5832     case VariantJanus:
5833       pieces = JanusArray;
5834       gameInfo.boardWidth = 10;
5835       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5836       nrCastlingRights = 6;
5837         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5838         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5839         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5840         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5841         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5842         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5843       break;
5844     case VariantFalcon:
5845       pieces = FalconArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5848       break;
5849     case VariantXiangqi:
5850       pieces = XiangqiArray;
5851       gameInfo.boardWidth  = 9;
5852       gameInfo.boardHeight = 10;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5855       break;
5856     case VariantShogi:
5857       pieces = ShogiArray;
5858       gameInfo.boardWidth  = 9;
5859       gameInfo.boardHeight = 9;
5860       gameInfo.holdingsSize = 7;
5861       nrCastlingRights = 0;
5862       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5863       break;
5864     case VariantCourier:
5865       pieces = CourierArray;
5866       gameInfo.boardWidth  = 12;
5867       nrCastlingRights = 0;
5868       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5869       break;
5870     case VariantKnightmate:
5871       pieces = KnightmateArray;
5872       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5873       break;
5874     case VariantSpartan:
5875       pieces = SpartanArray;
5876       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5877       break;
5878     case VariantFairy:
5879       pieces = fairyArray;
5880       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5881       break;
5882     case VariantGreat:
5883       pieces = GreatArray;
5884       gameInfo.boardWidth = 10;
5885       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5886       gameInfo.holdingsSize = 8;
5887       break;
5888     case VariantSuper:
5889       pieces = FIDEArray;
5890       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5891       gameInfo.holdingsSize = 8;
5892       startedFromSetupPosition = TRUE;
5893       break;
5894     case VariantCrazyhouse:
5895     case VariantBughouse:
5896       pieces = FIDEArray;
5897       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5898       gameInfo.holdingsSize = 5;
5899       break;
5900     case VariantWildCastle:
5901       pieces = FIDEArray;
5902       /* !!?shuffle with kings guaranteed to be on d or e file */
5903       shuffleOpenings = 1;
5904       break;
5905     case VariantNoCastle:
5906       pieces = FIDEArray;
5907       nrCastlingRights = 0;
5908       /* !!?unconstrained back-rank shuffle */
5909       shuffleOpenings = 1;
5910       break;
5911     }
5912
5913     overrule = 0;
5914     if(appData.NrFiles >= 0) {
5915         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5916         gameInfo.boardWidth = appData.NrFiles;
5917     }
5918     if(appData.NrRanks >= 0) {
5919         gameInfo.boardHeight = appData.NrRanks;
5920     }
5921     if(appData.holdingsSize >= 0) {
5922         i = appData.holdingsSize;
5923         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5924         gameInfo.holdingsSize = i;
5925     }
5926     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5927     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5928         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5929
5930     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5931     if(pawnRow < 1) pawnRow = 1;
5932     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5933
5934     /* User pieceToChar list overrules defaults */
5935     if(appData.pieceToCharTable != NULL)
5936         SetCharTable(pieceToChar, appData.pieceToCharTable);
5937
5938     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5939
5940         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5941             s = (ChessSquare) 0; /* account holding counts in guard band */
5942         for( i=0; i<BOARD_HEIGHT; i++ )
5943             initialPosition[i][j] = s;
5944
5945         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5946         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5947         initialPosition[pawnRow][j] = WhitePawn;
5948         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5949         if(gameInfo.variant == VariantXiangqi) {
5950             if(j&1) {
5951                 initialPosition[pawnRow][j] =
5952                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5953                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5954                    initialPosition[2][j] = WhiteCannon;
5955                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5956                 }
5957             }
5958         }
5959         if(gameInfo.variant == VariantGrand) {
5960             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5961                initialPosition[0][j] = WhiteRook;
5962                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5963             }
5964         }
5965         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5966     }
5967     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5968
5969             j=BOARD_LEFT+1;
5970             initialPosition[1][j] = WhiteBishop;
5971             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5972             j=BOARD_RGHT-2;
5973             initialPosition[1][j] = WhiteRook;
5974             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5975     }
5976
5977     if( nrCastlingRights == -1) {
5978         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5979         /*       This sets default castling rights from none to normal corners   */
5980         /* Variants with other castling rights must set them themselves above    */
5981         nrCastlingRights = 6;
5982
5983         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5984         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5985         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5986         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5987         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5988         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5989      }
5990
5991      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5992      if(gameInfo.variant == VariantGreat) { // promotion commoners
5993         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5994         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5995         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5996         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5997      }
5998      if( gameInfo.variant == VariantSChess ) {
5999       initialPosition[1][0] = BlackMarshall;
6000       initialPosition[2][0] = BlackAngel;
6001       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6002       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6003       initialPosition[1][1] = initialPosition[2][1] = 
6004       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6005      }
6006   if (appData.debugMode) {
6007     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6008   }
6009     if(shuffleOpenings) {
6010         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6011         startedFromSetupPosition = TRUE;
6012     }
6013     if(startedFromPositionFile) {
6014       /* [HGM] loadPos: use PositionFile for every new game */
6015       CopyBoard(initialPosition, filePosition);
6016       for(i=0; i<nrCastlingRights; i++)
6017           initialRights[i] = filePosition[CASTLING][i];
6018       startedFromSetupPosition = TRUE;
6019     }
6020
6021     CopyBoard(boards[0], initialPosition);
6022
6023     if(oldx != gameInfo.boardWidth ||
6024        oldy != gameInfo.boardHeight ||
6025        oldv != gameInfo.variant ||
6026        oldh != gameInfo.holdingsWidth
6027                                          )
6028             InitDrawingSizes(-2 ,0);
6029
6030     oldv = gameInfo.variant;
6031     if (redraw)
6032       DrawPosition(TRUE, boards[currentMove]);
6033 }
6034
6035 void
6036 SendBoard (ChessProgramState *cps, int moveNum)
6037 {
6038     char message[MSG_SIZ];
6039
6040     if (cps->useSetboard) {
6041       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6042       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6043       SendToProgram(message, cps);
6044       free(fen);
6045
6046     } else {
6047       ChessSquare *bp;
6048       int i, j, left=0, right=BOARD_WIDTH;
6049       /* Kludge to set black to move, avoiding the troublesome and now
6050        * deprecated "black" command.
6051        */
6052       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6053         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6054
6055       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6056
6057       SendToProgram("edit\n", cps);
6058       SendToProgram("#\n", cps);
6059       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6060         bp = &boards[moveNum][i][left];
6061         for (j = left; j < right; j++, bp++) {
6062           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6063           if ((int) *bp < (int) BlackPawn) {
6064             if(j == BOARD_RGHT+1)
6065                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6066             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6067             if(message[0] == '+' || message[0] == '~') {
6068               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6069                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6070                         AAA + j, ONE + i);
6071             }
6072             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6073                 message[1] = BOARD_RGHT   - 1 - j + '1';
6074                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6075             }
6076             SendToProgram(message, cps);
6077           }
6078         }
6079       }
6080
6081       SendToProgram("c\n", cps);
6082       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6083         bp = &boards[moveNum][i][left];
6084         for (j = left; j < right; j++, bp++) {
6085           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6086           if (((int) *bp != (int) EmptySquare)
6087               && ((int) *bp >= (int) BlackPawn)) {
6088             if(j == BOARD_LEFT-2)
6089                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6090             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6091                     AAA + j, ONE + i);
6092             if(message[0] == '+' || message[0] == '~') {
6093               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6094                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6095                         AAA + j, ONE + i);
6096             }
6097             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6098                 message[1] = BOARD_RGHT   - 1 - j + '1';
6099                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6100             }
6101             SendToProgram(message, cps);
6102           }
6103         }
6104       }
6105
6106       SendToProgram(".\n", cps);
6107     }
6108     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6109 }
6110
6111 char exclusionHeader[MSG_SIZ];
6112 int exCnt, excludePtr;
6113 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6114 static Exclusion excluTab[200];
6115 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6116
6117 static void
6118 WriteMap (int s)
6119 {
6120     int j;
6121     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6122     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6123 }
6124
6125 static void
6126 ClearMap ()
6127 {
6128     int j;
6129     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6130     excludePtr = 24; exCnt = 0;
6131     WriteMap(0);
6132 }
6133
6134 static void
6135 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6136 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6137     char buf[2*MOVE_LEN], *p;
6138     Exclusion *e = excluTab;
6139     int i;
6140     for(i=0; i<exCnt; i++)
6141         if(e[i].ff == fromX && e[i].fr == fromY &&
6142            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6143     if(i == exCnt) { // was not in exclude list; add it
6144         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6145         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6146             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6147             return; // abort
6148         }
6149         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6150         excludePtr++; e[i].mark = excludePtr++;
6151         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6152         exCnt++;
6153     }
6154     exclusionHeader[e[i].mark] = state;
6155 }
6156
6157 static int
6158 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6159 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6160     char *p, buf[MSG_SIZ];
6161     int j, k;
6162     ChessMove moveType;
6163     if(promoChar == -1) { // kludge to indicate best move
6164         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6165             return 1; // if unparsable, abort
6166     }
6167     // update exclusion map (resolving toggle by consulting existing state)
6168     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6169     j = k%8; k >>= 3;
6170     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6171     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6172          excludeMap[k] |=   1<<j;
6173     else excludeMap[k] &= ~(1<<j);
6174     // update header
6175     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6176     // inform engine
6177     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6178     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6179     SendToProgram(buf, &first);
6180     return (state == '+');
6181 }
6182
6183 static void
6184 ExcludeClick (int index)
6185 {
6186     int i, j;
6187     char buf[MSG_SIZ];
6188     Exclusion *e = excluTab;
6189     if(index < 25) { // none, best or tail clicked
6190         if(index < 13) { // none: include all
6191             WriteMap(0); // clear map
6192             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6193             SendToProgram("include all\n", &first); // and inform engine
6194         } else if(index > 18) { // tail
6195             if(exclusionHeader[19] == '-') { // tail was excluded
6196                 SendToProgram("include all\n", &first);
6197                 WriteMap(0); // clear map completely
6198                 // now re-exclude selected moves
6199                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6200                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6201             } else { // tail was included or in mixed state
6202                 SendToProgram("exclude all\n", &first);
6203                 WriteMap(0xFF); // fill map completely
6204                 // now re-include selected moves
6205                 j = 0; // count them
6206                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6207                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6208                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6209             }
6210         } else { // best
6211             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6212         }
6213     } else {
6214         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6215             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6216             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6217             break;
6218         }
6219     }
6220 }
6221
6222 ChessSquare
6223 DefaultPromoChoice (int white)
6224 {
6225     ChessSquare result;
6226     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6227         result = WhiteFerz; // no choice
6228     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6229         result= WhiteKing; // in Suicide Q is the last thing we want
6230     else if(gameInfo.variant == VariantSpartan)
6231         result = white ? WhiteQueen : WhiteAngel;
6232     else result = WhiteQueen;
6233     if(!white) result = WHITE_TO_BLACK result;
6234     return result;
6235 }
6236
6237 static int autoQueen; // [HGM] oneclick
6238
6239 int
6240 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6241 {
6242     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6243     /* [HGM] add Shogi promotions */
6244     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6245     ChessSquare piece;
6246     ChessMove moveType;
6247     Boolean premove;
6248
6249     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6250     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6251
6252     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6253       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6254         return FALSE;
6255
6256     piece = boards[currentMove][fromY][fromX];
6257     if(gameInfo.variant == VariantShogi) {
6258         promotionZoneSize = BOARD_HEIGHT/3;
6259         highestPromotingPiece = (int)WhiteFerz;
6260     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6261         promotionZoneSize = 3;
6262     }
6263
6264     // Treat Lance as Pawn when it is not representing Amazon
6265     if(gameInfo.variant != VariantSuper) {
6266         if(piece == WhiteLance) piece = WhitePawn; else
6267         if(piece == BlackLance) piece = BlackPawn;
6268     }
6269
6270     // next weed out all moves that do not touch the promotion zone at all
6271     if((int)piece >= BlackPawn) {
6272         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6273              return FALSE;
6274         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6275     } else {
6276         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6277            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6278     }
6279
6280     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6281
6282     // weed out mandatory Shogi promotions
6283     if(gameInfo.variant == VariantShogi) {
6284         if(piece >= BlackPawn) {
6285             if(toY == 0 && piece == BlackPawn ||
6286                toY == 0 && piece == BlackQueen ||
6287                toY <= 1 && piece == BlackKnight) {
6288                 *promoChoice = '+';
6289                 return FALSE;
6290             }
6291         } else {
6292             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6293                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6294                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6295                 *promoChoice = '+';
6296                 return FALSE;
6297             }
6298         }
6299     }
6300
6301     // weed out obviously illegal Pawn moves
6302     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6303         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6304         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6305         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6306         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6307         // note we are not allowed to test for valid (non-)capture, due to premove
6308     }
6309
6310     // we either have a choice what to promote to, or (in Shogi) whether to promote
6311     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6312         *promoChoice = PieceToChar(BlackFerz);  // no choice
6313         return FALSE;
6314     }
6315     // no sense asking what we must promote to if it is going to explode...
6316     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6317         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6318         return FALSE;
6319     }
6320     // give caller the default choice even if we will not make it
6321     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6322     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6323     if(        sweepSelect && gameInfo.variant != VariantGreat
6324                            && gameInfo.variant != VariantGrand
6325                            && gameInfo.variant != VariantSuper) return FALSE;
6326     if(autoQueen) return FALSE; // predetermined
6327
6328     // suppress promotion popup on illegal moves that are not premoves
6329     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6330               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6331     if(appData.testLegality && !premove) {
6332         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6333                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6334         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6335             return FALSE;
6336     }
6337
6338     return TRUE;
6339 }
6340
6341 int
6342 InPalace (int row, int column)
6343 {   /* [HGM] for Xiangqi */
6344     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6345          column < (BOARD_WIDTH + 4)/2 &&
6346          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6347     return FALSE;
6348 }
6349
6350 int
6351 PieceForSquare (int x, int y)
6352 {
6353   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6354      return -1;
6355   else
6356      return boards[currentMove][y][x];
6357 }
6358
6359 int
6360 OKToStartUserMove (int x, int y)
6361 {
6362     ChessSquare from_piece;
6363     int white_piece;
6364
6365     if (matchMode) return FALSE;
6366     if (gameMode == EditPosition) return TRUE;
6367
6368     if (x >= 0 && y >= 0)
6369       from_piece = boards[currentMove][y][x];
6370     else
6371       from_piece = EmptySquare;
6372
6373     if (from_piece == EmptySquare) return FALSE;
6374
6375     white_piece = (int)from_piece >= (int)WhitePawn &&
6376       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6377
6378     switch (gameMode) {
6379       case AnalyzeFile:
6380       case TwoMachinesPlay:
6381       case EndOfGame:
6382         return FALSE;
6383
6384       case IcsObserving:
6385       case IcsIdle:
6386         return FALSE;
6387
6388       case MachinePlaysWhite:
6389       case IcsPlayingBlack:
6390         if (appData.zippyPlay) return FALSE;
6391         if (white_piece) {
6392             DisplayMoveError(_("You are playing Black"));
6393             return FALSE;
6394         }
6395         break;
6396
6397       case MachinePlaysBlack:
6398       case IcsPlayingWhite:
6399         if (appData.zippyPlay) return FALSE;
6400         if (!white_piece) {
6401             DisplayMoveError(_("You are playing White"));
6402             return FALSE;
6403         }
6404         break;
6405
6406       case PlayFromGameFile:
6407             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6408       case EditGame:
6409         if (!white_piece && WhiteOnMove(currentMove)) {
6410             DisplayMoveError(_("It is White's turn"));
6411             return FALSE;
6412         }
6413         if (white_piece && !WhiteOnMove(currentMove)) {
6414             DisplayMoveError(_("It is Black's turn"));
6415             return FALSE;
6416         }
6417         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6418             /* Editing correspondence game history */
6419             /* Could disallow this or prompt for confirmation */
6420             cmailOldMove = -1;
6421         }
6422         break;
6423
6424       case BeginningOfGame:
6425         if (appData.icsActive) return FALSE;
6426         if (!appData.noChessProgram) {
6427             if (!white_piece) {
6428                 DisplayMoveError(_("You are playing White"));
6429                 return FALSE;
6430             }
6431         }
6432         break;
6433
6434       case Training:
6435         if (!white_piece && WhiteOnMove(currentMove)) {
6436             DisplayMoveError(_("It is White's turn"));
6437             return FALSE;
6438         }
6439         if (white_piece && !WhiteOnMove(currentMove)) {
6440             DisplayMoveError(_("It is Black's turn"));
6441             return FALSE;
6442         }
6443         break;
6444
6445       default:
6446       case IcsExamining:
6447         break;
6448     }
6449     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6450         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6451         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6452         && gameMode != AnalyzeFile && gameMode != Training) {
6453         DisplayMoveError(_("Displayed position is not current"));
6454         return FALSE;
6455     }
6456     return TRUE;
6457 }
6458
6459 Boolean
6460 OnlyMove (int *x, int *y, Boolean captures) 
6461 {
6462     DisambiguateClosure cl;
6463     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6464     switch(gameMode) {
6465       case MachinePlaysBlack:
6466       case IcsPlayingWhite:
6467       case BeginningOfGame:
6468         if(!WhiteOnMove(currentMove)) return FALSE;
6469         break;
6470       case MachinePlaysWhite:
6471       case IcsPlayingBlack:
6472         if(WhiteOnMove(currentMove)) return FALSE;
6473         break;
6474       case EditGame:
6475         break;
6476       default:
6477         return FALSE;
6478     }
6479     cl.pieceIn = EmptySquare;
6480     cl.rfIn = *y;
6481     cl.ffIn = *x;
6482     cl.rtIn = -1;
6483     cl.ftIn = -1;
6484     cl.promoCharIn = NULLCHAR;
6485     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6486     if( cl.kind == NormalMove ||
6487         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6488         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6489         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6490       fromX = cl.ff;
6491       fromY = cl.rf;
6492       *x = cl.ft;
6493       *y = cl.rt;
6494       return TRUE;
6495     }
6496     if(cl.kind != ImpossibleMove) return FALSE;
6497     cl.pieceIn = EmptySquare;
6498     cl.rfIn = -1;
6499     cl.ffIn = -1;
6500     cl.rtIn = *y;
6501     cl.ftIn = *x;
6502     cl.promoCharIn = NULLCHAR;
6503     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6504     if( cl.kind == NormalMove ||
6505         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6506         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6507         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6508       fromX = cl.ff;
6509       fromY = cl.rf;
6510       *x = cl.ft;
6511       *y = cl.rt;
6512       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6513       return TRUE;
6514     }
6515     return FALSE;
6516 }
6517
6518 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6519 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6520 int lastLoadGameUseList = FALSE;
6521 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6522 ChessMove lastLoadGameStart = EndOfFile;
6523 int doubleClick;
6524
6525 void
6526 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6527 {
6528     ChessMove moveType;
6529     ChessSquare pdown, pup;
6530     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6531
6532
6533     /* Check if the user is playing in turn.  This is complicated because we
6534        let the user "pick up" a piece before it is his turn.  So the piece he
6535        tried to pick up may have been captured by the time he puts it down!
6536        Therefore we use the color the user is supposed to be playing in this
6537        test, not the color of the piece that is currently on the starting
6538        square---except in EditGame mode, where the user is playing both
6539        sides; fortunately there the capture race can't happen.  (It can
6540        now happen in IcsExamining mode, but that's just too bad.  The user
6541        will get a somewhat confusing message in that case.)
6542        */
6543
6544     switch (gameMode) {
6545       case AnalyzeFile:
6546       case TwoMachinesPlay:
6547       case EndOfGame:
6548       case IcsObserving:
6549       case IcsIdle:
6550         /* We switched into a game mode where moves are not accepted,
6551            perhaps while the mouse button was down. */
6552         return;
6553
6554       case MachinePlaysWhite:
6555         /* User is moving for Black */
6556         if (WhiteOnMove(currentMove)) {
6557             DisplayMoveError(_("It is White's turn"));
6558             return;
6559         }
6560         break;
6561
6562       case MachinePlaysBlack:
6563         /* User is moving for White */
6564         if (!WhiteOnMove(currentMove)) {
6565             DisplayMoveError(_("It is Black's turn"));
6566             return;
6567         }
6568         break;
6569
6570       case PlayFromGameFile:
6571             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6572       case EditGame:
6573       case IcsExamining:
6574       case BeginningOfGame:
6575       case AnalyzeMode:
6576       case Training:
6577         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6578         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6579             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6580             /* User is moving for Black */
6581             if (WhiteOnMove(currentMove)) {
6582                 DisplayMoveError(_("It is White's turn"));
6583                 return;
6584             }
6585         } else {
6586             /* User is moving for White */
6587             if (!WhiteOnMove(currentMove)) {
6588                 DisplayMoveError(_("It is Black's turn"));
6589                 return;
6590             }
6591         }
6592         break;
6593
6594       case IcsPlayingBlack:
6595         /* User is moving for Black */
6596         if (WhiteOnMove(currentMove)) {
6597             if (!appData.premove) {
6598                 DisplayMoveError(_("It is White's turn"));
6599             } else if (toX >= 0 && toY >= 0) {
6600                 premoveToX = toX;
6601                 premoveToY = toY;
6602                 premoveFromX = fromX;
6603                 premoveFromY = fromY;
6604                 premovePromoChar = promoChar;
6605                 gotPremove = 1;
6606                 if (appData.debugMode)
6607                     fprintf(debugFP, "Got premove: fromX %d,"
6608                             "fromY %d, toX %d, toY %d\n",
6609                             fromX, fromY, toX, toY);
6610             }
6611             return;
6612         }
6613         break;
6614
6615       case IcsPlayingWhite:
6616         /* User is moving for White */
6617         if (!WhiteOnMove(currentMove)) {
6618             if (!appData.premove) {
6619                 DisplayMoveError(_("It is Black's turn"));
6620             } else if (toX >= 0 && toY >= 0) {
6621                 premoveToX = toX;
6622                 premoveToY = toY;
6623                 premoveFromX = fromX;
6624                 premoveFromY = fromY;
6625                 premovePromoChar = promoChar;
6626                 gotPremove = 1;
6627                 if (appData.debugMode)
6628                     fprintf(debugFP, "Got premove: fromX %d,"
6629                             "fromY %d, toX %d, toY %d\n",
6630                             fromX, fromY, toX, toY);
6631             }
6632             return;
6633         }
6634         break;
6635
6636       default:
6637         break;
6638
6639       case EditPosition:
6640         /* EditPosition, empty square, or different color piece;
6641            click-click move is possible */
6642         if (toX == -2 || toY == -2) {
6643             boards[0][fromY][fromX] = EmptySquare;
6644             DrawPosition(FALSE, boards[currentMove]);
6645             return;
6646         } else if (toX >= 0 && toY >= 0) {
6647             boards[0][toY][toX] = boards[0][fromY][fromX];
6648             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6649                 if(boards[0][fromY][0] != EmptySquare) {
6650                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6651                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6652                 }
6653             } else
6654             if(fromX == BOARD_RGHT+1) {
6655                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6656                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6657                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6658                 }
6659             } else
6660             boards[0][fromY][fromX] = EmptySquare;
6661             DrawPosition(FALSE, boards[currentMove]);
6662             return;
6663         }
6664         return;
6665     }
6666
6667     if(toX < 0 || toY < 0) return;
6668     pdown = boards[currentMove][fromY][fromX];
6669     pup = boards[currentMove][toY][toX];
6670
6671     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6672     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6673          if( pup != EmptySquare ) return;
6674          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6675            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6676                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6677            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6678            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6679            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6680            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6681          fromY = DROP_RANK;
6682     }
6683
6684     /* [HGM] always test for legality, to get promotion info */
6685     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6686                                          fromY, fromX, toY, toX, promoChar);
6687
6688     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6689
6690     /* [HGM] but possibly ignore an IllegalMove result */
6691     if (appData.testLegality) {
6692         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6693             DisplayMoveError(_("Illegal move"));
6694             return;
6695         }
6696     }
6697
6698     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6699         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6700              ClearPremoveHighlights(); // was included
6701         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6702         return;
6703     }
6704
6705     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6706 }
6707
6708 /* Common tail of UserMoveEvent and DropMenuEvent */
6709 int
6710 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6711 {
6712     char *bookHit = 0;
6713
6714     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6715         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6716         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6717         if(WhiteOnMove(currentMove)) {
6718             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6719         } else {
6720             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6721         }
6722     }
6723
6724     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6725        move type in caller when we know the move is a legal promotion */
6726     if(moveType == NormalMove && promoChar)
6727         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6728
6729     /* [HGM] <popupFix> The following if has been moved here from
6730        UserMoveEvent(). Because it seemed to belong here (why not allow
6731        piece drops in training games?), and because it can only be
6732        performed after it is known to what we promote. */
6733     if (gameMode == Training) {
6734       /* compare the move played on the board to the next move in the
6735        * game. If they match, display the move and the opponent's response.
6736        * If they don't match, display an error message.
6737        */
6738       int saveAnimate;
6739       Board testBoard;
6740       CopyBoard(testBoard, boards[currentMove]);
6741       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6742
6743       if (CompareBoards(testBoard, boards[currentMove+1])) {
6744         ForwardInner(currentMove+1);
6745
6746         /* Autoplay the opponent's response.
6747          * if appData.animate was TRUE when Training mode was entered,
6748          * the response will be animated.
6749          */
6750         saveAnimate = appData.animate;
6751         appData.animate = animateTraining;
6752         ForwardInner(currentMove+1);
6753         appData.animate = saveAnimate;
6754
6755         /* check for the end of the game */
6756         if (currentMove >= forwardMostMove) {
6757           gameMode = PlayFromGameFile;
6758           ModeHighlight();
6759           SetTrainingModeOff();
6760           DisplayInformation(_("End of game"));
6761         }
6762       } else {
6763         DisplayError(_("Incorrect move"), 0);
6764       }
6765       return 1;
6766     }
6767
6768   /* Ok, now we know that the move is good, so we can kill
6769      the previous line in Analysis Mode */
6770   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6771                                 && currentMove < forwardMostMove) {
6772     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6773     else forwardMostMove = currentMove;
6774   }
6775
6776   ClearMap();
6777
6778   /* If we need the chess program but it's dead, restart it */
6779   ResurrectChessProgram();
6780
6781   /* A user move restarts a paused game*/
6782   if (pausing)
6783     PauseEvent();
6784
6785   thinkOutput[0] = NULLCHAR;
6786
6787   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6788
6789   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6790     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6791     return 1;
6792   }
6793
6794   if (gameMode == BeginningOfGame) {
6795     if (appData.noChessProgram) {
6796       gameMode = EditGame;
6797       SetGameInfo();
6798     } else {
6799       char buf[MSG_SIZ];
6800       gameMode = MachinePlaysBlack;
6801       StartClocks();
6802       SetGameInfo();
6803       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6804       DisplayTitle(buf);
6805       if (first.sendName) {
6806         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6807         SendToProgram(buf, &first);
6808       }
6809       StartClocks();
6810     }
6811     ModeHighlight();
6812   }
6813
6814   /* Relay move to ICS or chess engine */
6815   if (appData.icsActive) {
6816     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6817         gameMode == IcsExamining) {
6818       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6819         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6820         SendToICS("draw ");
6821         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6822       }
6823       // also send plain move, in case ICS does not understand atomic claims
6824       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6825       ics_user_moved = 1;
6826     }
6827   } else {
6828     if (first.sendTime && (gameMode == BeginningOfGame ||
6829                            gameMode == MachinePlaysWhite ||
6830                            gameMode == MachinePlaysBlack)) {
6831       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6832     }
6833     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6834          // [HGM] book: if program might be playing, let it use book
6835         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6836         first.maybeThinking = TRUE;
6837     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6838         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6839         SendBoard(&first, currentMove+1);
6840     } else SendMoveToProgram(forwardMostMove-1, &first);
6841     if (currentMove == cmailOldMove + 1) {
6842       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6843     }
6844   }
6845
6846   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847
6848   switch (gameMode) {
6849   case EditGame:
6850     if(appData.testLegality)
6851     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6852     case MT_NONE:
6853     case MT_CHECK:
6854       break;
6855     case MT_CHECKMATE:
6856     case MT_STAINMATE:
6857       if (WhiteOnMove(currentMove)) {
6858         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6859       } else {
6860         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6861       }
6862       break;
6863     case MT_STALEMATE:
6864       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6865       break;
6866     }
6867     break;
6868
6869   case MachinePlaysBlack:
6870   case MachinePlaysWhite:
6871     /* disable certain menu options while machine is thinking */
6872     SetMachineThinkingEnables();
6873     break;
6874
6875   default:
6876     break;
6877   }
6878
6879   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6880   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6881
6882   if(bookHit) { // [HGM] book: simulate book reply
6883         static char bookMove[MSG_SIZ]; // a bit generous?
6884
6885         programStats.nodes = programStats.depth = programStats.time =
6886         programStats.score = programStats.got_only_move = 0;
6887         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6888
6889         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6890         strcat(bookMove, bookHit);
6891         HandleMachineMove(bookMove, &first);
6892   }
6893   return 1;
6894 }
6895
6896 void
6897 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6898 {
6899     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6900     Markers *m = (Markers *) closure;
6901     if(rf == fromY && ff == fromX)
6902         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6903                          || kind == WhiteCapturesEnPassant
6904                          || kind == BlackCapturesEnPassant);
6905     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6906 }
6907
6908 void
6909 MarkTargetSquares (int clear)
6910 {
6911   int x, y;
6912   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6913      !appData.testLegality || gameMode == EditPosition) return;
6914   if(clear) {
6915     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6916   } else {
6917     int capt = 0;
6918     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6919     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6920       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6921       if(capt)
6922       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6923     }
6924   }
6925   DrawPosition(TRUE, NULL);
6926 }
6927
6928 int
6929 Explode (Board board, int fromX, int fromY, int toX, int toY)
6930 {
6931     if(gameInfo.variant == VariantAtomic &&
6932        (board[toY][toX] != EmptySquare ||                     // capture?
6933         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6934                          board[fromY][fromX] == BlackPawn   )
6935       )) {
6936         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6937         return TRUE;
6938     }
6939     return FALSE;
6940 }
6941
6942 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6943
6944 int
6945 CanPromote (ChessSquare piece, int y)
6946 {
6947         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6948         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6949         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6950            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6951            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6952                                                   gameInfo.variant == VariantMakruk) return FALSE;
6953         return (piece == BlackPawn && y == 1 ||
6954                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6955                 piece == BlackLance && y == 1 ||
6956                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6957 }
6958
6959 void
6960 LeftClick (ClickType clickType, int xPix, int yPix)
6961 {
6962     int x, y;
6963     Boolean saveAnimate;
6964     static int second = 0, promotionChoice = 0, clearFlag = 0;
6965     char promoChoice = NULLCHAR;
6966     ChessSquare piece;
6967     static TimeMark lastClickTime, prevClickTime;
6968
6969     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6970
6971     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6972
6973     if (clickType == Press) ErrorPopDown();
6974
6975     x = EventToSquare(xPix, BOARD_WIDTH);
6976     y = EventToSquare(yPix, BOARD_HEIGHT);
6977     if (!flipView && y >= 0) {
6978         y = BOARD_HEIGHT - 1 - y;
6979     }
6980     if (flipView && x >= 0) {
6981         x = BOARD_WIDTH - 1 - x;
6982     }
6983
6984     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6985         defaultPromoChoice = promoSweep;
6986         promoSweep = EmptySquare;   // terminate sweep
6987         promoDefaultAltered = TRUE;
6988         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6989     }
6990
6991     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6992         if(clickType == Release) return; // ignore upclick of click-click destination
6993         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6994         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6995         if(gameInfo.holdingsWidth &&
6996                 (WhiteOnMove(currentMove)
6997                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6998                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6999             // click in right holdings, for determining promotion piece
7000             ChessSquare p = boards[currentMove][y][x];
7001             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7002             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7003             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7004                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7005                 fromX = fromY = -1;
7006                 return;
7007             }
7008         }
7009         DrawPosition(FALSE, boards[currentMove]);
7010         return;
7011     }
7012
7013     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7014     if(clickType == Press
7015             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7016               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7017               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7018         return;
7019
7020     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7021         // could be static click on premove from-square: abort premove
7022         gotPremove = 0;
7023         ClearPremoveHighlights();
7024     }
7025
7026     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7027         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7028
7029     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7030         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7031                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7032         defaultPromoChoice = DefaultPromoChoice(side);
7033     }
7034
7035     autoQueen = appData.alwaysPromoteToQueen;
7036
7037     if (fromX == -1) {
7038       int originalY = y;
7039       gatingPiece = EmptySquare;
7040       if (clickType != Press) {
7041         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7042             DragPieceEnd(xPix, yPix); dragging = 0;
7043             DrawPosition(FALSE, NULL);
7044         }
7045         return;
7046       }
7047       doubleClick = FALSE;
7048       fromX = x; fromY = y; toX = toY = -1;
7049       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7050          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7051          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7052             /* First square */
7053             if (OKToStartUserMove(fromX, fromY)) {
7054                 second = 0;
7055                 MarkTargetSquares(0);
7056                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7057                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7058                     promoSweep = defaultPromoChoice;
7059                     selectFlag = 0; lastX = xPix; lastY = yPix;
7060                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7061                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7062                 }
7063                 if (appData.highlightDragging) {
7064                     SetHighlights(fromX, fromY, -1, -1);
7065                 }
7066             } else fromX = fromY = -1;
7067             return;
7068         }
7069     }
7070
7071     /* fromX != -1 */
7072     if (clickType == Press && gameMode != EditPosition) {
7073         ChessSquare fromP;
7074         ChessSquare toP;
7075         int frc;
7076
7077         // ignore off-board to clicks
7078         if(y < 0 || x < 0) return;
7079
7080         /* Check if clicking again on the same color piece */
7081         fromP = boards[currentMove][fromY][fromX];
7082         toP = boards[currentMove][y][x];
7083         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7084         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7085              WhitePawn <= toP && toP <= WhiteKing &&
7086              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7087              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7088             (BlackPawn <= fromP && fromP <= BlackKing &&
7089              BlackPawn <= toP && toP <= BlackKing &&
7090              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7091              !(fromP == BlackKing && toP == BlackRook && frc))) {
7092             /* Clicked again on same color piece -- changed his mind */
7093             second = (x == fromX && y == fromY);
7094             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7095                 second = FALSE; // first double-click rather than scond click
7096                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7097             }
7098             promoDefaultAltered = FALSE;
7099             MarkTargetSquares(1);
7100            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7101             if (appData.highlightDragging) {
7102                 SetHighlights(x, y, -1, -1);
7103             } else {
7104                 ClearHighlights();
7105             }
7106             if (OKToStartUserMove(x, y)) {
7107                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7108                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7109                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7110                  gatingPiece = boards[currentMove][fromY][fromX];
7111                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7112                 fromX = x;
7113                 fromY = y; dragging = 1;
7114                 MarkTargetSquares(0);
7115                 DragPieceBegin(xPix, yPix, FALSE);
7116                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7117                     promoSweep = defaultPromoChoice;
7118                     selectFlag = 0; lastX = xPix; lastY = yPix;
7119                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7120                 }
7121             }
7122            }
7123            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7124            second = FALSE; 
7125         }
7126         // ignore clicks on holdings
7127         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7128     }
7129
7130     if (clickType == Release && x == fromX && y == fromY) {
7131         DragPieceEnd(xPix, yPix); dragging = 0;
7132         if(clearFlag) {
7133             // a deferred attempt to click-click move an empty square on top of a piece
7134             boards[currentMove][y][x] = EmptySquare;
7135             ClearHighlights();
7136             DrawPosition(FALSE, boards[currentMove]);
7137             fromX = fromY = -1; clearFlag = 0;
7138             return;
7139         }
7140         if (appData.animateDragging) {
7141             /* Undo animation damage if any */
7142             DrawPosition(FALSE, NULL);
7143         }
7144         if (second) {
7145             /* Second up/down in same square; just abort move */
7146             second = 0;
7147             fromX = fromY = -1;
7148             gatingPiece = EmptySquare;
7149             ClearHighlights();
7150             gotPremove = 0;
7151             ClearPremoveHighlights();
7152         } else {
7153             /* First upclick in same square; start click-click mode */
7154             SetHighlights(x, y, -1, -1);
7155         }
7156         return;
7157     }
7158
7159     clearFlag = 0;
7160
7161     /* we now have a different from- and (possibly off-board) to-square */
7162     /* Completed move */
7163     toX = x;
7164     toY = y;
7165     saveAnimate = appData.animate;
7166     MarkTargetSquares(1);
7167     if (clickType == Press) {
7168         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7169             // must be Edit Position mode with empty-square selected
7170             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7171             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7172             return;
7173         }
7174         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7175             ChessSquare piece = boards[currentMove][fromY][fromX];
7176             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7177             promoSweep = defaultPromoChoice;
7178             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7179             selectFlag = 0; lastX = xPix; lastY = yPix;
7180             Sweep(0); // Pawn that is going to promote: preview promotion piece
7181             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7182             DrawPosition(FALSE, boards[currentMove]);
7183             return;
7184         }
7185         /* Finish clickclick move */
7186         if (appData.animate || appData.highlightLastMove) {
7187             SetHighlights(fromX, fromY, toX, toY);
7188         } else {
7189             ClearHighlights();
7190         }
7191     } else {
7192         /* Finish drag move */
7193         if (appData.highlightLastMove) {
7194             SetHighlights(fromX, fromY, toX, toY);
7195         } else {
7196             ClearHighlights();
7197         }
7198         DragPieceEnd(xPix, yPix); dragging = 0;
7199         /* Don't animate move and drag both */
7200         appData.animate = FALSE;
7201     }
7202
7203     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7204     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7205         ChessSquare piece = boards[currentMove][fromY][fromX];
7206         if(gameMode == EditPosition && piece != EmptySquare &&
7207            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7208             int n;
7209
7210             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7211                 n = PieceToNumber(piece - (int)BlackPawn);
7212                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7213                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7214                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7215             } else
7216             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7217                 n = PieceToNumber(piece);
7218                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7219                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7220                 boards[currentMove][n][BOARD_WIDTH-2]++;
7221             }
7222             boards[currentMove][fromY][fromX] = EmptySquare;
7223         }
7224         ClearHighlights();
7225         fromX = fromY = -1;
7226         DrawPosition(TRUE, boards[currentMove]);
7227         return;
7228     }
7229
7230     // off-board moves should not be highlighted
7231     if(x < 0 || y < 0) ClearHighlights();
7232
7233     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7234
7235     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7236         SetHighlights(fromX, fromY, toX, toY);
7237         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7238             // [HGM] super: promotion to captured piece selected from holdings
7239             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7240             promotionChoice = TRUE;
7241             // kludge follows to temporarily execute move on display, without promoting yet
7242             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7243             boards[currentMove][toY][toX] = p;
7244             DrawPosition(FALSE, boards[currentMove]);
7245             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7246             boards[currentMove][toY][toX] = q;
7247             DisplayMessage("Click in holdings to choose piece", "");
7248             return;
7249         }
7250         PromotionPopUp();
7251     } else {
7252         int oldMove = currentMove;
7253         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7254         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7255         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7256         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7257            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7258             DrawPosition(TRUE, boards[currentMove]);
7259         fromX = fromY = -1;
7260     }
7261     appData.animate = saveAnimate;
7262     if (appData.animate || appData.animateDragging) {
7263         /* Undo animation damage if needed */
7264         DrawPosition(FALSE, NULL);
7265     }
7266 }
7267
7268 int
7269 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7270 {   // front-end-free part taken out of PieceMenuPopup
7271     int whichMenu; int xSqr, ySqr;
7272
7273     if(seekGraphUp) { // [HGM] seekgraph
7274         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7275         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7276         return -2;
7277     }
7278
7279     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7280          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7281         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7282         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7283         if(action == Press)   {
7284             originalFlip = flipView;
7285             flipView = !flipView; // temporarily flip board to see game from partners perspective
7286             DrawPosition(TRUE, partnerBoard);
7287             DisplayMessage(partnerStatus, "");
7288             partnerUp = TRUE;
7289         } else if(action == Release) {
7290             flipView = originalFlip;
7291             DrawPosition(TRUE, boards[currentMove]);
7292             partnerUp = FALSE;
7293         }
7294         return -2;
7295     }
7296
7297     xSqr = EventToSquare(x, BOARD_WIDTH);
7298     ySqr = EventToSquare(y, BOARD_HEIGHT);
7299     if (action == Release) {
7300         if(pieceSweep != EmptySquare) {
7301             EditPositionMenuEvent(pieceSweep, toX, toY);
7302             pieceSweep = EmptySquare;
7303         } else UnLoadPV(); // [HGM] pv
7304     }
7305     if (action != Press) return -2; // return code to be ignored
7306     switch (gameMode) {
7307       case IcsExamining:
7308         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7309       case EditPosition:
7310         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7311         if (xSqr < 0 || ySqr < 0) return -1;
7312         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7313         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7314         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7315         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7316         NextPiece(0);
7317         return 2; // grab
7318       case IcsObserving:
7319         if(!appData.icsEngineAnalyze) return -1;
7320       case IcsPlayingWhite:
7321       case IcsPlayingBlack:
7322         if(!appData.zippyPlay) goto noZip;
7323       case AnalyzeMode:
7324       case AnalyzeFile:
7325       case MachinePlaysWhite:
7326       case MachinePlaysBlack:
7327       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7328         if (!appData.dropMenu) {
7329           LoadPV(x, y);
7330           return 2; // flag front-end to grab mouse events
7331         }
7332         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7333            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7334       case EditGame:
7335       noZip:
7336         if (xSqr < 0 || ySqr < 0) return -1;
7337         if (!appData.dropMenu || appData.testLegality &&
7338             gameInfo.variant != VariantBughouse &&
7339             gameInfo.variant != VariantCrazyhouse) return -1;
7340         whichMenu = 1; // drop menu
7341         break;
7342       default:
7343         return -1;
7344     }
7345
7346     if (((*fromX = xSqr) < 0) ||
7347         ((*fromY = ySqr) < 0)) {
7348         *fromX = *fromY = -1;
7349         return -1;
7350     }
7351     if (flipView)
7352       *fromX = BOARD_WIDTH - 1 - *fromX;
7353     else
7354       *fromY = BOARD_HEIGHT - 1 - *fromY;
7355
7356     return whichMenu;
7357 }
7358
7359 void
7360 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7361 {
7362 //    char * hint = lastHint;
7363     FrontEndProgramStats stats;
7364
7365     stats.which = cps == &first ? 0 : 1;
7366     stats.depth = cpstats->depth;
7367     stats.nodes = cpstats->nodes;
7368     stats.score = cpstats->score;
7369     stats.time = cpstats->time;
7370     stats.pv = cpstats->movelist;
7371     stats.hint = lastHint;
7372     stats.an_move_index = 0;
7373     stats.an_move_count = 0;
7374
7375     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7376         stats.hint = cpstats->move_name;
7377         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7378         stats.an_move_count = cpstats->nr_moves;
7379     }
7380
7381     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7382
7383     SetProgramStats( &stats );
7384 }
7385
7386 void
7387 ClearEngineOutputPane (int which)
7388 {
7389     static FrontEndProgramStats dummyStats;
7390     dummyStats.which = which;
7391     dummyStats.pv = "#";
7392     SetProgramStats( &dummyStats );
7393 }
7394
7395 #define MAXPLAYERS 500
7396
7397 char *
7398 TourneyStandings (int display)
7399 {
7400     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7401     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7402     char result, *p, *names[MAXPLAYERS];
7403
7404     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7405         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7406     names[0] = p = strdup(appData.participants);
7407     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7408
7409     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7410
7411     while(result = appData.results[nr]) {
7412         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7413         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7414         wScore = bScore = 0;
7415         switch(result) {
7416           case '+': wScore = 2; break;
7417           case '-': bScore = 2; break;
7418           case '=': wScore = bScore = 1; break;
7419           case ' ':
7420           case '*': return strdup("busy"); // tourney not finished
7421         }
7422         score[w] += wScore;
7423         score[b] += bScore;
7424         games[w]++;
7425         games[b]++;
7426         nr++;
7427     }
7428     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7429     for(w=0; w<nPlayers; w++) {
7430         bScore = -1;
7431         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7432         ranking[w] = b; points[w] = bScore; score[b] = -2;
7433     }
7434     p = malloc(nPlayers*34+1);
7435     for(w=0; w<nPlayers && w<display; w++)
7436         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7437     free(names[0]);
7438     return p;
7439 }
7440
7441 void
7442 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7443 {       // count all piece types
7444         int p, f, r;
7445         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7446         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7447         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7448                 p = board[r][f];
7449                 pCnt[p]++;
7450                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7451                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7452                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7453                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7454                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7455                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7456         }
7457 }
7458
7459 int
7460 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7461 {
7462         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7463         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7464
7465         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7466         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7467         if(myPawns == 2 && nMine == 3) // KPP
7468             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7469         if(myPawns == 1 && nMine == 2) // KP
7470             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7471         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7472             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7473         if(myPawns) return FALSE;
7474         if(pCnt[WhiteRook+side])
7475             return pCnt[BlackRook-side] ||
7476                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7477                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7478                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7479         if(pCnt[WhiteCannon+side]) {
7480             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7481             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7482         }
7483         if(pCnt[WhiteKnight+side])
7484             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7485         return FALSE;
7486 }
7487
7488 int
7489 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7490 {
7491         VariantClass v = gameInfo.variant;
7492
7493         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7494         if(v == VariantShatranj) return TRUE; // always winnable through baring
7495         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7496         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7497
7498         if(v == VariantXiangqi) {
7499                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7500
7501                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7502                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7503                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7504                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7505                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7506                 if(stale) // we have at least one last-rank P plus perhaps C
7507                     return majors // KPKX
7508                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7509                 else // KCA*E*
7510                     return pCnt[WhiteFerz+side] // KCAK
7511                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7512                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7513                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7514
7515         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7516                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7517
7518                 if(nMine == 1) return FALSE; // bare King
7519                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7520                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7521                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7522                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7523                 if(pCnt[WhiteKnight+side])
7524                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7525                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7526                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7527                 if(nBishops)
7528                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7529                 if(pCnt[WhiteAlfil+side])
7530                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7531                 if(pCnt[WhiteWazir+side])
7532                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7533         }
7534
7535         return TRUE;
7536 }
7537
7538 int
7539 CompareWithRights (Board b1, Board b2)
7540 {
7541     int rights = 0;
7542     if(!CompareBoards(b1, b2)) return FALSE;
7543     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7544     /* compare castling rights */
7545     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7546            rights++; /* King lost rights, while rook still had them */
7547     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7548         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7549            rights++; /* but at least one rook lost them */
7550     }
7551     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7552            rights++;
7553     if( b1[CASTLING][5] != NoRights ) {
7554         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7555            rights++;
7556     }
7557     return rights == 0;
7558 }
7559
7560 int
7561 Adjudicate (ChessProgramState *cps)
7562 {       // [HGM] some adjudications useful with buggy engines
7563         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7564         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7565         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7566         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7567         int k, count = 0; static int bare = 1;
7568         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7569         Boolean canAdjudicate = !appData.icsActive;
7570
7571         // most tests only when we understand the game, i.e. legality-checking on
7572             if( appData.testLegality )
7573             {   /* [HGM] Some more adjudications for obstinate engines */
7574                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7575                 static int moveCount = 6;
7576                 ChessMove result;
7577                 char *reason = NULL;
7578
7579                 /* Count what is on board. */
7580                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7581
7582                 /* Some material-based adjudications that have to be made before stalemate test */
7583                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7584                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7585                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7586                      if(canAdjudicate && appData.checkMates) {
7587                          if(engineOpponent)
7588                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7589                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7590                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7591                          return 1;
7592                      }
7593                 }
7594
7595                 /* Bare King in Shatranj (loses) or Losers (wins) */
7596                 if( nrW == 1 || nrB == 1) {
7597                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7598                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7599                      if(canAdjudicate && appData.checkMates) {
7600                          if(engineOpponent)
7601                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7602                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7603                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7604                          return 1;
7605                      }
7606                   } else
7607                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7608                   {    /* bare King */
7609                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7610                         if(canAdjudicate && appData.checkMates) {
7611                             /* but only adjudicate if adjudication enabled */
7612                             if(engineOpponent)
7613                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7614                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7615                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7616                             return 1;
7617                         }
7618                   }
7619                 } else bare = 1;
7620
7621
7622             // don't wait for engine to announce game end if we can judge ourselves
7623             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7624               case MT_CHECK:
7625                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7626                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7627                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7628                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7629                             checkCnt++;
7630                         if(checkCnt >= 2) {
7631                             reason = "Xboard adjudication: 3rd check";
7632                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7633                             break;
7634                         }
7635                     }
7636                 }
7637               case MT_NONE:
7638               default:
7639                 break;
7640               case MT_STALEMATE:
7641               case MT_STAINMATE:
7642                 reason = "Xboard adjudication: Stalemate";
7643                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7644                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7645                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7646                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7647                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7648                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7649                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7650                                                                         EP_CHECKMATE : EP_WINS);
7651                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7652                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7653                 }
7654                 break;
7655               case MT_CHECKMATE:
7656                 reason = "Xboard adjudication: Checkmate";
7657                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7658                 break;
7659             }
7660
7661                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7662                     case EP_STALEMATE:
7663                         result = GameIsDrawn; break;
7664                     case EP_CHECKMATE:
7665                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7666                     case EP_WINS:
7667                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7668                     default:
7669                         result = EndOfFile;
7670                 }
7671                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7672                     if(engineOpponent)
7673                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                     GameEnds( result, reason, GE_XBOARD );
7675                     return 1;
7676                 }
7677
7678                 /* Next absolutely insufficient mating material. */
7679                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7680                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7681                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7682
7683                      /* always flag draws, for judging claims */
7684                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7685
7686                      if(canAdjudicate && appData.materialDraws) {
7687                          /* but only adjudicate them if adjudication enabled */
7688                          if(engineOpponent) {
7689                            SendToProgram("force\n", engineOpponent); // suppress reply
7690                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7691                          }
7692                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7693                          return 1;
7694                      }
7695                 }
7696
7697                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7698                 if(gameInfo.variant == VariantXiangqi ?
7699                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7700                  : nrW + nrB == 4 &&
7701                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7702                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7703                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7704                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7705                    ) ) {
7706                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7707                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7708                           if(engineOpponent) {
7709                             SendToProgram("force\n", engineOpponent); // suppress reply
7710                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7711                           }
7712                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7713                           return 1;
7714                      }
7715                 } else moveCount = 6;
7716             }
7717
7718         // Repetition draws and 50-move rule can be applied independently of legality testing
7719
7720                 /* Check for rep-draws */
7721                 count = 0;
7722                 for(k = forwardMostMove-2;
7723                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7724                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7725                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7726                     k-=2)
7727                 {   int rights=0;
7728                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7729                         /* compare castling rights */
7730                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7731                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7732                                 rights++; /* King lost rights, while rook still had them */
7733                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7734                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7735                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7736                                    rights++; /* but at least one rook lost them */
7737                         }
7738                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7739                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7740                                 rights++;
7741                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7742                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7743                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7744                                    rights++;
7745                         }
7746                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7747                             && appData.drawRepeats > 1) {
7748                              /* adjudicate after user-specified nr of repeats */
7749                              int result = GameIsDrawn;
7750                              char *details = "XBoard adjudication: repetition draw";
7751                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7752                                 // [HGM] xiangqi: check for forbidden perpetuals
7753                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7754                                 for(m=forwardMostMove; m>k; m-=2) {
7755                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7756                                         ourPerpetual = 0; // the current mover did not always check
7757                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7758                                         hisPerpetual = 0; // the opponent did not always check
7759                                 }
7760                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7761                                                                         ourPerpetual, hisPerpetual);
7762                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7763                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7764                                     details = "Xboard adjudication: perpetual checking";
7765                                 } else
7766                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7767                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7768                                 } else
7769                                 // Now check for perpetual chases
7770                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7771                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7772                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7773                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7774                                         static char resdet[MSG_SIZ];
7775                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7776                                         details = resdet;
7777                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7778                                     } else
7779                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7780                                         break; // Abort repetition-checking loop.
7781                                 }
7782                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7783                              }
7784                              if(engineOpponent) {
7785                                SendToProgram("force\n", engineOpponent); // suppress reply
7786                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7787                              }
7788                              GameEnds( result, details, GE_XBOARD );
7789                              return 1;
7790                         }
7791                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7792                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7793                     }
7794                 }
7795
7796                 /* Now we test for 50-move draws. Determine ply count */
7797                 count = forwardMostMove;
7798                 /* look for last irreversble move */
7799                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7800                     count--;
7801                 /* if we hit starting position, add initial plies */
7802                 if( count == backwardMostMove )
7803                     count -= initialRulePlies;
7804                 count = forwardMostMove - count;
7805                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7806                         // adjust reversible move counter for checks in Xiangqi
7807                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7808                         if(i < backwardMostMove) i = backwardMostMove;
7809                         while(i <= forwardMostMove) {
7810                                 lastCheck = inCheck; // check evasion does not count
7811                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7812                                 if(inCheck || lastCheck) count--; // check does not count
7813                                 i++;
7814                         }
7815                 }
7816                 if( count >= 100)
7817                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7818                          /* this is used to judge if draw claims are legal */
7819                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7820                          if(engineOpponent) {
7821                            SendToProgram("force\n", engineOpponent); // suppress reply
7822                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7823                          }
7824                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7825                          return 1;
7826                 }
7827
7828                 /* if draw offer is pending, treat it as a draw claim
7829                  * when draw condition present, to allow engines a way to
7830                  * claim draws before making their move to avoid a race
7831                  * condition occurring after their move
7832                  */
7833                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7834                          char *p = NULL;
7835                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7836                              p = "Draw claim: 50-move rule";
7837                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7838                              p = "Draw claim: 3-fold repetition";
7839                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7840                              p = "Draw claim: insufficient mating material";
7841                          if( p != NULL && canAdjudicate) {
7842                              if(engineOpponent) {
7843                                SendToProgram("force\n", engineOpponent); // suppress reply
7844                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7845                              }
7846                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7847                              return 1;
7848                          }
7849                 }
7850
7851                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7852                     if(engineOpponent) {
7853                       SendToProgram("force\n", engineOpponent); // suppress reply
7854                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7855                     }
7856                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7857                     return 1;
7858                 }
7859         return 0;
7860 }
7861
7862 char *
7863 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7864 {   // [HGM] book: this routine intercepts moves to simulate book replies
7865     char *bookHit = NULL;
7866
7867     //first determine if the incoming move brings opponent into his book
7868     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7869         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7870     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7871     if(bookHit != NULL && !cps->bookSuspend) {
7872         // make sure opponent is not going to reply after receiving move to book position
7873         SendToProgram("force\n", cps);
7874         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7875     }
7876     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7877     // now arrange restart after book miss
7878     if(bookHit) {
7879         // after a book hit we never send 'go', and the code after the call to this routine
7880         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7881         char buf[MSG_SIZ], *move = bookHit;
7882         if(cps->useSAN) {
7883             int fromX, fromY, toX, toY;
7884             char promoChar;
7885             ChessMove moveType;
7886             move = buf + 30;
7887             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7888                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7889                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7890                                     PosFlags(forwardMostMove),
7891                                     fromY, fromX, toY, toX, promoChar, move);
7892             } else {
7893                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7894                 bookHit = NULL;
7895             }
7896         }
7897         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7898         SendToProgram(buf, cps);
7899         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7900     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7901         SendToProgram("go\n", cps);
7902         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7903     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7904         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7905             SendToProgram("go\n", cps);
7906         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7907     }
7908     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7909 }
7910
7911 char *savedMessage;
7912 ChessProgramState *savedState;
7913 void
7914 DeferredBookMove (void)
7915 {
7916         if(savedState->lastPing != savedState->lastPong)
7917                     ScheduleDelayedEvent(DeferredBookMove, 10);
7918         else
7919         HandleMachineMove(savedMessage, savedState);
7920 }
7921
7922 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7923
7924 void
7925 HandleMachineMove (char *message, ChessProgramState *cps)
7926 {
7927     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7928     char realname[MSG_SIZ];
7929     int fromX, fromY, toX, toY;
7930     ChessMove moveType;
7931     char promoChar;
7932     char *p, *pv=buf1;
7933     int machineWhite;
7934     char *bookHit;
7935
7936     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7937         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7938         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7939             DisplayError(_("Invalid pairing from pairing engine"), 0);
7940             return;
7941         }
7942         pairingReceived = 1;
7943         NextMatchGame();
7944         return; // Skim the pairing messages here.
7945     }
7946
7947     cps->userError = 0;
7948
7949 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7950     /*
7951      * Kludge to ignore BEL characters
7952      */
7953     while (*message == '\007') message++;
7954
7955     /*
7956      * [HGM] engine debug message: ignore lines starting with '#' character
7957      */
7958     if(cps->debug && *message == '#') return;
7959
7960     /*
7961      * Look for book output
7962      */
7963     if (cps == &first && bookRequested) {
7964         if (message[0] == '\t' || message[0] == ' ') {
7965             /* Part of the book output is here; append it */
7966             strcat(bookOutput, message);
7967             strcat(bookOutput, "  \n");
7968             return;
7969         } else if (bookOutput[0] != NULLCHAR) {
7970             /* All of book output has arrived; display it */
7971             char *p = bookOutput;
7972             while (*p != NULLCHAR) {
7973                 if (*p == '\t') *p = ' ';
7974                 p++;
7975             }
7976             DisplayInformation(bookOutput);
7977             bookRequested = FALSE;
7978             /* Fall through to parse the current output */
7979         }
7980     }
7981
7982     /*
7983      * Look for machine move.
7984      */
7985     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7986         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7987     {
7988         /* This method is only useful on engines that support ping */
7989         if (cps->lastPing != cps->lastPong) {
7990           if (gameMode == BeginningOfGame) {
7991             /* Extra move from before last new; ignore */
7992             if (appData.debugMode) {
7993                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7994             }
7995           } else {
7996             if (appData.debugMode) {
7997                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7998                         cps->which, gameMode);
7999             }
8000
8001             SendToProgram("undo\n", cps);
8002           }
8003           return;
8004         }
8005
8006         switch (gameMode) {
8007           case BeginningOfGame:
8008             /* Extra move from before last reset; ignore */
8009             if (appData.debugMode) {
8010                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8011             }
8012             return;
8013
8014           case EndOfGame:
8015           case IcsIdle:
8016           default:
8017             /* Extra move after we tried to stop.  The mode test is
8018                not a reliable way of detecting this problem, but it's
8019                the best we can do on engines that don't support ping.
8020             */
8021             if (appData.debugMode) {
8022                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8023                         cps->which, gameMode);
8024             }
8025             SendToProgram("undo\n", cps);
8026             return;
8027
8028           case MachinePlaysWhite:
8029           case IcsPlayingWhite:
8030             machineWhite = TRUE;
8031             break;
8032
8033           case MachinePlaysBlack:
8034           case IcsPlayingBlack:
8035             machineWhite = FALSE;
8036             break;
8037
8038           case TwoMachinesPlay:
8039             machineWhite = (cps->twoMachinesColor[0] == 'w');
8040             break;
8041         }
8042         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8043             if (appData.debugMode) {
8044                 fprintf(debugFP,
8045                         "Ignoring move out of turn by %s, gameMode %d"
8046                         ", forwardMost %d\n",
8047                         cps->which, gameMode, forwardMostMove);
8048             }
8049             return;
8050         }
8051
8052         if(cps->alphaRank) AlphaRank(machineMove, 4);
8053         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8054                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8055             /* Machine move could not be parsed; ignore it. */
8056           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8057                     machineMove, _(cps->which));
8058             DisplayError(buf1, 0);
8059             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8060                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8061             if (gameMode == TwoMachinesPlay) {
8062               GameEnds(machineWhite ? BlackWins : WhiteWins,
8063                        buf1, GE_XBOARD);
8064             }
8065             return;
8066         }
8067
8068         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8069         /* So we have to redo legality test with true e.p. status here,  */
8070         /* to make sure an illegal e.p. capture does not slip through,   */
8071         /* to cause a forfeit on a justified illegal-move complaint      */
8072         /* of the opponent.                                              */
8073         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8074            ChessMove moveType;
8075            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8076                              fromY, fromX, toY, toX, promoChar);
8077             if(moveType == IllegalMove) {
8078               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8079                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8080                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8081                            buf1, GE_XBOARD);
8082                 return;
8083            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8084            /* [HGM] Kludge to handle engines that send FRC-style castling
8085               when they shouldn't (like TSCP-Gothic) */
8086            switch(moveType) {
8087              case WhiteASideCastleFR:
8088              case BlackASideCastleFR:
8089                toX+=2;
8090                currentMoveString[2]++;
8091                break;
8092              case WhiteHSideCastleFR:
8093              case BlackHSideCastleFR:
8094                toX--;
8095                currentMoveString[2]--;
8096                break;
8097              default: ; // nothing to do, but suppresses warning of pedantic compilers
8098            }
8099         }
8100         hintRequested = FALSE;
8101         lastHint[0] = NULLCHAR;
8102         bookRequested = FALSE;
8103         /* Program may be pondering now */
8104         cps->maybeThinking = TRUE;
8105         if (cps->sendTime == 2) cps->sendTime = 1;
8106         if (cps->offeredDraw) cps->offeredDraw--;
8107
8108         /* [AS] Save move info*/
8109         pvInfoList[ forwardMostMove ].score = programStats.score;
8110         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8111         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8112
8113         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8114
8115         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8116         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8117             int count = 0;
8118
8119             while( count < adjudicateLossPlies ) {
8120                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8121
8122                 if( count & 1 ) {
8123                     score = -score; /* Flip score for winning side */
8124                 }
8125
8126                 if( score > adjudicateLossThreshold ) {
8127                     break;
8128                 }
8129
8130                 count++;
8131             }
8132
8133             if( count >= adjudicateLossPlies ) {
8134                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8135
8136                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8137                     "Xboard adjudication",
8138                     GE_XBOARD );
8139
8140                 return;
8141             }
8142         }
8143
8144         if(Adjudicate(cps)) {
8145             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8146             return; // [HGM] adjudicate: for all automatic game ends
8147         }
8148
8149 #if ZIPPY
8150         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8151             first.initDone) {
8152           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8153                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8154                 SendToICS("draw ");
8155                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8156           }
8157           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8158           ics_user_moved = 1;
8159           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8160                 char buf[3*MSG_SIZ];
8161
8162                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8163                         programStats.score / 100.,
8164                         programStats.depth,
8165                         programStats.time / 100.,
8166                         (unsigned int)programStats.nodes,
8167                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8168                         programStats.movelist);
8169                 SendToICS(buf);
8170 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8171           }
8172         }
8173 #endif
8174
8175         /* [AS] Clear stats for next move */
8176         ClearProgramStats();
8177         thinkOutput[0] = NULLCHAR;
8178         hiddenThinkOutputState = 0;
8179
8180         bookHit = NULL;
8181         if (gameMode == TwoMachinesPlay) {
8182             /* [HGM] relaying draw offers moved to after reception of move */
8183             /* and interpreting offer as claim if it brings draw condition */
8184             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8185                 SendToProgram("draw\n", cps->other);
8186             }
8187             if (cps->other->sendTime) {
8188                 SendTimeRemaining(cps->other,
8189                                   cps->other->twoMachinesColor[0] == 'w');
8190             }
8191             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8192             if (firstMove && !bookHit) {
8193                 firstMove = FALSE;
8194                 if (cps->other->useColors) {
8195                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8196                 }
8197                 SendToProgram("go\n", cps->other);
8198             }
8199             cps->other->maybeThinking = TRUE;
8200         }
8201
8202         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8203
8204         if (!pausing && appData.ringBellAfterMoves) {
8205             RingBell();
8206         }
8207
8208         /*
8209          * Reenable menu items that were disabled while
8210          * machine was thinking
8211          */
8212         if (gameMode != TwoMachinesPlay)
8213             SetUserThinkingEnables();
8214
8215         // [HGM] book: after book hit opponent has received move and is now in force mode
8216         // force the book reply into it, and then fake that it outputted this move by jumping
8217         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8218         if(bookHit) {
8219                 static char bookMove[MSG_SIZ]; // a bit generous?
8220
8221                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8222                 strcat(bookMove, bookHit);
8223                 message = bookMove;
8224                 cps = cps->other;
8225                 programStats.nodes = programStats.depth = programStats.time =
8226                 programStats.score = programStats.got_only_move = 0;
8227                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8228
8229                 if(cps->lastPing != cps->lastPong) {
8230                     savedMessage = message; // args for deferred call
8231                     savedState = cps;
8232                     ScheduleDelayedEvent(DeferredBookMove, 10);
8233                     return;
8234                 }
8235                 goto FakeBookMove;
8236         }
8237
8238         return;
8239     }
8240
8241     /* Set special modes for chess engines.  Later something general
8242      *  could be added here; for now there is just one kludge feature,
8243      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8244      *  when "xboard" is given as an interactive command.
8245      */
8246     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8247         cps->useSigint = FALSE;
8248         cps->useSigterm = FALSE;
8249     }
8250     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8251       ParseFeatures(message+8, cps);
8252       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8253     }
8254
8255     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8256                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8257       int dummy, s=6; char buf[MSG_SIZ];
8258       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8259       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8260       if(startedFromSetupPosition) return;
8261       ParseFEN(boards[0], &dummy, message+s);
8262       DrawPosition(TRUE, boards[0]);
8263       startedFromSetupPosition = TRUE;
8264       return;
8265     }
8266     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8267      * want this, I was asked to put it in, and obliged.
8268      */
8269     if (!strncmp(message, "setboard ", 9)) {
8270         Board initial_position;
8271
8272         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8273
8274         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8275             DisplayError(_("Bad FEN received from engine"), 0);
8276             return ;
8277         } else {
8278            Reset(TRUE, FALSE);
8279            CopyBoard(boards[0], initial_position);
8280            initialRulePlies = FENrulePlies;
8281            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8282            else gameMode = MachinePlaysBlack;
8283            DrawPosition(FALSE, boards[currentMove]);
8284         }
8285         return;
8286     }
8287
8288     /*
8289      * Look for communication commands
8290      */
8291     if (!strncmp(message, "telluser ", 9)) {
8292         if(message[9] == '\\' && message[10] == '\\')
8293             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8294         PlayTellSound();
8295         DisplayNote(message + 9);
8296         return;
8297     }
8298     if (!strncmp(message, "tellusererror ", 14)) {
8299         cps->userError = 1;
8300         if(message[14] == '\\' && message[15] == '\\')
8301             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8302         PlayTellSound();
8303         DisplayError(message + 14, 0);
8304         return;
8305     }
8306     if (!strncmp(message, "tellopponent ", 13)) {
8307       if (appData.icsActive) {
8308         if (loggedOn) {
8309           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8310           SendToICS(buf1);
8311         }
8312       } else {
8313         DisplayNote(message + 13);
8314       }
8315       return;
8316     }
8317     if (!strncmp(message, "tellothers ", 11)) {
8318       if (appData.icsActive) {
8319         if (loggedOn) {
8320           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8321           SendToICS(buf1);
8322         }
8323       }
8324       return;
8325     }
8326     if (!strncmp(message, "tellall ", 8)) {
8327       if (appData.icsActive) {
8328         if (loggedOn) {
8329           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8330           SendToICS(buf1);
8331         }
8332       } else {
8333         DisplayNote(message + 8);
8334       }
8335       return;
8336     }
8337     if (strncmp(message, "warning", 7) == 0) {
8338         /* Undocumented feature, use tellusererror in new code */
8339         DisplayError(message, 0);
8340         return;
8341     }
8342     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8343         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8344         strcat(realname, " query");
8345         AskQuestion(realname, buf2, buf1, cps->pr);
8346         return;
8347     }
8348     /* Commands from the engine directly to ICS.  We don't allow these to be
8349      *  sent until we are logged on. Crafty kibitzes have been known to
8350      *  interfere with the login process.
8351      */
8352     if (loggedOn) {
8353         if (!strncmp(message, "tellics ", 8)) {
8354             SendToICS(message + 8);
8355             SendToICS("\n");
8356             return;
8357         }
8358         if (!strncmp(message, "tellicsnoalias ", 15)) {
8359             SendToICS(ics_prefix);
8360             SendToICS(message + 15);
8361             SendToICS("\n");
8362             return;
8363         }
8364         /* The following are for backward compatibility only */
8365         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8366             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8367             SendToICS(ics_prefix);
8368             SendToICS(message);
8369             SendToICS("\n");
8370             return;
8371         }
8372     }
8373     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8374         return;
8375     }
8376     /*
8377      * If the move is illegal, cancel it and redraw the board.
8378      * Also deal with other error cases.  Matching is rather loose
8379      * here to accommodate engines written before the spec.
8380      */
8381     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8382         strncmp(message, "Error", 5) == 0) {
8383         if (StrStr(message, "name") ||
8384             StrStr(message, "rating") || StrStr(message, "?") ||
8385             StrStr(message, "result") || StrStr(message, "board") ||
8386             StrStr(message, "bk") || StrStr(message, "computer") ||
8387             StrStr(message, "variant") || StrStr(message, "hint") ||
8388             StrStr(message, "random") || StrStr(message, "depth") ||
8389             StrStr(message, "accepted")) {
8390             return;
8391         }
8392         if (StrStr(message, "protover")) {
8393           /* Program is responding to input, so it's apparently done
8394              initializing, and this error message indicates it is
8395              protocol version 1.  So we don't need to wait any longer
8396              for it to initialize and send feature commands. */
8397           FeatureDone(cps, 1);
8398           cps->protocolVersion = 1;
8399           return;
8400         }
8401         cps->maybeThinking = FALSE;
8402
8403         if (StrStr(message, "draw")) {
8404             /* Program doesn't have "draw" command */
8405             cps->sendDrawOffers = 0;
8406             return;
8407         }
8408         if (cps->sendTime != 1 &&
8409             (StrStr(message, "time") || StrStr(message, "otim"))) {
8410           /* Program apparently doesn't have "time" or "otim" command */
8411           cps->sendTime = 0;
8412           return;
8413         }
8414         if (StrStr(message, "analyze")) {
8415             cps->analysisSupport = FALSE;
8416             cps->analyzing = FALSE;
8417 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8418             EditGameEvent(); // [HGM] try to preserve loaded game
8419             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8420             DisplayError(buf2, 0);
8421             return;
8422         }
8423         if (StrStr(message, "(no matching move)st")) {
8424           /* Special kludge for GNU Chess 4 only */
8425           cps->stKludge = TRUE;
8426           SendTimeControl(cps, movesPerSession, timeControl,
8427                           timeIncrement, appData.searchDepth,
8428                           searchTime);
8429           return;
8430         }
8431         if (StrStr(message, "(no matching move)sd")) {
8432           /* Special kludge for GNU Chess 4 only */
8433           cps->sdKludge = TRUE;
8434           SendTimeControl(cps, movesPerSession, timeControl,
8435                           timeIncrement, appData.searchDepth,
8436                           searchTime);
8437           return;
8438         }
8439         if (!StrStr(message, "llegal")) {
8440             return;
8441         }
8442         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8443             gameMode == IcsIdle) return;
8444         if (forwardMostMove <= backwardMostMove) return;
8445         if (pausing) PauseEvent();
8446       if(appData.forceIllegal) {
8447             // [HGM] illegal: machine refused move; force position after move into it
8448           SendToProgram("force\n", cps);
8449           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8450                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8451                 // when black is to move, while there might be nothing on a2 or black
8452                 // might already have the move. So send the board as if white has the move.
8453                 // But first we must change the stm of the engine, as it refused the last move
8454                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8455                 if(WhiteOnMove(forwardMostMove)) {
8456                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8457                     SendBoard(cps, forwardMostMove); // kludgeless board
8458                 } else {
8459                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8460                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8461                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8462                 }
8463           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8464             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8465                  gameMode == TwoMachinesPlay)
8466               SendToProgram("go\n", cps);
8467             return;
8468       } else
8469         if (gameMode == PlayFromGameFile) {
8470             /* Stop reading this game file */
8471             gameMode = EditGame;
8472             ModeHighlight();
8473         }
8474         /* [HGM] illegal-move claim should forfeit game when Xboard */
8475         /* only passes fully legal moves                            */
8476         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8477             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8478                                 "False illegal-move claim", GE_XBOARD );
8479             return; // do not take back move we tested as valid
8480         }
8481         currentMove = forwardMostMove-1;
8482         DisplayMove(currentMove-1); /* before DisplayMoveError */
8483         SwitchClocks(forwardMostMove-1); // [HGM] race
8484         DisplayBothClocks();
8485         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8486                 parseList[currentMove], _(cps->which));
8487         DisplayMoveError(buf1);
8488         DrawPosition(FALSE, boards[currentMove]);
8489
8490         SetUserThinkingEnables();
8491         return;
8492     }
8493     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8494         /* Program has a broken "time" command that
8495            outputs a string not ending in newline.
8496            Don't use it. */
8497         cps->sendTime = 0;
8498     }
8499
8500     /*
8501      * If chess program startup fails, exit with an error message.
8502      * Attempts to recover here are futile. [HGM] Well, we try anyway
8503      */
8504     if ((StrStr(message, "unknown host") != NULL)
8505         || (StrStr(message, "No remote directory") != NULL)
8506         || (StrStr(message, "not found") != NULL)
8507         || (StrStr(message, "No such file") != NULL)
8508         || (StrStr(message, "can't alloc") != NULL)
8509         || (StrStr(message, "Permission denied") != NULL)) {
8510
8511         cps->maybeThinking = FALSE;
8512         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8513                 _(cps->which), cps->program, cps->host, message);
8514         RemoveInputSource(cps->isr);
8515         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8516             cps->isr = NULL;
8517             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8518             cps->pr = NoProc; 
8519             if(cps == &first) {
8520                 appData.noChessProgram = TRUE;
8521                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8522                 gameMode = BeginningOfGame; ModeHighlight();
8523                 SetNCPMode();
8524             }
8525             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8526             DisplayMessage("", ""); // erase waiting message
8527             DisplayError(buf1, 0);
8528         }
8529         return;
8530     }
8531
8532     /*
8533      * Look for hint output
8534      */
8535     if (sscanf(message, "Hint: %s", buf1) == 1) {
8536         if (cps == &first && hintRequested) {
8537             hintRequested = FALSE;
8538             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8539                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8540                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8541                                     PosFlags(forwardMostMove),
8542                                     fromY, fromX, toY, toX, promoChar, buf1);
8543                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8544                 DisplayInformation(buf2);
8545             } else {
8546                 /* Hint move could not be parsed!? */
8547               snprintf(buf2, sizeof(buf2),
8548                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8549                         buf1, _(cps->which));
8550                 DisplayError(buf2, 0);
8551             }
8552         } else {
8553           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8554         }
8555         return;
8556     }
8557
8558     /*
8559      * Ignore other messages if game is not in progress
8560      */
8561     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8562         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8563
8564     /*
8565      * look for win, lose, draw, or draw offer
8566      */
8567     if (strncmp(message, "1-0", 3) == 0) {
8568         char *p, *q, *r = "";
8569         p = strchr(message, '{');
8570         if (p) {
8571             q = strchr(p, '}');
8572             if (q) {
8573                 *q = NULLCHAR;
8574                 r = p + 1;
8575             }
8576         }
8577         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8578         return;
8579     } else if (strncmp(message, "0-1", 3) == 0) {
8580         char *p, *q, *r = "";
8581         p = strchr(message, '{');
8582         if (p) {
8583             q = strchr(p, '}');
8584             if (q) {
8585                 *q = NULLCHAR;
8586                 r = p + 1;
8587             }
8588         }
8589         /* Kludge for Arasan 4.1 bug */
8590         if (strcmp(r, "Black resigns") == 0) {
8591             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8592             return;
8593         }
8594         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8595         return;
8596     } else if (strncmp(message, "1/2", 3) == 0) {
8597         char *p, *q, *r = "";
8598         p = strchr(message, '{');
8599         if (p) {
8600             q = strchr(p, '}');
8601             if (q) {
8602                 *q = NULLCHAR;
8603                 r = p + 1;
8604             }
8605         }
8606
8607         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8608         return;
8609
8610     } else if (strncmp(message, "White resign", 12) == 0) {
8611         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8612         return;
8613     } else if (strncmp(message, "Black resign", 12) == 0) {
8614         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8615         return;
8616     } else if (strncmp(message, "White matches", 13) == 0 ||
8617                strncmp(message, "Black matches", 13) == 0   ) {
8618         /* [HGM] ignore GNUShogi noises */
8619         return;
8620     } else if (strncmp(message, "White", 5) == 0 &&
8621                message[5] != '(' &&
8622                StrStr(message, "Black") == NULL) {
8623         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8624         return;
8625     } else if (strncmp(message, "Black", 5) == 0 &&
8626                message[5] != '(') {
8627         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8628         return;
8629     } else if (strcmp(message, "resign") == 0 ||
8630                strcmp(message, "computer resigns") == 0) {
8631         switch (gameMode) {
8632           case MachinePlaysBlack:
8633           case IcsPlayingBlack:
8634             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8635             break;
8636           case MachinePlaysWhite:
8637           case IcsPlayingWhite:
8638             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8639             break;
8640           case TwoMachinesPlay:
8641             if (cps->twoMachinesColor[0] == 'w')
8642               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8643             else
8644               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8645             break;
8646           default:
8647             /* can't happen */
8648             break;
8649         }
8650         return;
8651     } else if (strncmp(message, "opponent mates", 14) == 0) {
8652         switch (gameMode) {
8653           case MachinePlaysBlack:
8654           case IcsPlayingBlack:
8655             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8656             break;
8657           case MachinePlaysWhite:
8658           case IcsPlayingWhite:
8659             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8660             break;
8661           case TwoMachinesPlay:
8662             if (cps->twoMachinesColor[0] == 'w')
8663               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8664             else
8665               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8666             break;
8667           default:
8668             /* can't happen */
8669             break;
8670         }
8671         return;
8672     } else if (strncmp(message, "computer mates", 14) == 0) {
8673         switch (gameMode) {
8674           case MachinePlaysBlack:
8675           case IcsPlayingBlack:
8676             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8677             break;
8678           case MachinePlaysWhite:
8679           case IcsPlayingWhite:
8680             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8681             break;
8682           case TwoMachinesPlay:
8683             if (cps->twoMachinesColor[0] == 'w')
8684               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8685             else
8686               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8687             break;
8688           default:
8689             /* can't happen */
8690             break;
8691         }
8692         return;
8693     } else if (strncmp(message, "checkmate", 9) == 0) {
8694         if (WhiteOnMove(forwardMostMove)) {
8695             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8696         } else {
8697             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8698         }
8699         return;
8700     } else if (strstr(message, "Draw") != NULL ||
8701                strstr(message, "game is a draw") != NULL) {
8702         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8703         return;
8704     } else if (strstr(message, "offer") != NULL &&
8705                strstr(message, "draw") != NULL) {
8706 #if ZIPPY
8707         if (appData.zippyPlay && first.initDone) {
8708             /* Relay offer to ICS */
8709             SendToICS(ics_prefix);
8710             SendToICS("draw\n");
8711         }
8712 #endif
8713         cps->offeredDraw = 2; /* valid until this engine moves twice */
8714         if (gameMode == TwoMachinesPlay) {
8715             if (cps->other->offeredDraw) {
8716                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8717             /* [HGM] in two-machine mode we delay relaying draw offer      */
8718             /* until after we also have move, to see if it is really claim */
8719             }
8720         } else if (gameMode == MachinePlaysWhite ||
8721                    gameMode == MachinePlaysBlack) {
8722           if (userOfferedDraw) {
8723             DisplayInformation(_("Machine accepts your draw offer"));
8724             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8725           } else {
8726             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8727           }
8728         }
8729     }
8730
8731
8732     /*
8733      * Look for thinking output
8734      */
8735     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8736           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8737                                 ) {
8738         int plylev, mvleft, mvtot, curscore, time;
8739         char mvname[MOVE_LEN];
8740         u64 nodes; // [DM]
8741         char plyext;
8742         int ignore = FALSE;
8743         int prefixHint = FALSE;
8744         mvname[0] = NULLCHAR;
8745
8746         switch (gameMode) {
8747           case MachinePlaysBlack:
8748           case IcsPlayingBlack:
8749             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8750             break;
8751           case MachinePlaysWhite:
8752           case IcsPlayingWhite:
8753             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8754             break;
8755           case AnalyzeMode:
8756           case AnalyzeFile:
8757             break;
8758           case IcsObserving: /* [DM] icsEngineAnalyze */
8759             if (!appData.icsEngineAnalyze) ignore = TRUE;
8760             break;
8761           case TwoMachinesPlay:
8762             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8763                 ignore = TRUE;
8764             }
8765             break;
8766           default:
8767             ignore = TRUE;
8768             break;
8769         }
8770
8771         if (!ignore) {
8772             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8773             buf1[0] = NULLCHAR;
8774             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8775                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8776
8777                 if (plyext != ' ' && plyext != '\t') {
8778                     time *= 100;
8779                 }
8780
8781                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8782                 if( cps->scoreIsAbsolute &&
8783                     ( gameMode == MachinePlaysBlack ||
8784                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8785                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8786                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8787                      !WhiteOnMove(currentMove)
8788                     ) )
8789                 {
8790                     curscore = -curscore;
8791                 }
8792
8793                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8794
8795                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8796                         char buf[MSG_SIZ];
8797                         FILE *f;
8798                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8799                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8800                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8801                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8802                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8803                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8804                                 fclose(f);
8805                         } else DisplayError(_("failed writing PV"), 0);
8806                 }
8807
8808                 tempStats.depth = plylev;
8809                 tempStats.nodes = nodes;
8810                 tempStats.time = time;
8811                 tempStats.score = curscore;
8812                 tempStats.got_only_move = 0;
8813
8814                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8815                         int ticklen;
8816
8817                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8818                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8819                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8820                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8821                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8822                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8823                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8824                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8825                 }
8826
8827                 /* Buffer overflow protection */
8828                 if (pv[0] != NULLCHAR) {
8829                     if (strlen(pv) >= sizeof(tempStats.movelist)
8830                         && appData.debugMode) {
8831                         fprintf(debugFP,
8832                                 "PV is too long; using the first %u bytes.\n",
8833                                 (unsigned) sizeof(tempStats.movelist) - 1);
8834                     }
8835
8836                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8837                 } else {
8838                     sprintf(tempStats.movelist, " no PV\n");
8839                 }
8840
8841                 if (tempStats.seen_stat) {
8842                     tempStats.ok_to_send = 1;
8843                 }
8844
8845                 if (strchr(tempStats.movelist, '(') != NULL) {
8846                     tempStats.line_is_book = 1;
8847                     tempStats.nr_moves = 0;
8848                     tempStats.moves_left = 0;
8849                 } else {
8850                     tempStats.line_is_book = 0;
8851                 }
8852
8853                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8854                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8855
8856                 SendProgramStatsToFrontend( cps, &tempStats );
8857
8858                 /*
8859                     [AS] Protect the thinkOutput buffer from overflow... this
8860                     is only useful if buf1 hasn't overflowed first!
8861                 */
8862                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8863                          plylev,
8864                          (gameMode == TwoMachinesPlay ?
8865                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8866                          ((double) curscore) / 100.0,
8867                          prefixHint ? lastHint : "",
8868                          prefixHint ? " " : "" );
8869
8870                 if( buf1[0] != NULLCHAR ) {
8871                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8872
8873                     if( strlen(pv) > max_len ) {
8874                         if( appData.debugMode) {
8875                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8876                         }
8877                         pv[max_len+1] = '\0';
8878                     }
8879
8880                     strcat( thinkOutput, pv);
8881                 }
8882
8883                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8884                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8885                     DisplayMove(currentMove - 1);
8886                 }
8887                 return;
8888
8889             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8890                 /* crafty (9.25+) says "(only move) <move>"
8891                  * if there is only 1 legal move
8892                  */
8893                 sscanf(p, "(only move) %s", buf1);
8894                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8895                 sprintf(programStats.movelist, "%s (only move)", buf1);
8896                 programStats.depth = 1;
8897                 programStats.nr_moves = 1;
8898                 programStats.moves_left = 1;
8899                 programStats.nodes = 1;
8900                 programStats.time = 1;
8901                 programStats.got_only_move = 1;
8902
8903                 /* Not really, but we also use this member to
8904                    mean "line isn't going to change" (Crafty
8905                    isn't searching, so stats won't change) */
8906                 programStats.line_is_book = 1;
8907
8908                 SendProgramStatsToFrontend( cps, &programStats );
8909
8910                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8911                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8912                     DisplayMove(currentMove - 1);
8913                 }
8914                 return;
8915             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8916                               &time, &nodes, &plylev, &mvleft,
8917                               &mvtot, mvname) >= 5) {
8918                 /* The stat01: line is from Crafty (9.29+) in response
8919                    to the "." command */
8920                 programStats.seen_stat = 1;
8921                 cps->maybeThinking = TRUE;
8922
8923                 if (programStats.got_only_move || !appData.periodicUpdates)
8924                   return;
8925
8926                 programStats.depth = plylev;
8927                 programStats.time = time;
8928                 programStats.nodes = nodes;
8929                 programStats.moves_left = mvleft;
8930                 programStats.nr_moves = mvtot;
8931                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8932                 programStats.ok_to_send = 1;
8933                 programStats.movelist[0] = '\0';
8934
8935                 SendProgramStatsToFrontend( cps, &programStats );
8936
8937                 return;
8938
8939             } else if (strncmp(message,"++",2) == 0) {
8940                 /* Crafty 9.29+ outputs this */
8941                 programStats.got_fail = 2;
8942                 return;
8943
8944             } else if (strncmp(message,"--",2) == 0) {
8945                 /* Crafty 9.29+ outputs this */
8946                 programStats.got_fail = 1;
8947                 return;
8948
8949             } else if (thinkOutput[0] != NULLCHAR &&
8950                        strncmp(message, "    ", 4) == 0) {
8951                 unsigned message_len;
8952
8953                 p = message;
8954                 while (*p && *p == ' ') p++;
8955
8956                 message_len = strlen( p );
8957
8958                 /* [AS] Avoid buffer overflow */
8959                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8960                     strcat(thinkOutput, " ");
8961                     strcat(thinkOutput, p);
8962                 }
8963
8964                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8965                     strcat(programStats.movelist, " ");
8966                     strcat(programStats.movelist, p);
8967                 }
8968
8969                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8970                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8971                     DisplayMove(currentMove - 1);
8972                 }
8973                 return;
8974             }
8975         }
8976         else {
8977             buf1[0] = NULLCHAR;
8978
8979             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8980                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8981             {
8982                 ChessProgramStats cpstats;
8983
8984                 if (plyext != ' ' && plyext != '\t') {
8985                     time *= 100;
8986                 }
8987
8988                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8989                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8990                     curscore = -curscore;
8991                 }
8992
8993                 cpstats.depth = plylev;
8994                 cpstats.nodes = nodes;
8995                 cpstats.time = time;
8996                 cpstats.score = curscore;
8997                 cpstats.got_only_move = 0;
8998                 cpstats.movelist[0] = '\0';
8999
9000                 if (buf1[0] != NULLCHAR) {
9001                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9002                 }
9003
9004                 cpstats.ok_to_send = 0;
9005                 cpstats.line_is_book = 0;
9006                 cpstats.nr_moves = 0;
9007                 cpstats.moves_left = 0;
9008
9009                 SendProgramStatsToFrontend( cps, &cpstats );
9010             }
9011         }
9012     }
9013 }
9014
9015
9016 /* Parse a game score from the character string "game", and
9017    record it as the history of the current game.  The game
9018    score is NOT assumed to start from the standard position.
9019    The display is not updated in any way.
9020    */
9021 void
9022 ParseGameHistory (char *game)
9023 {
9024     ChessMove moveType;
9025     int fromX, fromY, toX, toY, boardIndex;
9026     char promoChar;
9027     char *p, *q;
9028     char buf[MSG_SIZ];
9029
9030     if (appData.debugMode)
9031       fprintf(debugFP, "Parsing game history: %s\n", game);
9032
9033     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9034     gameInfo.site = StrSave(appData.icsHost);
9035     gameInfo.date = PGNDate();
9036     gameInfo.round = StrSave("-");
9037
9038     /* Parse out names of players */
9039     while (*game == ' ') game++;
9040     p = buf;
9041     while (*game != ' ') *p++ = *game++;
9042     *p = NULLCHAR;
9043     gameInfo.white = StrSave(buf);
9044     while (*game == ' ') game++;
9045     p = buf;
9046     while (*game != ' ' && *game != '\n') *p++ = *game++;
9047     *p = NULLCHAR;
9048     gameInfo.black = StrSave(buf);
9049
9050     /* Parse moves */
9051     boardIndex = blackPlaysFirst ? 1 : 0;
9052     yynewstr(game);
9053     for (;;) {
9054         yyboardindex = boardIndex;
9055         moveType = (ChessMove) Myylex();
9056         switch (moveType) {
9057           case IllegalMove:             /* maybe suicide chess, etc. */
9058   if (appData.debugMode) {
9059     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9060     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9061     setbuf(debugFP, NULL);
9062   }
9063           case WhitePromotion:
9064           case BlackPromotion:
9065           case WhiteNonPromotion:
9066           case BlackNonPromotion:
9067           case NormalMove:
9068           case WhiteCapturesEnPassant:
9069           case BlackCapturesEnPassant:
9070           case WhiteKingSideCastle:
9071           case WhiteQueenSideCastle:
9072           case BlackKingSideCastle:
9073           case BlackQueenSideCastle:
9074           case WhiteKingSideCastleWild:
9075           case WhiteQueenSideCastleWild:
9076           case BlackKingSideCastleWild:
9077           case BlackQueenSideCastleWild:
9078           /* PUSH Fabien */
9079           case WhiteHSideCastleFR:
9080           case WhiteASideCastleFR:
9081           case BlackHSideCastleFR:
9082           case BlackASideCastleFR:
9083           /* POP Fabien */
9084             fromX = currentMoveString[0] - AAA;
9085             fromY = currentMoveString[1] - ONE;
9086             toX = currentMoveString[2] - AAA;
9087             toY = currentMoveString[3] - ONE;
9088             promoChar = currentMoveString[4];
9089             break;
9090           case WhiteDrop:
9091           case BlackDrop:
9092             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9093             fromX = moveType == WhiteDrop ?
9094               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9095             (int) CharToPiece(ToLower(currentMoveString[0]));
9096             fromY = DROP_RANK;
9097             toX = currentMoveString[2] - AAA;
9098             toY = currentMoveString[3] - ONE;
9099             promoChar = NULLCHAR;
9100             break;
9101           case AmbiguousMove:
9102             /* bug? */
9103             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9104   if (appData.debugMode) {
9105     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9106     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9107     setbuf(debugFP, NULL);
9108   }
9109             DisplayError(buf, 0);
9110             return;
9111           case ImpossibleMove:
9112             /* bug? */
9113             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9114   if (appData.debugMode) {
9115     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9117     setbuf(debugFP, NULL);
9118   }
9119             DisplayError(buf, 0);
9120             return;
9121           case EndOfFile:
9122             if (boardIndex < backwardMostMove) {
9123                 /* Oops, gap.  How did that happen? */
9124                 DisplayError(_("Gap in move list"), 0);
9125                 return;
9126             }
9127             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9128             if (boardIndex > forwardMostMove) {
9129                 forwardMostMove = boardIndex;
9130             }
9131             return;
9132           case ElapsedTime:
9133             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9134                 strcat(parseList[boardIndex-1], " ");
9135                 strcat(parseList[boardIndex-1], yy_text);
9136             }
9137             continue;
9138           case Comment:
9139           case PGNTag:
9140           case NAG:
9141           default:
9142             /* ignore */
9143             continue;
9144           case WhiteWins:
9145           case BlackWins:
9146           case GameIsDrawn:
9147           case GameUnfinished:
9148             if (gameMode == IcsExamining) {
9149                 if (boardIndex < backwardMostMove) {
9150                     /* Oops, gap.  How did that happen? */
9151                     return;
9152                 }
9153                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9154                 return;
9155             }
9156             gameInfo.result = moveType;
9157             p = strchr(yy_text, '{');
9158             if (p == NULL) p = strchr(yy_text, '(');
9159             if (p == NULL) {
9160                 p = yy_text;
9161                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9162             } else {
9163                 q = strchr(p, *p == '{' ? '}' : ')');
9164                 if (q != NULL) *q = NULLCHAR;
9165                 p++;
9166             }
9167             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9168             gameInfo.resultDetails = StrSave(p);
9169             continue;
9170         }
9171         if (boardIndex >= forwardMostMove &&
9172             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9173             backwardMostMove = blackPlaysFirst ? 1 : 0;
9174             return;
9175         }
9176         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9177                                  fromY, fromX, toY, toX, promoChar,
9178                                  parseList[boardIndex]);
9179         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9180         /* currentMoveString is set as a side-effect of yylex */
9181         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9182         strcat(moveList[boardIndex], "\n");
9183         boardIndex++;
9184         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9185         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9186           case MT_NONE:
9187           case MT_STALEMATE:
9188           default:
9189             break;
9190           case MT_CHECK:
9191             if(gameInfo.variant != VariantShogi)
9192                 strcat(parseList[boardIndex - 1], "+");
9193             break;
9194           case MT_CHECKMATE:
9195           case MT_STAINMATE:
9196             strcat(parseList[boardIndex - 1], "#");
9197             break;
9198         }
9199     }
9200 }
9201
9202
9203 /* Apply a move to the given board  */
9204 void
9205 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9206 {
9207   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9208   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9209
9210     /* [HGM] compute & store e.p. status and castling rights for new position */
9211     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9212
9213       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9214       oldEP = (signed char)board[EP_STATUS];
9215       board[EP_STATUS] = EP_NONE;
9216
9217   if (fromY == DROP_RANK) {
9218         /* must be first */
9219         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9220             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9221             return;
9222         }
9223         piece = board[toY][toX] = (ChessSquare) fromX;
9224   } else {
9225       int i;
9226
9227       if( board[toY][toX] != EmptySquare )
9228            board[EP_STATUS] = EP_CAPTURE;
9229
9230       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9231            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9232                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9233       } else
9234       if( board[fromY][fromX] == WhitePawn ) {
9235            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9236                board[EP_STATUS] = EP_PAWN_MOVE;
9237            if( toY-fromY==2) {
9238                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9239                         gameInfo.variant != VariantBerolina || toX < fromX)
9240                       board[EP_STATUS] = toX | berolina;
9241                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9242                         gameInfo.variant != VariantBerolina || toX > fromX)
9243                       board[EP_STATUS] = toX;
9244            }
9245       } else
9246       if( board[fromY][fromX] == BlackPawn ) {
9247            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9248                board[EP_STATUS] = EP_PAWN_MOVE;
9249            if( toY-fromY== -2) {
9250                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9251                         gameInfo.variant != VariantBerolina || toX < fromX)
9252                       board[EP_STATUS] = toX | berolina;
9253                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9254                         gameInfo.variant != VariantBerolina || toX > fromX)
9255                       board[EP_STATUS] = toX;
9256            }
9257        }
9258
9259        for(i=0; i<nrCastlingRights; i++) {
9260            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9261               board[CASTLING][i] == toX   && castlingRank[i] == toY
9262              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9263        }
9264
9265      if (fromX == toX && fromY == toY) return;
9266
9267      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9268      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9269      if(gameInfo.variant == VariantKnightmate)
9270          king += (int) WhiteUnicorn - (int) WhiteKing;
9271
9272     /* Code added by Tord: */
9273     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9274     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9275         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9276       board[fromY][fromX] = EmptySquare;
9277       board[toY][toX] = EmptySquare;
9278       if((toX > fromX) != (piece == WhiteRook)) {
9279         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9280       } else {
9281         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9282       }
9283     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9284                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9285       board[fromY][fromX] = EmptySquare;
9286       board[toY][toX] = EmptySquare;
9287       if((toX > fromX) != (piece == BlackRook)) {
9288         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9289       } else {
9290         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9291       }
9292     /* End of code added by Tord */
9293
9294     } else if (board[fromY][fromX] == king
9295         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9296         && toY == fromY && toX > fromX+1) {
9297         board[fromY][fromX] = EmptySquare;
9298         board[toY][toX] = king;
9299         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9300         board[fromY][BOARD_RGHT-1] = EmptySquare;
9301     } else if (board[fromY][fromX] == king
9302         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9303                && toY == fromY && toX < fromX-1) {
9304         board[fromY][fromX] = EmptySquare;
9305         board[toY][toX] = king;
9306         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9307         board[fromY][BOARD_LEFT] = EmptySquare;
9308     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9309                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9310                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9311                ) {
9312         /* white pawn promotion */
9313         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9314         if(gameInfo.variant==VariantBughouse ||
9315            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9316             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9317         board[fromY][fromX] = EmptySquare;
9318     } else if ((fromY >= BOARD_HEIGHT>>1)
9319                && (toX != fromX)
9320                && gameInfo.variant != VariantXiangqi
9321                && gameInfo.variant != VariantBerolina
9322                && (board[fromY][fromX] == WhitePawn)
9323                && (board[toY][toX] == EmptySquare)) {
9324         board[fromY][fromX] = EmptySquare;
9325         board[toY][toX] = WhitePawn;
9326         captured = board[toY - 1][toX];
9327         board[toY - 1][toX] = EmptySquare;
9328     } else if ((fromY == BOARD_HEIGHT-4)
9329                && (toX == fromX)
9330                && gameInfo.variant == VariantBerolina
9331                && (board[fromY][fromX] == WhitePawn)
9332                && (board[toY][toX] == EmptySquare)) {
9333         board[fromY][fromX] = EmptySquare;
9334         board[toY][toX] = WhitePawn;
9335         if(oldEP & EP_BEROLIN_A) {
9336                 captured = board[fromY][fromX-1];
9337                 board[fromY][fromX-1] = EmptySquare;
9338         }else{  captured = board[fromY][fromX+1];
9339                 board[fromY][fromX+1] = EmptySquare;
9340         }
9341     } else if (board[fromY][fromX] == king
9342         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9343                && toY == fromY && toX > fromX+1) {
9344         board[fromY][fromX] = EmptySquare;
9345         board[toY][toX] = king;
9346         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9347         board[fromY][BOARD_RGHT-1] = EmptySquare;
9348     } else if (board[fromY][fromX] == king
9349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9350                && toY == fromY && toX < fromX-1) {
9351         board[fromY][fromX] = EmptySquare;
9352         board[toY][toX] = king;
9353         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9354         board[fromY][BOARD_LEFT] = EmptySquare;
9355     } else if (fromY == 7 && fromX == 3
9356                && board[fromY][fromX] == BlackKing
9357                && toY == 7 && toX == 5) {
9358         board[fromY][fromX] = EmptySquare;
9359         board[toY][toX] = BlackKing;
9360         board[fromY][7] = EmptySquare;
9361         board[toY][4] = BlackRook;
9362     } else if (fromY == 7 && fromX == 3
9363                && board[fromY][fromX] == BlackKing
9364                && toY == 7 && toX == 1) {
9365         board[fromY][fromX] = EmptySquare;
9366         board[toY][toX] = BlackKing;
9367         board[fromY][0] = EmptySquare;
9368         board[toY][2] = BlackRook;
9369     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9370                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9371                && toY < promoRank && promoChar
9372                ) {
9373         /* black pawn promotion */
9374         board[toY][toX] = CharToPiece(ToLower(promoChar));
9375         if(gameInfo.variant==VariantBughouse ||
9376            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9377             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9378         board[fromY][fromX] = EmptySquare;
9379     } else if ((fromY < BOARD_HEIGHT>>1)
9380                && (toX != fromX)
9381                && gameInfo.variant != VariantXiangqi
9382                && gameInfo.variant != VariantBerolina
9383                && (board[fromY][fromX] == BlackPawn)
9384                && (board[toY][toX] == EmptySquare)) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = BlackPawn;
9387         captured = board[toY + 1][toX];
9388         board[toY + 1][toX] = EmptySquare;
9389     } else if ((fromY == 3)
9390                && (toX == fromX)
9391                && gameInfo.variant == VariantBerolina
9392                && (board[fromY][fromX] == BlackPawn)
9393                && (board[toY][toX] == EmptySquare)) {
9394         board[fromY][fromX] = EmptySquare;
9395         board[toY][toX] = BlackPawn;
9396         if(oldEP & EP_BEROLIN_A) {
9397                 captured = board[fromY][fromX-1];
9398                 board[fromY][fromX-1] = EmptySquare;
9399         }else{  captured = board[fromY][fromX+1];
9400                 board[fromY][fromX+1] = EmptySquare;
9401         }
9402     } else {
9403         board[toY][toX] = board[fromY][fromX];
9404         board[fromY][fromX] = EmptySquare;
9405     }
9406   }
9407
9408     if (gameInfo.holdingsWidth != 0) {
9409
9410       /* !!A lot more code needs to be written to support holdings  */
9411       /* [HGM] OK, so I have written it. Holdings are stored in the */
9412       /* penultimate board files, so they are automaticlly stored   */
9413       /* in the game history.                                       */
9414       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9415                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9416         /* Delete from holdings, by decreasing count */
9417         /* and erasing image if necessary            */
9418         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9419         if(p < (int) BlackPawn) { /* white drop */
9420              p -= (int)WhitePawn;
9421                  p = PieceToNumber((ChessSquare)p);
9422              if(p >= gameInfo.holdingsSize) p = 0;
9423              if(--board[p][BOARD_WIDTH-2] <= 0)
9424                   board[p][BOARD_WIDTH-1] = EmptySquare;
9425              if((int)board[p][BOARD_WIDTH-2] < 0)
9426                         board[p][BOARD_WIDTH-2] = 0;
9427         } else {                  /* black drop */
9428              p -= (int)BlackPawn;
9429                  p = PieceToNumber((ChessSquare)p);
9430              if(p >= gameInfo.holdingsSize) p = 0;
9431              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9432                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9433              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9434                         board[BOARD_HEIGHT-1-p][1] = 0;
9435         }
9436       }
9437       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9438           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9439         /* [HGM] holdings: Add to holdings, if holdings exist */
9440         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9441                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9442                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9443         }
9444         p = (int) captured;
9445         if (p >= (int) BlackPawn) {
9446           p -= (int)BlackPawn;
9447           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9448                   /* in Shogi restore piece to its original  first */
9449                   captured = (ChessSquare) (DEMOTED captured);
9450                   p = DEMOTED p;
9451           }
9452           p = PieceToNumber((ChessSquare)p);
9453           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9454           board[p][BOARD_WIDTH-2]++;
9455           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9456         } else {
9457           p -= (int)WhitePawn;
9458           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9459                   captured = (ChessSquare) (DEMOTED captured);
9460                   p = DEMOTED p;
9461           }
9462           p = PieceToNumber((ChessSquare)p);
9463           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9464           board[BOARD_HEIGHT-1-p][1]++;
9465           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9466         }
9467       }
9468     } else if (gameInfo.variant == VariantAtomic) {
9469       if (captured != EmptySquare) {
9470         int y, x;
9471         for (y = toY-1; y <= toY+1; y++) {
9472           for (x = toX-1; x <= toX+1; x++) {
9473             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9474                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9475               board[y][x] = EmptySquare;
9476             }
9477           }
9478         }
9479         board[toY][toX] = EmptySquare;
9480       }
9481     }
9482     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9483         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9484     } else
9485     if(promoChar == '+') {
9486         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9487         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9488     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9489         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9490         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9491            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9492         board[toY][toX] = newPiece;
9493     }
9494     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9495                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9496         // [HGM] superchess: take promotion piece out of holdings
9497         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9498         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9499             if(!--board[k][BOARD_WIDTH-2])
9500                 board[k][BOARD_WIDTH-1] = EmptySquare;
9501         } else {
9502             if(!--board[BOARD_HEIGHT-1-k][1])
9503                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9504         }
9505     }
9506
9507 }
9508
9509 /* Updates forwardMostMove */
9510 void
9511 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9512 {
9513 //    forwardMostMove++; // [HGM] bare: moved downstream
9514
9515     (void) CoordsToAlgebraic(boards[forwardMostMove],
9516                              PosFlags(forwardMostMove),
9517                              fromY, fromX, toY, toX, promoChar,
9518                              parseList[forwardMostMove]);
9519
9520     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9521         int timeLeft; static int lastLoadFlag=0; int king, piece;
9522         piece = boards[forwardMostMove][fromY][fromX];
9523         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9524         if(gameInfo.variant == VariantKnightmate)
9525             king += (int) WhiteUnicorn - (int) WhiteKing;
9526         if(forwardMostMove == 0) {
9527             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9528                 fprintf(serverMoves, "%s;", UserName());
9529             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9530                 fprintf(serverMoves, "%s;", second.tidy);
9531             fprintf(serverMoves, "%s;", first.tidy);
9532             if(gameMode == MachinePlaysWhite)
9533                 fprintf(serverMoves, "%s;", UserName());
9534             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9535                 fprintf(serverMoves, "%s;", second.tidy);
9536         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9537         lastLoadFlag = loadFlag;
9538         // print base move
9539         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9540         // print castling suffix
9541         if( toY == fromY && piece == king ) {
9542             if(toX-fromX > 1)
9543                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9544             if(fromX-toX >1)
9545                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9546         }
9547         // e.p. suffix
9548         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9549              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9550              boards[forwardMostMove][toY][toX] == EmptySquare
9551              && fromX != toX && fromY != toY)
9552                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9553         // promotion suffix
9554         if(promoChar != NULLCHAR)
9555                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9556         if(!loadFlag) {
9557                 char buf[MOVE_LEN*2], *p; int len;
9558             fprintf(serverMoves, "/%d/%d",
9559                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9560             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9561             else                      timeLeft = blackTimeRemaining/1000;
9562             fprintf(serverMoves, "/%d", timeLeft);
9563                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9564                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9565                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9566             fprintf(serverMoves, "/%s", buf);
9567         }
9568         fflush(serverMoves);
9569     }
9570
9571     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9572         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9573       return;
9574     }
9575     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9576     if (commentList[forwardMostMove+1] != NULL) {
9577         free(commentList[forwardMostMove+1]);
9578         commentList[forwardMostMove+1] = NULL;
9579     }
9580     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9581     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9582     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9583     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9584     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9585     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9586     adjustedClock = FALSE;
9587     gameInfo.result = GameUnfinished;
9588     if (gameInfo.resultDetails != NULL) {
9589         free(gameInfo.resultDetails);
9590         gameInfo.resultDetails = NULL;
9591     }
9592     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9593                               moveList[forwardMostMove - 1]);
9594     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9595       case MT_NONE:
9596       case MT_STALEMATE:
9597       default:
9598         break;
9599       case MT_CHECK:
9600         if(gameInfo.variant != VariantShogi)
9601             strcat(parseList[forwardMostMove - 1], "+");
9602         break;
9603       case MT_CHECKMATE:
9604       case MT_STAINMATE:
9605         strcat(parseList[forwardMostMove - 1], "#");
9606         break;
9607     }
9608
9609 }
9610
9611 /* Updates currentMove if not pausing */
9612 void
9613 ShowMove (int fromX, int fromY, int toX, int toY)
9614 {
9615     int instant = (gameMode == PlayFromGameFile) ?
9616         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9617     if(appData.noGUI) return;
9618     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9619         if (!instant) {
9620             if (forwardMostMove == currentMove + 1) {
9621                 AnimateMove(boards[forwardMostMove - 1],
9622                             fromX, fromY, toX, toY);
9623             }
9624             if (appData.highlightLastMove) {
9625                 SetHighlights(fromX, fromY, toX, toY);
9626             }
9627         }
9628         currentMove = forwardMostMove;
9629     }
9630
9631     if (instant) return;
9632
9633     DisplayMove(currentMove - 1);
9634     DrawPosition(FALSE, boards[currentMove]);
9635     DisplayBothClocks();
9636     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9637 }
9638
9639 void
9640 SendEgtPath (ChessProgramState *cps)
9641 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9642         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9643
9644         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9645
9646         while(*p) {
9647             char c, *q = name+1, *r, *s;
9648
9649             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9650             while(*p && *p != ',') *q++ = *p++;
9651             *q++ = ':'; *q = 0;
9652             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9653                 strcmp(name, ",nalimov:") == 0 ) {
9654                 // take nalimov path from the menu-changeable option first, if it is defined
9655               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9656                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9657             } else
9658             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9659                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9660                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9661                 s = r = StrStr(s, ":") + 1; // beginning of path info
9662                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9663                 c = *r; *r = 0;             // temporarily null-terminate path info
9664                     *--q = 0;               // strip of trailig ':' from name
9665                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9666                 *r = c;
9667                 SendToProgram(buf,cps);     // send egtbpath command for this format
9668             }
9669             if(*p == ',') p++; // read away comma to position for next format name
9670         }
9671 }
9672
9673 void
9674 InitChessProgram (ChessProgramState *cps, int setup)
9675 /* setup needed to setup FRC opening position */
9676 {
9677     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9678     if (appData.noChessProgram) return;
9679     hintRequested = FALSE;
9680     bookRequested = FALSE;
9681
9682     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9683     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9684     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9685     if(cps->memSize) { /* [HGM] memory */
9686       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9687         SendToProgram(buf, cps);
9688     }
9689     SendEgtPath(cps); /* [HGM] EGT */
9690     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9691       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9692         SendToProgram(buf, cps);
9693     }
9694
9695     SendToProgram(cps->initString, cps);
9696     if (gameInfo.variant != VariantNormal &&
9697         gameInfo.variant != VariantLoadable
9698         /* [HGM] also send variant if board size non-standard */
9699         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9700                                             ) {
9701       char *v = VariantName(gameInfo.variant);
9702       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9703         /* [HGM] in protocol 1 we have to assume all variants valid */
9704         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9705         DisplayFatalError(buf, 0, 1);
9706         return;
9707       }
9708
9709       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9710       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9711       if( gameInfo.variant == VariantXiangqi )
9712            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9713       if( gameInfo.variant == VariantShogi )
9714            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9715       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9716            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9717       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9718           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9719            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9720       if( gameInfo.variant == VariantCourier )
9721            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9722       if( gameInfo.variant == VariantSuper )
9723            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9724       if( gameInfo.variant == VariantGreat )
9725            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9726       if( gameInfo.variant == VariantSChess )
9727            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9728       if( gameInfo.variant == VariantGrand )
9729            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9730
9731       if(overruled) {
9732         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9733                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9734            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9735            if(StrStr(cps->variants, b) == NULL) {
9736                // specific sized variant not known, check if general sizing allowed
9737                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9738                    if(StrStr(cps->variants, "boardsize") == NULL) {
9739                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9740                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9741                        DisplayFatalError(buf, 0, 1);
9742                        return;
9743                    }
9744                    /* [HGM] here we really should compare with the maximum supported board size */
9745                }
9746            }
9747       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9748       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9749       SendToProgram(buf, cps);
9750     }
9751     currentlyInitializedVariant = gameInfo.variant;
9752
9753     /* [HGM] send opening position in FRC to first engine */
9754     if(setup) {
9755           SendToProgram("force\n", cps);
9756           SendBoard(cps, 0);
9757           /* engine is now in force mode! Set flag to wake it up after first move. */
9758           setboardSpoiledMachineBlack = 1;
9759     }
9760
9761     if (cps->sendICS) {
9762       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9763       SendToProgram(buf, cps);
9764     }
9765     cps->maybeThinking = FALSE;
9766     cps->offeredDraw = 0;
9767     if (!appData.icsActive) {
9768         SendTimeControl(cps, movesPerSession, timeControl,
9769                         timeIncrement, appData.searchDepth,
9770                         searchTime);
9771     }
9772     if (appData.showThinking
9773         // [HGM] thinking: four options require thinking output to be sent
9774         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9775                                 ) {
9776         SendToProgram("post\n", cps);
9777     }
9778     SendToProgram("hard\n", cps);
9779     if (!appData.ponderNextMove) {
9780         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9781            it without being sure what state we are in first.  "hard"
9782            is not a toggle, so that one is OK.
9783          */
9784         SendToProgram("easy\n", cps);
9785     }
9786     if (cps->usePing) {
9787       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9788       SendToProgram(buf, cps);
9789     }
9790     cps->initDone = TRUE;
9791     ClearEngineOutputPane(cps == &second);
9792 }
9793
9794
9795 void
9796 StartChessProgram (ChessProgramState *cps)
9797 {
9798     char buf[MSG_SIZ];
9799     int err;
9800
9801     if (appData.noChessProgram) return;
9802     cps->initDone = FALSE;
9803
9804     if (strcmp(cps->host, "localhost") == 0) {
9805         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9806     } else if (*appData.remoteShell == NULLCHAR) {
9807         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9808     } else {
9809         if (*appData.remoteUser == NULLCHAR) {
9810           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9811                     cps->program);
9812         } else {
9813           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9814                     cps->host, appData.remoteUser, cps->program);
9815         }
9816         err = StartChildProcess(buf, "", &cps->pr);
9817     }
9818
9819     if (err != 0) {
9820       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9821         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9822         if(cps != &first) return;
9823         appData.noChessProgram = TRUE;
9824         ThawUI();
9825         SetNCPMode();
9826 //      DisplayFatalError(buf, err, 1);
9827 //      cps->pr = NoProc;
9828 //      cps->isr = NULL;
9829         return;
9830     }
9831
9832     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9833     if (cps->protocolVersion > 1) {
9834       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9835       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9836       cps->comboCnt = 0;  //                and values of combo boxes
9837       SendToProgram(buf, cps);
9838     } else {
9839       SendToProgram("xboard\n", cps);
9840     }
9841 }
9842
9843 void
9844 TwoMachinesEventIfReady P((void))
9845 {
9846   static int curMess = 0;
9847   if (first.lastPing != first.lastPong) {
9848     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9849     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9850     return;
9851   }
9852   if (second.lastPing != second.lastPong) {
9853     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9854     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9855     return;
9856   }
9857   DisplayMessage("", ""); curMess = 0;
9858   ThawUI();
9859   TwoMachinesEvent();
9860 }
9861
9862 char *
9863 MakeName (char *template)
9864 {
9865     time_t clock;
9866     struct tm *tm;
9867     static char buf[MSG_SIZ];
9868     char *p = buf;
9869     int i;
9870
9871     clock = time((time_t *)NULL);
9872     tm = localtime(&clock);
9873
9874     while(*p++ = *template++) if(p[-1] == '%') {
9875         switch(*template++) {
9876           case 0:   *p = 0; return buf;
9877           case 'Y': i = tm->tm_year+1900; break;
9878           case 'y': i = tm->tm_year-100; break;
9879           case 'M': i = tm->tm_mon+1; break;
9880           case 'd': i = tm->tm_mday; break;
9881           case 'h': i = tm->tm_hour; break;
9882           case 'm': i = tm->tm_min; break;
9883           case 's': i = tm->tm_sec; break;
9884           default:  i = 0;
9885         }
9886         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9887     }
9888     return buf;
9889 }
9890
9891 int
9892 CountPlayers (char *p)
9893 {
9894     int n = 0;
9895     while(p = strchr(p, '\n')) p++, n++; // count participants
9896     return n;
9897 }
9898
9899 FILE *
9900 WriteTourneyFile (char *results, FILE *f)
9901 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9902     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9903     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9904         // create a file with tournament description
9905         fprintf(f, "-participants {%s}\n", appData.participants);
9906         fprintf(f, "-seedBase %d\n", appData.seedBase);
9907         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9908         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9909         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9910         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9911         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9912         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9913         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9914         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9915         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9916         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9917         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9918         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9919         if(searchTime > 0)
9920                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9921         else {
9922                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9923                 fprintf(f, "-tc %s\n", appData.timeControl);
9924                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9925         }
9926         fprintf(f, "-results \"%s\"\n", results);
9927     }
9928     return f;
9929 }
9930
9931 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9932
9933 void
9934 Substitute (char *participants, int expunge)
9935 {
9936     int i, changed, changes=0, nPlayers=0;
9937     char *p, *q, *r, buf[MSG_SIZ];
9938     if(participants == NULL) return;
9939     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9940     r = p = participants; q = appData.participants;
9941     while(*p && *p == *q) {
9942         if(*p == '\n') r = p+1, nPlayers++;
9943         p++; q++;
9944     }
9945     if(*p) { // difference
9946         while(*p && *p++ != '\n');
9947         while(*q && *q++ != '\n');
9948       changed = nPlayers;
9949         changes = 1 + (strcmp(p, q) != 0);
9950     }
9951     if(changes == 1) { // a single engine mnemonic was changed
9952         q = r; while(*q) nPlayers += (*q++ == '\n');
9953         p = buf; while(*r && (*p = *r++) != '\n') p++;
9954         *p = NULLCHAR;
9955         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9956         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9957         if(mnemonic[i]) { // The substitute is valid
9958             FILE *f;
9959             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9960                 flock(fileno(f), LOCK_EX);
9961                 ParseArgsFromFile(f);
9962                 fseek(f, 0, SEEK_SET);
9963                 FREE(appData.participants); appData.participants = participants;
9964                 if(expunge) { // erase results of replaced engine
9965                     int len = strlen(appData.results), w, b, dummy;
9966                     for(i=0; i<len; i++) {
9967                         Pairing(i, nPlayers, &w, &b, &dummy);
9968                         if((w == changed || b == changed) && appData.results[i] == '*') {
9969                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9970                             fclose(f);
9971                             return;
9972                         }
9973                     }
9974                     for(i=0; i<len; i++) {
9975                         Pairing(i, nPlayers, &w, &b, &dummy);
9976                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9977                     }
9978                 }
9979                 WriteTourneyFile(appData.results, f);
9980                 fclose(f); // release lock
9981                 return;
9982             }
9983         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9984     }
9985     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9986     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9987     free(participants);
9988     return;
9989 }
9990
9991 int
9992 CreateTourney (char *name)
9993 {
9994         FILE *f;
9995         if(matchMode && strcmp(name, appData.tourneyFile)) {
9996              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9997         }
9998         if(name[0] == NULLCHAR) {
9999             if(appData.participants[0])
10000                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10001             return 0;
10002         }
10003         f = fopen(name, "r");
10004         if(f) { // file exists
10005             ASSIGN(appData.tourneyFile, name);
10006             ParseArgsFromFile(f); // parse it
10007         } else {
10008             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10009             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10010                 DisplayError(_("Not enough participants"), 0);
10011                 return 0;
10012             }
10013             ASSIGN(appData.tourneyFile, name);
10014             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10015             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10016         }
10017         fclose(f);
10018         appData.noChessProgram = FALSE;
10019         appData.clockMode = TRUE;
10020         SetGNUMode();
10021         return 1;
10022 }
10023
10024 int
10025 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10026 {
10027     char buf[MSG_SIZ], *p, *q;
10028     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10029     skip = !all && group[0]; // if group requested, we start in skip mode
10030     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10031         p = names; q = buf; header = 0;
10032         while(*p && *p != '\n') *q++ = *p++;
10033         *q = 0;
10034         if(*p == '\n') p++;
10035         if(buf[0] == '#') {
10036             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10037             depth++; // we must be entering a new group
10038             if(all) continue; // suppress printing group headers when complete list requested
10039             header = 1;
10040             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10041         }
10042         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10043         if(engineList[i]) free(engineList[i]);
10044         engineList[i] = strdup(buf);
10045         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10046         if(engineMnemonic[i]) free(engineMnemonic[i]);
10047         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10048             strcat(buf, " (");
10049             sscanf(q + 8, "%s", buf + strlen(buf));
10050             strcat(buf, ")");
10051         }
10052         engineMnemonic[i] = strdup(buf);
10053         i++;
10054     }
10055     engineList[i] = engineMnemonic[i] = NULL;
10056     return i;
10057 }
10058
10059 // following implemented as macro to avoid type limitations
10060 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10061
10062 void
10063 SwapEngines (int n)
10064 {   // swap settings for first engine and other engine (so far only some selected options)
10065     int h;
10066     char *p;
10067     if(n == 0) return;
10068     SWAP(directory, p)
10069     SWAP(chessProgram, p)
10070     SWAP(isUCI, h)
10071     SWAP(hasOwnBookUCI, h)
10072     SWAP(protocolVersion, h)
10073     SWAP(reuse, h)
10074     SWAP(scoreIsAbsolute, h)
10075     SWAP(timeOdds, h)
10076     SWAP(logo, p)
10077     SWAP(pgnName, p)
10078     SWAP(pvSAN, h)
10079     SWAP(engOptions, p)
10080 }
10081
10082 int
10083 SetPlayer (int player, char *p)
10084 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10085     int i;
10086     char buf[MSG_SIZ], *engineName;
10087     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10088     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10089     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10090     if(mnemonic[i]) {
10091         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10092         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10093         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10094         ParseArgsFromString(buf);
10095     }
10096     free(engineName);
10097     return i;
10098 }
10099
10100 char *recentEngines;
10101
10102 void
10103 RecentEngineEvent (int nr)
10104 {
10105     int n;
10106 //    SwapEngines(1); // bump first to second
10107 //    ReplaceEngine(&second, 1); // and load it there
10108     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10109     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10110     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10111         ReplaceEngine(&first, 0);
10112         FloatToFront(&appData.recentEngineList, command[n]);
10113     }
10114 }
10115
10116 int
10117 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10118 {   // determine players from game number
10119     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10120
10121     if(appData.tourneyType == 0) {
10122         roundsPerCycle = (nPlayers - 1) | 1;
10123         pairingsPerRound = nPlayers / 2;
10124     } else if(appData.tourneyType > 0) {
10125         roundsPerCycle = nPlayers - appData.tourneyType;
10126         pairingsPerRound = appData.tourneyType;
10127     }
10128     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10129     gamesPerCycle = gamesPerRound * roundsPerCycle;
10130     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10131     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10132     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10133     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10134     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10135     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10136
10137     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10138     if(appData.roundSync) *syncInterval = gamesPerRound;
10139
10140     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10141
10142     if(appData.tourneyType == 0) {
10143         if(curPairing == (nPlayers-1)/2 ) {
10144             *whitePlayer = curRound;
10145             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10146         } else {
10147             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10148             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10149             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10150             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10151         }
10152     } else if(appData.tourneyType > 1) {
10153         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10154         *whitePlayer = curRound + appData.tourneyType;
10155     } else if(appData.tourneyType > 0) {
10156         *whitePlayer = curPairing;
10157         *blackPlayer = curRound + appData.tourneyType;
10158     }
10159
10160     // take care of white/black alternation per round. 
10161     // For cycles and games this is already taken care of by default, derived from matchGame!
10162     return curRound & 1;
10163 }
10164
10165 int
10166 NextTourneyGame (int nr, int *swapColors)
10167 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10168     char *p, *q;
10169     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10170     FILE *tf;
10171     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10172     tf = fopen(appData.tourneyFile, "r");
10173     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10174     ParseArgsFromFile(tf); fclose(tf);
10175     InitTimeControls(); // TC might be altered from tourney file
10176
10177     nPlayers = CountPlayers(appData.participants); // count participants
10178     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10179     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10180
10181     if(syncInterval) {
10182         p = q = appData.results;
10183         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10184         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10185             DisplayMessage(_("Waiting for other game(s)"),"");
10186             waitingForGame = TRUE;
10187             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10188             return 0;
10189         }
10190         waitingForGame = FALSE;
10191     }
10192
10193     if(appData.tourneyType < 0) {
10194         if(nr>=0 && !pairingReceived) {
10195             char buf[1<<16];
10196             if(pairing.pr == NoProc) {
10197                 if(!appData.pairingEngine[0]) {
10198                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10199                     return 0;
10200                 }
10201                 StartChessProgram(&pairing); // starts the pairing engine
10202             }
10203             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10204             SendToProgram(buf, &pairing);
10205             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10206             SendToProgram(buf, &pairing);
10207             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10208         }
10209         pairingReceived = 0;                              // ... so we continue here 
10210         *swapColors = 0;
10211         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10212         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10213         matchGame = 1; roundNr = nr / syncInterval + 1;
10214     }
10215
10216     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10217
10218     // redefine engines, engine dir, etc.
10219     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10220     if(first.pr == NoProc) {
10221       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10222       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10223     }
10224     if(second.pr == NoProc) {
10225       SwapEngines(1);
10226       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10227       SwapEngines(1);         // and make that valid for second engine by swapping
10228       InitEngine(&second, 1);
10229     }
10230     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10231     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10232     return 1;
10233 }
10234
10235 void
10236 NextMatchGame ()
10237 {   // performs game initialization that does not invoke engines, and then tries to start the game
10238     int res, firstWhite, swapColors = 0;
10239     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10240     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
10241         char buf[MSG_SIZ];
10242         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10243         if(strcmp(buf, currentDebugFile)) { // name has changed
10244             FILE *f = fopen(buf, "w");
10245             if(f) { // if opening the new file failed, just keep using the old one
10246                 ASSIGN(currentDebugFile, buf);
10247                 fclose(debugFP);
10248                 debugFP = f;
10249             }
10250             if(appData.serverFileName) {
10251                 if(serverFP) fclose(serverFP);
10252                 serverFP = fopen(appData.serverFileName, "w");
10253                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10254                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10255             }
10256         }
10257     }
10258     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10259     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10260     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10261     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10262     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10263     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10264     Reset(FALSE, first.pr != NoProc);
10265     res = LoadGameOrPosition(matchGame); // setup game
10266     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10267     if(!res) return; // abort when bad game/pos file
10268     TwoMachinesEvent();
10269 }
10270
10271 void
10272 UserAdjudicationEvent (int result)
10273 {
10274     ChessMove gameResult = GameIsDrawn;
10275
10276     if( result > 0 ) {
10277         gameResult = WhiteWins;
10278     }
10279     else if( result < 0 ) {
10280         gameResult = BlackWins;
10281     }
10282
10283     if( gameMode == TwoMachinesPlay ) {
10284         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10285     }
10286 }
10287
10288
10289 // [HGM] save: calculate checksum of game to make games easily identifiable
10290 int
10291 StringCheckSum (char *s)
10292 {
10293         int i = 0;
10294         if(s==NULL) return 0;
10295         while(*s) i = i*259 + *s++;
10296         return i;
10297 }
10298
10299 int
10300 GameCheckSum ()
10301 {
10302         int i, sum=0;
10303         for(i=backwardMostMove; i<forwardMostMove; i++) {
10304                 sum += pvInfoList[i].depth;
10305                 sum += StringCheckSum(parseList[i]);
10306                 sum += StringCheckSum(commentList[i]);
10307                 sum *= 261;
10308         }
10309         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10310         return sum + StringCheckSum(commentList[i]);
10311 } // end of save patch
10312
10313 void
10314 GameEnds (ChessMove result, char *resultDetails, int whosays)
10315 {
10316     GameMode nextGameMode;
10317     int isIcsGame;
10318     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10319
10320     if(endingGame) return; /* [HGM] crash: forbid recursion */
10321     endingGame = 1;
10322     if(twoBoards) { // [HGM] dual: switch back to one board
10323         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10324         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10325     }
10326     if (appData.debugMode) {
10327       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10328               result, resultDetails ? resultDetails : "(null)", whosays);
10329     }
10330
10331     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10332
10333     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10334         /* If we are playing on ICS, the server decides when the
10335            game is over, but the engine can offer to draw, claim
10336            a draw, or resign.
10337          */
10338 #if ZIPPY
10339         if (appData.zippyPlay && first.initDone) {
10340             if (result == GameIsDrawn) {
10341                 /* In case draw still needs to be claimed */
10342                 SendToICS(ics_prefix);
10343                 SendToICS("draw\n");
10344             } else if (StrCaseStr(resultDetails, "resign")) {
10345                 SendToICS(ics_prefix);
10346                 SendToICS("resign\n");
10347             }
10348         }
10349 #endif
10350         endingGame = 0; /* [HGM] crash */
10351         return;
10352     }
10353
10354     /* If we're loading the game from a file, stop */
10355     if (whosays == GE_FILE) {
10356       (void) StopLoadGameTimer();
10357       gameFileFP = NULL;
10358     }
10359
10360     /* Cancel draw offers */
10361     first.offeredDraw = second.offeredDraw = 0;
10362
10363     /* If this is an ICS game, only ICS can really say it's done;
10364        if not, anyone can. */
10365     isIcsGame = (gameMode == IcsPlayingWhite ||
10366                  gameMode == IcsPlayingBlack ||
10367                  gameMode == IcsObserving    ||
10368                  gameMode == IcsExamining);
10369
10370     if (!isIcsGame || whosays == GE_ICS) {
10371         /* OK -- not an ICS game, or ICS said it was done */
10372         StopClocks();
10373         if (!isIcsGame && !appData.noChessProgram)
10374           SetUserThinkingEnables();
10375
10376         /* [HGM] if a machine claims the game end we verify this claim */
10377         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10378             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10379                 char claimer;
10380                 ChessMove trueResult = (ChessMove) -1;
10381
10382                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10383                                             first.twoMachinesColor[0] :
10384                                             second.twoMachinesColor[0] ;
10385
10386                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10387                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10388                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10389                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10390                 } else
10391                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10392                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10393                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10394                 } else
10395                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10396                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10397                 }
10398
10399                 // now verify win claims, but not in drop games, as we don't understand those yet
10400                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10401                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10402                     (result == WhiteWins && claimer == 'w' ||
10403                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10404                       if (appData.debugMode) {
10405                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10406                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10407                       }
10408                       if(result != trueResult) {
10409                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10410                               result = claimer == 'w' ? BlackWins : WhiteWins;
10411                               resultDetails = buf;
10412                       }
10413                 } else
10414                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10415                     && (forwardMostMove <= backwardMostMove ||
10416                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10417                         (claimer=='b')==(forwardMostMove&1))
10418                                                                                   ) {
10419                       /* [HGM] verify: draws that were not flagged are false claims */
10420                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10421                       result = claimer == 'w' ? BlackWins : WhiteWins;
10422                       resultDetails = buf;
10423                 }
10424                 /* (Claiming a loss is accepted no questions asked!) */
10425             }
10426             /* [HGM] bare: don't allow bare King to win */
10427             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10428                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10429                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10430                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10431                && result != GameIsDrawn)
10432             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10433                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10434                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10435                         if(p >= 0 && p <= (int)WhiteKing) k++;
10436                 }
10437                 if (appData.debugMode) {
10438                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10439                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10440                 }
10441                 if(k <= 1) {
10442                         result = GameIsDrawn;
10443                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10444                         resultDetails = buf;
10445                 }
10446             }
10447         }
10448
10449
10450         if(serverMoves != NULL && !loadFlag) { char c = '=';
10451             if(result==WhiteWins) c = '+';
10452             if(result==BlackWins) c = '-';
10453             if(resultDetails != NULL)
10454                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10455         }
10456         if (resultDetails != NULL) {
10457             gameInfo.result = result;
10458             gameInfo.resultDetails = StrSave(resultDetails);
10459
10460             /* display last move only if game was not loaded from file */
10461             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10462                 DisplayMove(currentMove - 1);
10463
10464             if (forwardMostMove != 0) {
10465                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10466                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10467                                                                 ) {
10468                     if (*appData.saveGameFile != NULLCHAR) {
10469                         SaveGameToFile(appData.saveGameFile, TRUE);
10470                     } else if (appData.autoSaveGames) {
10471                         AutoSaveGame();
10472                     }
10473                     if (*appData.savePositionFile != NULLCHAR) {
10474                         SavePositionToFile(appData.savePositionFile);
10475                     }
10476                 }
10477             }
10478
10479             /* Tell program how game ended in case it is learning */
10480             /* [HGM] Moved this to after saving the PGN, just in case */
10481             /* engine died and we got here through time loss. In that */
10482             /* case we will get a fatal error writing the pipe, which */
10483             /* would otherwise lose us the PGN.                       */
10484             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10485             /* output during GameEnds should never be fatal anymore   */
10486             if (gameMode == MachinePlaysWhite ||
10487                 gameMode == MachinePlaysBlack ||
10488                 gameMode == TwoMachinesPlay ||
10489                 gameMode == IcsPlayingWhite ||
10490                 gameMode == IcsPlayingBlack ||
10491                 gameMode == BeginningOfGame) {
10492                 char buf[MSG_SIZ];
10493                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10494                         resultDetails);
10495                 if (first.pr != NoProc) {
10496                     SendToProgram(buf, &first);
10497                 }
10498                 if (second.pr != NoProc &&
10499                     gameMode == TwoMachinesPlay) {
10500                     SendToProgram(buf, &second);
10501                 }
10502             }
10503         }
10504
10505         if (appData.icsActive) {
10506             if (appData.quietPlay &&
10507                 (gameMode == IcsPlayingWhite ||
10508                  gameMode == IcsPlayingBlack)) {
10509                 SendToICS(ics_prefix);
10510                 SendToICS("set shout 1\n");
10511             }
10512             nextGameMode = IcsIdle;
10513             ics_user_moved = FALSE;
10514             /* clean up premove.  It's ugly when the game has ended and the
10515              * premove highlights are still on the board.
10516              */
10517             if (gotPremove) {
10518               gotPremove = FALSE;
10519               ClearPremoveHighlights();
10520               DrawPosition(FALSE, boards[currentMove]);
10521             }
10522             if (whosays == GE_ICS) {
10523                 switch (result) {
10524                 case WhiteWins:
10525                     if (gameMode == IcsPlayingWhite)
10526                         PlayIcsWinSound();
10527                     else if(gameMode == IcsPlayingBlack)
10528                         PlayIcsLossSound();
10529                     break;
10530                 case BlackWins:
10531                     if (gameMode == IcsPlayingBlack)
10532                         PlayIcsWinSound();
10533                     else if(gameMode == IcsPlayingWhite)
10534                         PlayIcsLossSound();
10535                     break;
10536                 case GameIsDrawn:
10537                     PlayIcsDrawSound();
10538                     break;
10539                 default:
10540                     PlayIcsUnfinishedSound();
10541                 }
10542             }
10543         } else if (gameMode == EditGame ||
10544                    gameMode == PlayFromGameFile ||
10545                    gameMode == AnalyzeMode ||
10546                    gameMode == AnalyzeFile) {
10547             nextGameMode = gameMode;
10548         } else {
10549             nextGameMode = EndOfGame;
10550         }
10551         pausing = FALSE;
10552         ModeHighlight();
10553     } else {
10554         nextGameMode = gameMode;
10555     }
10556
10557     if (appData.noChessProgram) {
10558         gameMode = nextGameMode;
10559         ModeHighlight();
10560         endingGame = 0; /* [HGM] crash */
10561         return;
10562     }
10563
10564     if (first.reuse) {
10565         /* Put first chess program into idle state */
10566         if (first.pr != NoProc &&
10567             (gameMode == MachinePlaysWhite ||
10568              gameMode == MachinePlaysBlack ||
10569              gameMode == TwoMachinesPlay ||
10570              gameMode == IcsPlayingWhite ||
10571              gameMode == IcsPlayingBlack ||
10572              gameMode == BeginningOfGame)) {
10573             SendToProgram("force\n", &first);
10574             if (first.usePing) {
10575               char buf[MSG_SIZ];
10576               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10577               SendToProgram(buf, &first);
10578             }
10579         }
10580     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10581         /* Kill off first chess program */
10582         if (first.isr != NULL)
10583           RemoveInputSource(first.isr);
10584         first.isr = NULL;
10585
10586         if (first.pr != NoProc) {
10587             ExitAnalyzeMode();
10588             DoSleep( appData.delayBeforeQuit );
10589             SendToProgram("quit\n", &first);
10590             DoSleep( appData.delayAfterQuit );
10591             DestroyChildProcess(first.pr, first.useSigterm);
10592         }
10593         first.pr = NoProc;
10594     }
10595     if (second.reuse) {
10596         /* Put second chess program into idle state */
10597         if (second.pr != NoProc &&
10598             gameMode == TwoMachinesPlay) {
10599             SendToProgram("force\n", &second);
10600             if (second.usePing) {
10601               char buf[MSG_SIZ];
10602               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10603               SendToProgram(buf, &second);
10604             }
10605         }
10606     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10607         /* Kill off second chess program */
10608         if (second.isr != NULL)
10609           RemoveInputSource(second.isr);
10610         second.isr = NULL;
10611
10612         if (second.pr != NoProc) {
10613             DoSleep( appData.delayBeforeQuit );
10614             SendToProgram("quit\n", &second);
10615             DoSleep( appData.delayAfterQuit );
10616             DestroyChildProcess(second.pr, second.useSigterm);
10617         }
10618         second.pr = NoProc;
10619     }
10620
10621     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10622         char resChar = '=';
10623         switch (result) {
10624         case WhiteWins:
10625           resChar = '+';
10626           if (first.twoMachinesColor[0] == 'w') {
10627             first.matchWins++;
10628           } else {
10629             second.matchWins++;
10630           }
10631           break;
10632         case BlackWins:
10633           resChar = '-';
10634           if (first.twoMachinesColor[0] == 'b') {
10635             first.matchWins++;
10636           } else {
10637             second.matchWins++;
10638           }
10639           break;
10640         case GameUnfinished:
10641           resChar = ' ';
10642         default:
10643           break;
10644         }
10645
10646         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10647         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10648             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10649             ReserveGame(nextGame, resChar); // sets nextGame
10650             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10651             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10652         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10653
10654         if (nextGame <= appData.matchGames && !abortMatch) {
10655             gameMode = nextGameMode;
10656             matchGame = nextGame; // this will be overruled in tourney mode!
10657             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10658             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10659             endingGame = 0; /* [HGM] crash */
10660             return;
10661         } else {
10662             gameMode = nextGameMode;
10663             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10664                      first.tidy, second.tidy,
10665                      first.matchWins, second.matchWins,
10666                      appData.matchGames - (first.matchWins + second.matchWins));
10667             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10668             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10669             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10670             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10671                 first.twoMachinesColor = "black\n";
10672                 second.twoMachinesColor = "white\n";
10673             } else {
10674                 first.twoMachinesColor = "white\n";
10675                 second.twoMachinesColor = "black\n";
10676             }
10677         }
10678     }
10679     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10680         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10681       ExitAnalyzeMode();
10682     gameMode = nextGameMode;
10683     ModeHighlight();
10684     endingGame = 0;  /* [HGM] crash */
10685     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10686         if(matchMode == TRUE) { // match through command line: exit with or without popup
10687             if(ranking) {
10688                 ToNrEvent(forwardMostMove);
10689                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10690                 else ExitEvent(0);
10691             } else DisplayFatalError(buf, 0, 0);
10692         } else { // match through menu; just stop, with or without popup
10693             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10694             ModeHighlight();
10695             if(ranking){
10696                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10697             } else DisplayNote(buf);
10698       }
10699       if(ranking) free(ranking);
10700     }
10701 }
10702
10703 /* Assumes program was just initialized (initString sent).
10704    Leaves program in force mode. */
10705 void
10706 FeedMovesToProgram (ChessProgramState *cps, int upto)
10707 {
10708     int i;
10709
10710     if (appData.debugMode)
10711       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10712               startedFromSetupPosition ? "position and " : "",
10713               backwardMostMove, upto, cps->which);
10714     if(currentlyInitializedVariant != gameInfo.variant) {
10715       char buf[MSG_SIZ];
10716         // [HGM] variantswitch: make engine aware of new variant
10717         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10718                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10719         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10720         SendToProgram(buf, cps);
10721         currentlyInitializedVariant = gameInfo.variant;
10722     }
10723     SendToProgram("force\n", cps);
10724     if (startedFromSetupPosition) {
10725         SendBoard(cps, backwardMostMove);
10726     if (appData.debugMode) {
10727         fprintf(debugFP, "feedMoves\n");
10728     }
10729     }
10730     for (i = backwardMostMove; i < upto; i++) {
10731         SendMoveToProgram(i, cps);
10732     }
10733 }
10734
10735
10736 int
10737 ResurrectChessProgram ()
10738 {
10739      /* The chess program may have exited.
10740         If so, restart it and feed it all the moves made so far. */
10741     static int doInit = 0;
10742
10743     if (appData.noChessProgram) return 1;
10744
10745     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10746         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10747         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10748         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10749     } else {
10750         if (first.pr != NoProc) return 1;
10751         StartChessProgram(&first);
10752     }
10753     InitChessProgram(&first, FALSE);
10754     FeedMovesToProgram(&first, currentMove);
10755
10756     if (!first.sendTime) {
10757         /* can't tell gnuchess what its clock should read,
10758            so we bow to its notion. */
10759         ResetClocks();
10760         timeRemaining[0][currentMove] = whiteTimeRemaining;
10761         timeRemaining[1][currentMove] = blackTimeRemaining;
10762     }
10763
10764     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10765                 appData.icsEngineAnalyze) && first.analysisSupport) {
10766       SendToProgram("analyze\n", &first);
10767       first.analyzing = TRUE;
10768     }
10769     return 1;
10770 }
10771
10772 /*
10773  * Button procedures
10774  */
10775 void
10776 Reset (int redraw, int init)
10777 {
10778     int i;
10779
10780     if (appData.debugMode) {
10781         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10782                 redraw, init, gameMode);
10783     }
10784     CleanupTail(); // [HGM] vari: delete any stored variations
10785     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10786     pausing = pauseExamInvalid = FALSE;
10787     startedFromSetupPosition = blackPlaysFirst = FALSE;
10788     firstMove = TRUE;
10789     whiteFlag = blackFlag = FALSE;
10790     userOfferedDraw = FALSE;
10791     hintRequested = bookRequested = FALSE;
10792     first.maybeThinking = FALSE;
10793     second.maybeThinking = FALSE;
10794     first.bookSuspend = FALSE; // [HGM] book
10795     second.bookSuspend = FALSE;
10796     thinkOutput[0] = NULLCHAR;
10797     lastHint[0] = NULLCHAR;
10798     ClearGameInfo(&gameInfo);
10799     gameInfo.variant = StringToVariant(appData.variant);
10800     ics_user_moved = ics_clock_paused = FALSE;
10801     ics_getting_history = H_FALSE;
10802     ics_gamenum = -1;
10803     white_holding[0] = black_holding[0] = NULLCHAR;
10804     ClearProgramStats();
10805     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10806
10807     ResetFrontEnd();
10808     ClearHighlights();
10809     flipView = appData.flipView;
10810     ClearPremoveHighlights();
10811     gotPremove = FALSE;
10812     alarmSounded = FALSE;
10813
10814     GameEnds(EndOfFile, NULL, GE_PLAYER);
10815     if(appData.serverMovesName != NULL) {
10816         /* [HGM] prepare to make moves file for broadcasting */
10817         clock_t t = clock();
10818         if(serverMoves != NULL) fclose(serverMoves);
10819         serverMoves = fopen(appData.serverMovesName, "r");
10820         if(serverMoves != NULL) {
10821             fclose(serverMoves);
10822             /* delay 15 sec before overwriting, so all clients can see end */
10823             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10824         }
10825         serverMoves = fopen(appData.serverMovesName, "w");
10826     }
10827
10828     ExitAnalyzeMode();
10829     gameMode = BeginningOfGame;
10830     ModeHighlight();
10831     if(appData.icsActive) gameInfo.variant = VariantNormal;
10832     currentMove = forwardMostMove = backwardMostMove = 0;
10833     MarkTargetSquares(1);
10834     InitPosition(redraw);
10835     for (i = 0; i < MAX_MOVES; i++) {
10836         if (commentList[i] != NULL) {
10837             free(commentList[i]);
10838             commentList[i] = NULL;
10839         }
10840     }
10841     ResetClocks();
10842     timeRemaining[0][0] = whiteTimeRemaining;
10843     timeRemaining[1][0] = blackTimeRemaining;
10844
10845     if (first.pr == NoProc) {
10846         StartChessProgram(&first);
10847     }
10848     if (init) {
10849             InitChessProgram(&first, startedFromSetupPosition);
10850     }
10851     DisplayTitle("");
10852     DisplayMessage("", "");
10853     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10854     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10855     ClearMap();        // [HGM] exclude: invalidate map
10856 }
10857
10858 void
10859 AutoPlayGameLoop ()
10860 {
10861     for (;;) {
10862         if (!AutoPlayOneMove())
10863           return;
10864         if (matchMode || appData.timeDelay == 0)
10865           continue;
10866         if (appData.timeDelay < 0)
10867           return;
10868         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10869         break;
10870     }
10871 }
10872
10873
10874 int
10875 AutoPlayOneMove ()
10876 {
10877     int fromX, fromY, toX, toY;
10878
10879     if (appData.debugMode) {
10880       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10881     }
10882
10883     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10884       return FALSE;
10885
10886     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10887       pvInfoList[currentMove].depth = programStats.depth;
10888       pvInfoList[currentMove].score = programStats.score;
10889       pvInfoList[currentMove].time  = 0;
10890       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10891     }
10892
10893     if (currentMove >= forwardMostMove) {
10894       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10895 //      gameMode = EndOfGame;
10896 //      ModeHighlight();
10897
10898       /* [AS] Clear current move marker at the end of a game */
10899       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10900
10901       return FALSE;
10902     }
10903
10904     toX = moveList[currentMove][2] - AAA;
10905     toY = moveList[currentMove][3] - ONE;
10906
10907     if (moveList[currentMove][1] == '@') {
10908         if (appData.highlightLastMove) {
10909             SetHighlights(-1, -1, toX, toY);
10910         }
10911     } else {
10912         fromX = moveList[currentMove][0] - AAA;
10913         fromY = moveList[currentMove][1] - ONE;
10914
10915         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10916
10917         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10918
10919         if (appData.highlightLastMove) {
10920             SetHighlights(fromX, fromY, toX, toY);
10921         }
10922     }
10923     DisplayMove(currentMove);
10924     SendMoveToProgram(currentMove++, &first);
10925     DisplayBothClocks();
10926     DrawPosition(FALSE, boards[currentMove]);
10927     // [HGM] PV info: always display, routine tests if empty
10928     DisplayComment(currentMove - 1, commentList[currentMove]);
10929     return TRUE;
10930 }
10931
10932
10933 int
10934 LoadGameOneMove (ChessMove readAhead)
10935 {
10936     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10937     char promoChar = NULLCHAR;
10938     ChessMove moveType;
10939     char move[MSG_SIZ];
10940     char *p, *q;
10941
10942     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10943         gameMode != AnalyzeMode && gameMode != Training) {
10944         gameFileFP = NULL;
10945         return FALSE;
10946     }
10947
10948     yyboardindex = forwardMostMove;
10949     if (readAhead != EndOfFile) {
10950       moveType = readAhead;
10951     } else {
10952       if (gameFileFP == NULL)
10953           return FALSE;
10954       moveType = (ChessMove) Myylex();
10955     }
10956
10957     done = FALSE;
10958     switch (moveType) {
10959       case Comment:
10960         if (appData.debugMode)
10961           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10962         p = yy_text;
10963
10964         /* append the comment but don't display it */
10965         AppendComment(currentMove, p, FALSE);
10966         return TRUE;
10967
10968       case WhiteCapturesEnPassant:
10969       case BlackCapturesEnPassant:
10970       case WhitePromotion:
10971       case BlackPromotion:
10972       case WhiteNonPromotion:
10973       case BlackNonPromotion:
10974       case NormalMove:
10975       case WhiteKingSideCastle:
10976       case WhiteQueenSideCastle:
10977       case BlackKingSideCastle:
10978       case BlackQueenSideCastle:
10979       case WhiteKingSideCastleWild:
10980       case WhiteQueenSideCastleWild:
10981       case BlackKingSideCastleWild:
10982       case BlackQueenSideCastleWild:
10983       /* PUSH Fabien */
10984       case WhiteHSideCastleFR:
10985       case WhiteASideCastleFR:
10986       case BlackHSideCastleFR:
10987       case BlackASideCastleFR:
10988       /* POP Fabien */
10989         if (appData.debugMode)
10990           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10991         fromX = currentMoveString[0] - AAA;
10992         fromY = currentMoveString[1] - ONE;
10993         toX = currentMoveString[2] - AAA;
10994         toY = currentMoveString[3] - ONE;
10995         promoChar = currentMoveString[4];
10996         break;
10997
10998       case WhiteDrop:
10999       case BlackDrop:
11000         if (appData.debugMode)
11001           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11002         fromX = moveType == WhiteDrop ?
11003           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11004         (int) CharToPiece(ToLower(currentMoveString[0]));
11005         fromY = DROP_RANK;
11006         toX = currentMoveString[2] - AAA;
11007         toY = currentMoveString[3] - ONE;
11008         break;
11009
11010       case WhiteWins:
11011       case BlackWins:
11012       case GameIsDrawn:
11013       case GameUnfinished:
11014         if (appData.debugMode)
11015           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11016         p = strchr(yy_text, '{');
11017         if (p == NULL) p = strchr(yy_text, '(');
11018         if (p == NULL) {
11019             p = yy_text;
11020             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11021         } else {
11022             q = strchr(p, *p == '{' ? '}' : ')');
11023             if (q != NULL) *q = NULLCHAR;
11024             p++;
11025         }
11026         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11027         GameEnds(moveType, p, GE_FILE);
11028         done = TRUE;
11029         if (cmailMsgLoaded) {
11030             ClearHighlights();
11031             flipView = WhiteOnMove(currentMove);
11032             if (moveType == GameUnfinished) flipView = !flipView;
11033             if (appData.debugMode)
11034               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11035         }
11036         break;
11037
11038       case EndOfFile:
11039         if (appData.debugMode)
11040           fprintf(debugFP, "Parser hit end of file\n");
11041         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11042           case MT_NONE:
11043           case MT_CHECK:
11044             break;
11045           case MT_CHECKMATE:
11046           case MT_STAINMATE:
11047             if (WhiteOnMove(currentMove)) {
11048                 GameEnds(BlackWins, "Black mates", GE_FILE);
11049             } else {
11050                 GameEnds(WhiteWins, "White mates", GE_FILE);
11051             }
11052             break;
11053           case MT_STALEMATE:
11054             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11055             break;
11056         }
11057         done = TRUE;
11058         break;
11059
11060       case MoveNumberOne:
11061         if (lastLoadGameStart == GNUChessGame) {
11062             /* GNUChessGames have numbers, but they aren't move numbers */
11063             if (appData.debugMode)
11064               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11065                       yy_text, (int) moveType);
11066             return LoadGameOneMove(EndOfFile); /* tail recursion */
11067         }
11068         /* else fall thru */
11069
11070       case XBoardGame:
11071       case GNUChessGame:
11072       case PGNTag:
11073         /* Reached start of next game in file */
11074         if (appData.debugMode)
11075           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11076         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11077           case MT_NONE:
11078           case MT_CHECK:
11079             break;
11080           case MT_CHECKMATE:
11081           case MT_STAINMATE:
11082             if (WhiteOnMove(currentMove)) {
11083                 GameEnds(BlackWins, "Black mates", GE_FILE);
11084             } else {
11085                 GameEnds(WhiteWins, "White mates", GE_FILE);
11086             }
11087             break;
11088           case MT_STALEMATE:
11089             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11090             break;
11091         }
11092         done = TRUE;
11093         break;
11094
11095       case PositionDiagram:     /* should not happen; ignore */
11096       case ElapsedTime:         /* ignore */
11097       case NAG:                 /* ignore */
11098         if (appData.debugMode)
11099           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11100                   yy_text, (int) moveType);
11101         return LoadGameOneMove(EndOfFile); /* tail recursion */
11102
11103       case IllegalMove:
11104         if (appData.testLegality) {
11105             if (appData.debugMode)
11106               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11107             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11108                     (forwardMostMove / 2) + 1,
11109                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11110             DisplayError(move, 0);
11111             done = TRUE;
11112         } else {
11113             if (appData.debugMode)
11114               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11115                       yy_text, currentMoveString);
11116             fromX = currentMoveString[0] - AAA;
11117             fromY = currentMoveString[1] - ONE;
11118             toX = currentMoveString[2] - AAA;
11119             toY = currentMoveString[3] - ONE;
11120             promoChar = currentMoveString[4];
11121         }
11122         break;
11123
11124       case AmbiguousMove:
11125         if (appData.debugMode)
11126           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11127         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11128                 (forwardMostMove / 2) + 1,
11129                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11130         DisplayError(move, 0);
11131         done = TRUE;
11132         break;
11133
11134       default:
11135       case ImpossibleMove:
11136         if (appData.debugMode)
11137           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11138         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11139                 (forwardMostMove / 2) + 1,
11140                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11141         DisplayError(move, 0);
11142         done = TRUE;
11143         break;
11144     }
11145
11146     if (done) {
11147         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11148             DrawPosition(FALSE, boards[currentMove]);
11149             DisplayBothClocks();
11150             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11151               DisplayComment(currentMove - 1, commentList[currentMove]);
11152         }
11153         (void) StopLoadGameTimer();
11154         gameFileFP = NULL;
11155         cmailOldMove = forwardMostMove;
11156         return FALSE;
11157     } else {
11158         /* currentMoveString is set as a side-effect of yylex */
11159
11160         thinkOutput[0] = NULLCHAR;
11161         MakeMove(fromX, fromY, toX, toY, promoChar);
11162         currentMove = forwardMostMove;
11163         return TRUE;
11164     }
11165 }
11166
11167 /* Load the nth game from the given file */
11168 int
11169 LoadGameFromFile (char *filename, int n, char *title, int useList)
11170 {
11171     FILE *f;
11172     char buf[MSG_SIZ];
11173
11174     if (strcmp(filename, "-") == 0) {
11175         f = stdin;
11176         title = "stdin";
11177     } else {
11178         f = fopen(filename, "rb");
11179         if (f == NULL) {
11180           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11181             DisplayError(buf, errno);
11182             return FALSE;
11183         }
11184     }
11185     if (fseek(f, 0, 0) == -1) {
11186         /* f is not seekable; probably a pipe */
11187         useList = FALSE;
11188     }
11189     if (useList && n == 0) {
11190         int error = GameListBuild(f);
11191         if (error) {
11192             DisplayError(_("Cannot build game list"), error);
11193         } else if (!ListEmpty(&gameList) &&
11194                    ((ListGame *) gameList.tailPred)->number > 1) {
11195             GameListPopUp(f, title);
11196             return TRUE;
11197         }
11198         GameListDestroy();
11199         n = 1;
11200     }
11201     if (n == 0) n = 1;
11202     return LoadGame(f, n, title, FALSE);
11203 }
11204
11205
11206 void
11207 MakeRegisteredMove ()
11208 {
11209     int fromX, fromY, toX, toY;
11210     char promoChar;
11211     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11212         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11213           case CMAIL_MOVE:
11214           case CMAIL_DRAW:
11215             if (appData.debugMode)
11216               fprintf(debugFP, "Restoring %s for game %d\n",
11217                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11218
11219             thinkOutput[0] = NULLCHAR;
11220             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11221             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11222             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11223             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11224             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11225             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11226             MakeMove(fromX, fromY, toX, toY, promoChar);
11227             ShowMove(fromX, fromY, toX, toY);
11228
11229             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11230               case MT_NONE:
11231               case MT_CHECK:
11232                 break;
11233
11234               case MT_CHECKMATE:
11235               case MT_STAINMATE:
11236                 if (WhiteOnMove(currentMove)) {
11237                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11238                 } else {
11239                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11240                 }
11241                 break;
11242
11243               case MT_STALEMATE:
11244                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11245                 break;
11246             }
11247
11248             break;
11249
11250           case CMAIL_RESIGN:
11251             if (WhiteOnMove(currentMove)) {
11252                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11253             } else {
11254                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11255             }
11256             break;
11257
11258           case CMAIL_ACCEPT:
11259             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11260             break;
11261
11262           default:
11263             break;
11264         }
11265     }
11266
11267     return;
11268 }
11269
11270 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11271 int
11272 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11273 {
11274     int retVal;
11275
11276     if (gameNumber > nCmailGames) {
11277         DisplayError(_("No more games in this message"), 0);
11278         return FALSE;
11279     }
11280     if (f == lastLoadGameFP) {
11281         int offset = gameNumber - lastLoadGameNumber;
11282         if (offset == 0) {
11283             cmailMsg[0] = NULLCHAR;
11284             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11285                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11286                 nCmailMovesRegistered--;
11287             }
11288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11289             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11290                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11291             }
11292         } else {
11293             if (! RegisterMove()) return FALSE;
11294         }
11295     }
11296
11297     retVal = LoadGame(f, gameNumber, title, useList);
11298
11299     /* Make move registered during previous look at this game, if any */
11300     MakeRegisteredMove();
11301
11302     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11303         commentList[currentMove]
11304           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11305         DisplayComment(currentMove - 1, commentList[currentMove]);
11306     }
11307
11308     return retVal;
11309 }
11310
11311 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11312 int
11313 ReloadGame (int offset)
11314 {
11315     int gameNumber = lastLoadGameNumber + offset;
11316     if (lastLoadGameFP == NULL) {
11317         DisplayError(_("No game has been loaded yet"), 0);
11318         return FALSE;
11319     }
11320     if (gameNumber <= 0) {
11321         DisplayError(_("Can't back up any further"), 0);
11322         return FALSE;
11323     }
11324     if (cmailMsgLoaded) {
11325         return CmailLoadGame(lastLoadGameFP, gameNumber,
11326                              lastLoadGameTitle, lastLoadGameUseList);
11327     } else {
11328         return LoadGame(lastLoadGameFP, gameNumber,
11329                         lastLoadGameTitle, lastLoadGameUseList);
11330     }
11331 }
11332
11333 int keys[EmptySquare+1];
11334
11335 int
11336 PositionMatches (Board b1, Board b2)
11337 {
11338     int r, f, sum=0;
11339     switch(appData.searchMode) {
11340         case 1: return CompareWithRights(b1, b2);
11341         case 2:
11342             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11343                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11344             }
11345             return TRUE;
11346         case 3:
11347             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11348               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11349                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11350             }
11351             return sum==0;
11352         case 4:
11353             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11354                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11355             }
11356             return sum==0;
11357     }
11358     return TRUE;
11359 }
11360
11361 #define Q_PROMO  4
11362 #define Q_EP     3
11363 #define Q_BCASTL 2
11364 #define Q_WCASTL 1
11365
11366 int pieceList[256], quickBoard[256];
11367 ChessSquare pieceType[256] = { EmptySquare };
11368 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11369 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11370 int soughtTotal, turn;
11371 Boolean epOK, flipSearch;
11372
11373 typedef struct {
11374     unsigned char piece, to;
11375 } Move;
11376
11377 #define DSIZE (250000)
11378
11379 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11380 Move *moveDatabase = initialSpace;
11381 unsigned int movePtr, dataSize = DSIZE;
11382
11383 int
11384 MakePieceList (Board board, int *counts)
11385 {
11386     int r, f, n=Q_PROMO, total=0;
11387     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11388     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11389         int sq = f + (r<<4);
11390         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11391             quickBoard[sq] = ++n;
11392             pieceList[n] = sq;
11393             pieceType[n] = board[r][f];
11394             counts[board[r][f]]++;
11395             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11396             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11397             total++;
11398         }
11399     }
11400     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11401     return total;
11402 }
11403
11404 void
11405 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11406 {
11407     int sq = fromX + (fromY<<4);
11408     int piece = quickBoard[sq];
11409     quickBoard[sq] = 0;
11410     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11411     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11412         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11413         moveDatabase[movePtr++].piece = Q_WCASTL;
11414         quickBoard[sq] = piece;
11415         piece = quickBoard[from]; quickBoard[from] = 0;
11416         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11417     } else
11418     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11419         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11420         moveDatabase[movePtr++].piece = Q_BCASTL;
11421         quickBoard[sq] = piece;
11422         piece = quickBoard[from]; quickBoard[from] = 0;
11423         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11424     } else
11425     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11426         quickBoard[(fromY<<4)+toX] = 0;
11427         moveDatabase[movePtr].piece = Q_EP;
11428         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11429         moveDatabase[movePtr].to = sq;
11430     } else
11431     if(promoPiece != pieceType[piece]) {
11432         moveDatabase[movePtr++].piece = Q_PROMO;
11433         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11434     }
11435     moveDatabase[movePtr].piece = piece;
11436     quickBoard[sq] = piece;
11437     movePtr++;
11438 }
11439
11440 int
11441 PackGame (Board board)
11442 {
11443     Move *newSpace = NULL;
11444     moveDatabase[movePtr].piece = 0; // terminate previous game
11445     if(movePtr > dataSize) {
11446         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11447         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11448         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11449         if(newSpace) {
11450             int i;
11451             Move *p = moveDatabase, *q = newSpace;
11452             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11453             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11454             moveDatabase = newSpace;
11455         } else { // calloc failed, we must be out of memory. Too bad...
11456             dataSize = 0; // prevent calloc events for all subsequent games
11457             return 0;     // and signal this one isn't cached
11458         }
11459     }
11460     movePtr++;
11461     MakePieceList(board, counts);
11462     return movePtr;
11463 }
11464
11465 int
11466 QuickCompare (Board board, int *minCounts, int *maxCounts)
11467 {   // compare according to search mode
11468     int r, f;
11469     switch(appData.searchMode)
11470     {
11471       case 1: // exact position match
11472         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11473         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11474             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11475         }
11476         break;
11477       case 2: // can have extra material on empty squares
11478         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11479             if(board[r][f] == EmptySquare) continue;
11480             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11481         }
11482         break;
11483       case 3: // material with exact Pawn structure
11484         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11485             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11486             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11487         } // fall through to material comparison
11488       case 4: // exact material
11489         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11490         break;
11491       case 6: // material range with given imbalance
11492         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11493         // fall through to range comparison
11494       case 5: // material range
11495         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11496     }
11497     return TRUE;
11498 }
11499
11500 int
11501 QuickScan (Board board, Move *move)
11502 {   // reconstruct game,and compare all positions in it
11503     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11504     do {
11505         int piece = move->piece;
11506         int to = move->to, from = pieceList[piece];
11507         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11508           if(!piece) return -1;
11509           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11510             piece = (++move)->piece;
11511             from = pieceList[piece];
11512             counts[pieceType[piece]]--;
11513             pieceType[piece] = (ChessSquare) move->to;
11514             counts[move->to]++;
11515           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11516             counts[pieceType[quickBoard[to]]]--;
11517             quickBoard[to] = 0; total--;
11518             move++;
11519             continue;
11520           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11521             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11522             from  = pieceList[piece]; // so this must be King
11523             quickBoard[from] = 0;
11524             quickBoard[to] = piece;
11525             pieceList[piece] = to;
11526             move++;
11527             continue;
11528           }
11529         }
11530         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11531         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11532         quickBoard[from] = 0;
11533         quickBoard[to] = piece;
11534         pieceList[piece] = to;
11535         cnt++; turn ^= 3;
11536         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11537            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11538            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11539                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11540           ) {
11541             static int lastCounts[EmptySquare+1];
11542             int i;
11543             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11544             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11545         } else stretch = 0;
11546         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11547         move++;
11548     } while(1);
11549 }
11550
11551 void
11552 InitSearch ()
11553 {
11554     int r, f;
11555     flipSearch = FALSE;
11556     CopyBoard(soughtBoard, boards[currentMove]);
11557     soughtTotal = MakePieceList(soughtBoard, maxSought);
11558     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11559     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11560     CopyBoard(reverseBoard, boards[currentMove]);
11561     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11562         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11563         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11564         reverseBoard[r][f] = piece;
11565     }
11566     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11567     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11568     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11569                  || (boards[currentMove][CASTLING][2] == NoRights || 
11570                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11571                  && (boards[currentMove][CASTLING][5] == NoRights || 
11572                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11573       ) {
11574         flipSearch = TRUE;
11575         CopyBoard(flipBoard, soughtBoard);
11576         CopyBoard(rotateBoard, reverseBoard);
11577         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11578             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11579             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11580         }
11581     }
11582     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11583     if(appData.searchMode >= 5) {
11584         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11585         MakePieceList(soughtBoard, minSought);
11586         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11587     }
11588     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11589         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11590 }
11591
11592 GameInfo dummyInfo;
11593
11594 int
11595 GameContainsPosition (FILE *f, ListGame *lg)
11596 {
11597     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11598     int fromX, fromY, toX, toY;
11599     char promoChar;
11600     static int initDone=FALSE;
11601
11602     // weed out games based on numerical tag comparison
11603     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11604     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11605     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11606     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11607     if(!initDone) {
11608         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11609         initDone = TRUE;
11610     }
11611     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11612     else CopyBoard(boards[scratch], initialPosition); // default start position
11613     if(lg->moves) {
11614         turn = btm + 1;
11615         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11616         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11617     }
11618     if(btm) plyNr++;
11619     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11620     fseek(f, lg->offset, 0);
11621     yynewfile(f);
11622     while(1) {
11623         yyboardindex = scratch;
11624         quickFlag = plyNr+1;
11625         next = Myylex();
11626         quickFlag = 0;
11627         switch(next) {
11628             case PGNTag:
11629                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11630             default:
11631                 continue;
11632
11633             case XBoardGame:
11634             case GNUChessGame:
11635                 if(plyNr) return -1; // after we have seen moves, this is for new game
11636               continue;
11637
11638             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11639             case ImpossibleMove:
11640             case WhiteWins: // game ends here with these four
11641             case BlackWins:
11642             case GameIsDrawn:
11643             case GameUnfinished:
11644                 return -1;
11645
11646             case IllegalMove:
11647                 if(appData.testLegality) return -1;
11648             case WhiteCapturesEnPassant:
11649             case BlackCapturesEnPassant:
11650             case WhitePromotion:
11651             case BlackPromotion:
11652             case WhiteNonPromotion:
11653             case BlackNonPromotion:
11654             case NormalMove:
11655             case WhiteKingSideCastle:
11656             case WhiteQueenSideCastle:
11657             case BlackKingSideCastle:
11658             case BlackQueenSideCastle:
11659             case WhiteKingSideCastleWild:
11660             case WhiteQueenSideCastleWild:
11661             case BlackKingSideCastleWild:
11662             case BlackQueenSideCastleWild:
11663             case WhiteHSideCastleFR:
11664             case WhiteASideCastleFR:
11665             case BlackHSideCastleFR:
11666             case BlackASideCastleFR:
11667                 fromX = currentMoveString[0] - AAA;
11668                 fromY = currentMoveString[1] - ONE;
11669                 toX = currentMoveString[2] - AAA;
11670                 toY = currentMoveString[3] - ONE;
11671                 promoChar = currentMoveString[4];
11672                 break;
11673             case WhiteDrop:
11674             case BlackDrop:
11675                 fromX = next == WhiteDrop ?
11676                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11677                   (int) CharToPiece(ToLower(currentMoveString[0]));
11678                 fromY = DROP_RANK;
11679                 toX = currentMoveString[2] - AAA;
11680                 toY = currentMoveString[3] - ONE;
11681                 promoChar = 0;
11682                 break;
11683         }
11684         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11685         plyNr++;
11686         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11687         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11688         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11689         if(appData.findMirror) {
11690             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11691             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11692         }
11693     }
11694 }
11695
11696 /* Load the nth game from open file f */
11697 int
11698 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11699 {
11700     ChessMove cm;
11701     char buf[MSG_SIZ];
11702     int gn = gameNumber;
11703     ListGame *lg = NULL;
11704     int numPGNTags = 0;
11705     int err, pos = -1;
11706     GameMode oldGameMode;
11707     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11708
11709     if (appData.debugMode)
11710         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11711
11712     if (gameMode == Training )
11713         SetTrainingModeOff();
11714
11715     oldGameMode = gameMode;
11716     if (gameMode != BeginningOfGame) {
11717       Reset(FALSE, TRUE);
11718     }
11719
11720     gameFileFP = f;
11721     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11722         fclose(lastLoadGameFP);
11723     }
11724
11725     if (useList) {
11726         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11727
11728         if (lg) {
11729             fseek(f, lg->offset, 0);
11730             GameListHighlight(gameNumber);
11731             pos = lg->position;
11732             gn = 1;
11733         }
11734         else {
11735             DisplayError(_("Game number out of range"), 0);
11736             return FALSE;
11737         }
11738     } else {
11739         GameListDestroy();
11740         if (fseek(f, 0, 0) == -1) {
11741             if (f == lastLoadGameFP ?
11742                 gameNumber == lastLoadGameNumber + 1 :
11743                 gameNumber == 1) {
11744                 gn = 1;
11745             } else {
11746                 DisplayError(_("Can't seek on game file"), 0);
11747                 return FALSE;
11748             }
11749         }
11750     }
11751     lastLoadGameFP = f;
11752     lastLoadGameNumber = gameNumber;
11753     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11754     lastLoadGameUseList = useList;
11755
11756     yynewfile(f);
11757
11758     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11759       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11760                 lg->gameInfo.black);
11761             DisplayTitle(buf);
11762     } else if (*title != NULLCHAR) {
11763         if (gameNumber > 1) {
11764           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11765             DisplayTitle(buf);
11766         } else {
11767             DisplayTitle(title);
11768         }
11769     }
11770
11771     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11772         gameMode = PlayFromGameFile;
11773         ModeHighlight();
11774     }
11775
11776     currentMove = forwardMostMove = backwardMostMove = 0;
11777     CopyBoard(boards[0], initialPosition);
11778     StopClocks();
11779
11780     /*
11781      * Skip the first gn-1 games in the file.
11782      * Also skip over anything that precedes an identifiable
11783      * start of game marker, to avoid being confused by
11784      * garbage at the start of the file.  Currently
11785      * recognized start of game markers are the move number "1",
11786      * the pattern "gnuchess .* game", the pattern
11787      * "^[#;%] [^ ]* game file", and a PGN tag block.
11788      * A game that starts with one of the latter two patterns
11789      * will also have a move number 1, possibly
11790      * following a position diagram.
11791      * 5-4-02: Let's try being more lenient and allowing a game to
11792      * start with an unnumbered move.  Does that break anything?
11793      */
11794     cm = lastLoadGameStart = EndOfFile;
11795     while (gn > 0) {
11796         yyboardindex = forwardMostMove;
11797         cm = (ChessMove) Myylex();
11798         switch (cm) {
11799           case EndOfFile:
11800             if (cmailMsgLoaded) {
11801                 nCmailGames = CMAIL_MAX_GAMES - gn;
11802             } else {
11803                 Reset(TRUE, TRUE);
11804                 DisplayError(_("Game not found in file"), 0);
11805             }
11806             return FALSE;
11807
11808           case GNUChessGame:
11809           case XBoardGame:
11810             gn--;
11811             lastLoadGameStart = cm;
11812             break;
11813
11814           case MoveNumberOne:
11815             switch (lastLoadGameStart) {
11816               case GNUChessGame:
11817               case XBoardGame:
11818               case PGNTag:
11819                 break;
11820               case MoveNumberOne:
11821               case EndOfFile:
11822                 gn--;           /* count this game */
11823                 lastLoadGameStart = cm;
11824                 break;
11825               default:
11826                 /* impossible */
11827                 break;
11828             }
11829             break;
11830
11831           case PGNTag:
11832             switch (lastLoadGameStart) {
11833               case GNUChessGame:
11834               case PGNTag:
11835               case MoveNumberOne:
11836               case EndOfFile:
11837                 gn--;           /* count this game */
11838                 lastLoadGameStart = cm;
11839                 break;
11840               case XBoardGame:
11841                 lastLoadGameStart = cm; /* game counted already */
11842                 break;
11843               default:
11844                 /* impossible */
11845                 break;
11846             }
11847             if (gn > 0) {
11848                 do {
11849                     yyboardindex = forwardMostMove;
11850                     cm = (ChessMove) Myylex();
11851                 } while (cm == PGNTag || cm == Comment);
11852             }
11853             break;
11854
11855           case WhiteWins:
11856           case BlackWins:
11857           case GameIsDrawn:
11858             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11859                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11860                     != CMAIL_OLD_RESULT) {
11861                     nCmailResults ++ ;
11862                     cmailResult[  CMAIL_MAX_GAMES
11863                                 - gn - 1] = CMAIL_OLD_RESULT;
11864                 }
11865             }
11866             break;
11867
11868           case NormalMove:
11869             /* Only a NormalMove can be at the start of a game
11870              * without a position diagram. */
11871             if (lastLoadGameStart == EndOfFile ) {
11872               gn--;
11873               lastLoadGameStart = MoveNumberOne;
11874             }
11875             break;
11876
11877           default:
11878             break;
11879         }
11880     }
11881
11882     if (appData.debugMode)
11883       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11884
11885     if (cm == XBoardGame) {
11886         /* Skip any header junk before position diagram and/or move 1 */
11887         for (;;) {
11888             yyboardindex = forwardMostMove;
11889             cm = (ChessMove) Myylex();
11890
11891             if (cm == EndOfFile ||
11892                 cm == GNUChessGame || cm == XBoardGame) {
11893                 /* Empty game; pretend end-of-file and handle later */
11894                 cm = EndOfFile;
11895                 break;
11896             }
11897
11898             if (cm == MoveNumberOne || cm == PositionDiagram ||
11899                 cm == PGNTag || cm == Comment)
11900               break;
11901         }
11902     } else if (cm == GNUChessGame) {
11903         if (gameInfo.event != NULL) {
11904             free(gameInfo.event);
11905         }
11906         gameInfo.event = StrSave(yy_text);
11907     }
11908
11909     startedFromSetupPosition = FALSE;
11910     while (cm == PGNTag) {
11911         if (appData.debugMode)
11912           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11913         err = ParsePGNTag(yy_text, &gameInfo);
11914         if (!err) numPGNTags++;
11915
11916         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11917         if(gameInfo.variant != oldVariant) {
11918             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11919             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11920             InitPosition(TRUE);
11921             oldVariant = gameInfo.variant;
11922             if (appData.debugMode)
11923               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11924         }
11925
11926
11927         if (gameInfo.fen != NULL) {
11928           Board initial_position;
11929           startedFromSetupPosition = TRUE;
11930           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11931             Reset(TRUE, TRUE);
11932             DisplayError(_("Bad FEN position in file"), 0);
11933             return FALSE;
11934           }
11935           CopyBoard(boards[0], initial_position);
11936           if (blackPlaysFirst) {
11937             currentMove = forwardMostMove = backwardMostMove = 1;
11938             CopyBoard(boards[1], initial_position);
11939             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11940             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11941             timeRemaining[0][1] = whiteTimeRemaining;
11942             timeRemaining[1][1] = blackTimeRemaining;
11943             if (commentList[0] != NULL) {
11944               commentList[1] = commentList[0];
11945               commentList[0] = NULL;
11946             }
11947           } else {
11948             currentMove = forwardMostMove = backwardMostMove = 0;
11949           }
11950           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11951           {   int i;
11952               initialRulePlies = FENrulePlies;
11953               for( i=0; i< nrCastlingRights; i++ )
11954                   initialRights[i] = initial_position[CASTLING][i];
11955           }
11956           yyboardindex = forwardMostMove;
11957           free(gameInfo.fen);
11958           gameInfo.fen = NULL;
11959         }
11960
11961         yyboardindex = forwardMostMove;
11962         cm = (ChessMove) Myylex();
11963
11964         /* Handle comments interspersed among the tags */
11965         while (cm == Comment) {
11966             char *p;
11967             if (appData.debugMode)
11968               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11969             p = yy_text;
11970             AppendComment(currentMove, p, FALSE);
11971             yyboardindex = forwardMostMove;
11972             cm = (ChessMove) Myylex();
11973         }
11974     }
11975
11976     /* don't rely on existence of Event tag since if game was
11977      * pasted from clipboard the Event tag may not exist
11978      */
11979     if (numPGNTags > 0){
11980         char *tags;
11981         if (gameInfo.variant == VariantNormal) {
11982           VariantClass v = StringToVariant(gameInfo.event);
11983           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11984           if(v < VariantShogi) gameInfo.variant = v;
11985         }
11986         if (!matchMode) {
11987           if( appData.autoDisplayTags ) {
11988             tags = PGNTags(&gameInfo);
11989             TagsPopUp(tags, CmailMsg());
11990             free(tags);
11991           }
11992         }
11993     } else {
11994         /* Make something up, but don't display it now */
11995         SetGameInfo();
11996         TagsPopDown();
11997     }
11998
11999     if (cm == PositionDiagram) {
12000         int i, j;
12001         char *p;
12002         Board initial_position;
12003
12004         if (appData.debugMode)
12005           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12006
12007         if (!startedFromSetupPosition) {
12008             p = yy_text;
12009             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12010               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12011                 switch (*p) {
12012                   case '{':
12013                   case '[':
12014                   case '-':
12015                   case ' ':
12016                   case '\t':
12017                   case '\n':
12018                   case '\r':
12019                     break;
12020                   default:
12021                     initial_position[i][j++] = CharToPiece(*p);
12022                     break;
12023                 }
12024             while (*p == ' ' || *p == '\t' ||
12025                    *p == '\n' || *p == '\r') p++;
12026
12027             if (strncmp(p, "black", strlen("black"))==0)
12028               blackPlaysFirst = TRUE;
12029             else
12030               blackPlaysFirst = FALSE;
12031             startedFromSetupPosition = TRUE;
12032
12033             CopyBoard(boards[0], initial_position);
12034             if (blackPlaysFirst) {
12035                 currentMove = forwardMostMove = backwardMostMove = 1;
12036                 CopyBoard(boards[1], initial_position);
12037                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12038                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12039                 timeRemaining[0][1] = whiteTimeRemaining;
12040                 timeRemaining[1][1] = blackTimeRemaining;
12041                 if (commentList[0] != NULL) {
12042                     commentList[1] = commentList[0];
12043                     commentList[0] = NULL;
12044                 }
12045             } else {
12046                 currentMove = forwardMostMove = backwardMostMove = 0;
12047             }
12048         }
12049         yyboardindex = forwardMostMove;
12050         cm = (ChessMove) Myylex();
12051     }
12052
12053     if (first.pr == NoProc) {
12054         StartChessProgram(&first);
12055     }
12056     InitChessProgram(&first, FALSE);
12057     SendToProgram("force\n", &first);
12058     if (startedFromSetupPosition) {
12059         SendBoard(&first, forwardMostMove);
12060     if (appData.debugMode) {
12061         fprintf(debugFP, "Load Game\n");
12062     }
12063         DisplayBothClocks();
12064     }
12065
12066     /* [HGM] server: flag to write setup moves in broadcast file as one */
12067     loadFlag = appData.suppressLoadMoves;
12068
12069     while (cm == Comment) {
12070         char *p;
12071         if (appData.debugMode)
12072           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12073         p = yy_text;
12074         AppendComment(currentMove, p, FALSE);
12075         yyboardindex = forwardMostMove;
12076         cm = (ChessMove) Myylex();
12077     }
12078
12079     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12080         cm == WhiteWins || cm == BlackWins ||
12081         cm == GameIsDrawn || cm == GameUnfinished) {
12082         DisplayMessage("", _("No moves in game"));
12083         if (cmailMsgLoaded) {
12084             if (appData.debugMode)
12085               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12086             ClearHighlights();
12087             flipView = FALSE;
12088         }
12089         DrawPosition(FALSE, boards[currentMove]);
12090         DisplayBothClocks();
12091         gameMode = EditGame;
12092         ModeHighlight();
12093         gameFileFP = NULL;
12094         cmailOldMove = 0;
12095         return TRUE;
12096     }
12097
12098     // [HGM] PV info: routine tests if comment empty
12099     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12100         DisplayComment(currentMove - 1, commentList[currentMove]);
12101     }
12102     if (!matchMode && appData.timeDelay != 0)
12103       DrawPosition(FALSE, boards[currentMove]);
12104
12105     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12106       programStats.ok_to_send = 1;
12107     }
12108
12109     /* if the first token after the PGN tags is a move
12110      * and not move number 1, retrieve it from the parser
12111      */
12112     if (cm != MoveNumberOne)
12113         LoadGameOneMove(cm);
12114
12115     /* load the remaining moves from the file */
12116     while (LoadGameOneMove(EndOfFile)) {
12117       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12118       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12119     }
12120
12121     /* rewind to the start of the game */
12122     currentMove = backwardMostMove;
12123
12124     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12125
12126     if (oldGameMode == AnalyzeFile ||
12127         oldGameMode == AnalyzeMode) {
12128       AnalyzeFileEvent();
12129     }
12130
12131     if (!matchMode && pos >= 0) {
12132         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12133     } else
12134     if (matchMode || appData.timeDelay == 0) {
12135       ToEndEvent();
12136     } else if (appData.timeDelay > 0) {
12137       AutoPlayGameLoop();
12138     }
12139
12140     if (appData.debugMode)
12141         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12142
12143     loadFlag = 0; /* [HGM] true game starts */
12144     return TRUE;
12145 }
12146
12147 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12148 int
12149 ReloadPosition (int offset)
12150 {
12151     int positionNumber = lastLoadPositionNumber + offset;
12152     if (lastLoadPositionFP == NULL) {
12153         DisplayError(_("No position has been loaded yet"), 0);
12154         return FALSE;
12155     }
12156     if (positionNumber <= 0) {
12157         DisplayError(_("Can't back up any further"), 0);
12158         return FALSE;
12159     }
12160     return LoadPosition(lastLoadPositionFP, positionNumber,
12161                         lastLoadPositionTitle);
12162 }
12163
12164 /* Load the nth position from the given file */
12165 int
12166 LoadPositionFromFile (char *filename, int n, char *title)
12167 {
12168     FILE *f;
12169     char buf[MSG_SIZ];
12170
12171     if (strcmp(filename, "-") == 0) {
12172         return LoadPosition(stdin, n, "stdin");
12173     } else {
12174         f = fopen(filename, "rb");
12175         if (f == NULL) {
12176             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12177             DisplayError(buf, errno);
12178             return FALSE;
12179         } else {
12180             return LoadPosition(f, n, title);
12181         }
12182     }
12183 }
12184
12185 /* Load the nth position from the given open file, and close it */
12186 int
12187 LoadPosition (FILE *f, int positionNumber, char *title)
12188 {
12189     char *p, line[MSG_SIZ];
12190     Board initial_position;
12191     int i, j, fenMode, pn;
12192
12193     if (gameMode == Training )
12194         SetTrainingModeOff();
12195
12196     if (gameMode != BeginningOfGame) {
12197         Reset(FALSE, TRUE);
12198     }
12199     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12200         fclose(lastLoadPositionFP);
12201     }
12202     if (positionNumber == 0) positionNumber = 1;
12203     lastLoadPositionFP = f;
12204     lastLoadPositionNumber = positionNumber;
12205     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12206     if (first.pr == NoProc && !appData.noChessProgram) {
12207       StartChessProgram(&first);
12208       InitChessProgram(&first, FALSE);
12209     }
12210     pn = positionNumber;
12211     if (positionNumber < 0) {
12212         /* Negative position number means to seek to that byte offset */
12213         if (fseek(f, -positionNumber, 0) == -1) {
12214             DisplayError(_("Can't seek on position file"), 0);
12215             return FALSE;
12216         };
12217         pn = 1;
12218     } else {
12219         if (fseek(f, 0, 0) == -1) {
12220             if (f == lastLoadPositionFP ?
12221                 positionNumber == lastLoadPositionNumber + 1 :
12222                 positionNumber == 1) {
12223                 pn = 1;
12224             } else {
12225                 DisplayError(_("Can't seek on position file"), 0);
12226                 return FALSE;
12227             }
12228         }
12229     }
12230     /* See if this file is FEN or old-style xboard */
12231     if (fgets(line, MSG_SIZ, f) == NULL) {
12232         DisplayError(_("Position not found in file"), 0);
12233         return FALSE;
12234     }
12235     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12236     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12237
12238     if (pn >= 2) {
12239         if (fenMode || line[0] == '#') pn--;
12240         while (pn > 0) {
12241             /* skip positions before number pn */
12242             if (fgets(line, MSG_SIZ, f) == NULL) {
12243                 Reset(TRUE, TRUE);
12244                 DisplayError(_("Position not found in file"), 0);
12245                 return FALSE;
12246             }
12247             if (fenMode || line[0] == '#') pn--;
12248         }
12249     }
12250
12251     if (fenMode) {
12252         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12253             DisplayError(_("Bad FEN position in file"), 0);
12254             return FALSE;
12255         }
12256     } else {
12257         (void) fgets(line, MSG_SIZ, f);
12258         (void) fgets(line, MSG_SIZ, f);
12259
12260         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12261             (void) fgets(line, MSG_SIZ, f);
12262             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12263                 if (*p == ' ')
12264                   continue;
12265                 initial_position[i][j++] = CharToPiece(*p);
12266             }
12267         }
12268
12269         blackPlaysFirst = FALSE;
12270         if (!feof(f)) {
12271             (void) fgets(line, MSG_SIZ, f);
12272             if (strncmp(line, "black", strlen("black"))==0)
12273               blackPlaysFirst = TRUE;
12274         }
12275     }
12276     startedFromSetupPosition = TRUE;
12277
12278     CopyBoard(boards[0], initial_position);
12279     if (blackPlaysFirst) {
12280         currentMove = forwardMostMove = backwardMostMove = 1;
12281         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12282         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12283         CopyBoard(boards[1], initial_position);
12284         DisplayMessage("", _("Black to play"));
12285     } else {
12286         currentMove = forwardMostMove = backwardMostMove = 0;
12287         DisplayMessage("", _("White to play"));
12288     }
12289     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12290     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12291         SendToProgram("force\n", &first);
12292         SendBoard(&first, forwardMostMove);
12293     }
12294     if (appData.debugMode) {
12295 int i, j;
12296   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12297   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12298         fprintf(debugFP, "Load Position\n");
12299     }
12300
12301     if (positionNumber > 1) {
12302       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12303         DisplayTitle(line);
12304     } else {
12305         DisplayTitle(title);
12306     }
12307     gameMode = EditGame;
12308     ModeHighlight();
12309     ResetClocks();
12310     timeRemaining[0][1] = whiteTimeRemaining;
12311     timeRemaining[1][1] = blackTimeRemaining;
12312     DrawPosition(FALSE, boards[currentMove]);
12313
12314     return TRUE;
12315 }
12316
12317
12318 void
12319 CopyPlayerNameIntoFileName (char **dest, char *src)
12320 {
12321     while (*src != NULLCHAR && *src != ',') {
12322         if (*src == ' ') {
12323             *(*dest)++ = '_';
12324             src++;
12325         } else {
12326             *(*dest)++ = *src++;
12327         }
12328     }
12329 }
12330
12331 char *
12332 DefaultFileName (char *ext)
12333 {
12334     static char def[MSG_SIZ];
12335     char *p;
12336
12337     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12338         p = def;
12339         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12340         *p++ = '-';
12341         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12342         *p++ = '.';
12343         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12344     } else {
12345         def[0] = NULLCHAR;
12346     }
12347     return def;
12348 }
12349
12350 /* Save the current game to the given file */
12351 int
12352 SaveGameToFile (char *filename, int append)
12353 {
12354     FILE *f;
12355     char buf[MSG_SIZ];
12356     int result, i, t,tot=0;
12357
12358     if (strcmp(filename, "-") == 0) {
12359         return SaveGame(stdout, 0, NULL);
12360     } else {
12361         for(i=0; i<10; i++) { // upto 10 tries
12362              f = fopen(filename, append ? "a" : "w");
12363              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12364              if(f || errno != 13) break;
12365              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12366              tot += t;
12367         }
12368         if (f == NULL) {
12369             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12370             DisplayError(buf, errno);
12371             return FALSE;
12372         } else {
12373             safeStrCpy(buf, lastMsg, MSG_SIZ);
12374             DisplayMessage(_("Waiting for access to save file"), "");
12375             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12376             DisplayMessage(_("Saving game"), "");
12377             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12378             result = SaveGame(f, 0, NULL);
12379             DisplayMessage(buf, "");
12380             return result;
12381         }
12382     }
12383 }
12384
12385 char *
12386 SavePart (char *str)
12387 {
12388     static char buf[MSG_SIZ];
12389     char *p;
12390
12391     p = strchr(str, ' ');
12392     if (p == NULL) return str;
12393     strncpy(buf, str, p - str);
12394     buf[p - str] = NULLCHAR;
12395     return buf;
12396 }
12397
12398 #define PGN_MAX_LINE 75
12399
12400 #define PGN_SIDE_WHITE  0
12401 #define PGN_SIDE_BLACK  1
12402
12403 static int
12404 FindFirstMoveOutOfBook (int side)
12405 {
12406     int result = -1;
12407
12408     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12409         int index = backwardMostMove;
12410         int has_book_hit = 0;
12411
12412         if( (index % 2) != side ) {
12413             index++;
12414         }
12415
12416         while( index < forwardMostMove ) {
12417             /* Check to see if engine is in book */
12418             int depth = pvInfoList[index].depth;
12419             int score = pvInfoList[index].score;
12420             int in_book = 0;
12421
12422             if( depth <= 2 ) {
12423                 in_book = 1;
12424             }
12425             else if( score == 0 && depth == 63 ) {
12426                 in_book = 1; /* Zappa */
12427             }
12428             else if( score == 2 && depth == 99 ) {
12429                 in_book = 1; /* Abrok */
12430             }
12431
12432             has_book_hit += in_book;
12433
12434             if( ! in_book ) {
12435                 result = index;
12436
12437                 break;
12438             }
12439
12440             index += 2;
12441         }
12442     }
12443
12444     return result;
12445 }
12446
12447 void
12448 GetOutOfBookInfo (char * buf)
12449 {
12450     int oob[2];
12451     int i;
12452     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12453
12454     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12455     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12456
12457     *buf = '\0';
12458
12459     if( oob[0] >= 0 || oob[1] >= 0 ) {
12460         for( i=0; i<2; i++ ) {
12461             int idx = oob[i];
12462
12463             if( idx >= 0 ) {
12464                 if( i > 0 && oob[0] >= 0 ) {
12465                     strcat( buf, "   " );
12466                 }
12467
12468                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12469                 sprintf( buf+strlen(buf), "%s%.2f",
12470                     pvInfoList[idx].score >= 0 ? "+" : "",
12471                     pvInfoList[idx].score / 100.0 );
12472             }
12473         }
12474     }
12475 }
12476
12477 /* Save game in PGN style and close the file */
12478 int
12479 SaveGamePGN (FILE *f)
12480 {
12481     int i, offset, linelen, newblock;
12482     time_t tm;
12483 //    char *movetext;
12484     char numtext[32];
12485     int movelen, numlen, blank;
12486     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12487
12488     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12489
12490     tm = time((time_t *) NULL);
12491
12492     PrintPGNTags(f, &gameInfo);
12493
12494     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12495
12496     if (backwardMostMove > 0 || startedFromSetupPosition) {
12497         char *fen = PositionToFEN(backwardMostMove, NULL);
12498         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12499         fprintf(f, "\n{--------------\n");
12500         PrintPosition(f, backwardMostMove);
12501         fprintf(f, "--------------}\n");
12502         free(fen);
12503     }
12504     else {
12505         /* [AS] Out of book annotation */
12506         if( appData.saveOutOfBookInfo ) {
12507             char buf[64];
12508
12509             GetOutOfBookInfo( buf );
12510
12511             if( buf[0] != '\0' ) {
12512                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12513             }
12514         }
12515
12516         fprintf(f, "\n");
12517     }
12518
12519     i = backwardMostMove;
12520     linelen = 0;
12521     newblock = TRUE;
12522
12523     while (i < forwardMostMove) {
12524         /* Print comments preceding this move */
12525         if (commentList[i] != NULL) {
12526             if (linelen > 0) fprintf(f, "\n");
12527             fprintf(f, "%s", commentList[i]);
12528             linelen = 0;
12529             newblock = TRUE;
12530         }
12531
12532         /* Format move number */
12533         if ((i % 2) == 0)
12534           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12535         else
12536           if (newblock)
12537             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12538           else
12539             numtext[0] = NULLCHAR;
12540
12541         numlen = strlen(numtext);
12542         newblock = FALSE;
12543
12544         /* Print move number */
12545         blank = linelen > 0 && numlen > 0;
12546         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12547             fprintf(f, "\n");
12548             linelen = 0;
12549             blank = 0;
12550         }
12551         if (blank) {
12552             fprintf(f, " ");
12553             linelen++;
12554         }
12555         fprintf(f, "%s", numtext);
12556         linelen += numlen;
12557
12558         /* Get move */
12559         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12560         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12561
12562         /* Print move */
12563         blank = linelen > 0 && movelen > 0;
12564         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12565             fprintf(f, "\n");
12566             linelen = 0;
12567             blank = 0;
12568         }
12569         if (blank) {
12570             fprintf(f, " ");
12571             linelen++;
12572         }
12573         fprintf(f, "%s", move_buffer);
12574         linelen += movelen;
12575
12576         /* [AS] Add PV info if present */
12577         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12578             /* [HGM] add time */
12579             char buf[MSG_SIZ]; int seconds;
12580
12581             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12582
12583             if( seconds <= 0)
12584               buf[0] = 0;
12585             else
12586               if( seconds < 30 )
12587                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12588               else
12589                 {
12590                   seconds = (seconds + 4)/10; // round to full seconds
12591                   if( seconds < 60 )
12592                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12593                   else
12594                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12595                 }
12596
12597             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12598                       pvInfoList[i].score >= 0 ? "+" : "",
12599                       pvInfoList[i].score / 100.0,
12600                       pvInfoList[i].depth,
12601                       buf );
12602
12603             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12604
12605             /* Print score/depth */
12606             blank = linelen > 0 && movelen > 0;
12607             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12608                 fprintf(f, "\n");
12609                 linelen = 0;
12610                 blank = 0;
12611             }
12612             if (blank) {
12613                 fprintf(f, " ");
12614                 linelen++;
12615             }
12616             fprintf(f, "%s", move_buffer);
12617             linelen += movelen;
12618         }
12619
12620         i++;
12621     }
12622
12623     /* Start a new line */
12624     if (linelen > 0) fprintf(f, "\n");
12625
12626     /* Print comments after last move */
12627     if (commentList[i] != NULL) {
12628         fprintf(f, "%s\n", commentList[i]);
12629     }
12630
12631     /* Print result */
12632     if (gameInfo.resultDetails != NULL &&
12633         gameInfo.resultDetails[0] != NULLCHAR) {
12634         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12635                 PGNResult(gameInfo.result));
12636     } else {
12637         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12638     }
12639
12640     fclose(f);
12641     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12642     return TRUE;
12643 }
12644
12645 /* Save game in old style and close the file */
12646 int
12647 SaveGameOldStyle (FILE *f)
12648 {
12649     int i, offset;
12650     time_t tm;
12651
12652     tm = time((time_t *) NULL);
12653
12654     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12655     PrintOpponents(f);
12656
12657     if (backwardMostMove > 0 || startedFromSetupPosition) {
12658         fprintf(f, "\n[--------------\n");
12659         PrintPosition(f, backwardMostMove);
12660         fprintf(f, "--------------]\n");
12661     } else {
12662         fprintf(f, "\n");
12663     }
12664
12665     i = backwardMostMove;
12666     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12667
12668     while (i < forwardMostMove) {
12669         if (commentList[i] != NULL) {
12670             fprintf(f, "[%s]\n", commentList[i]);
12671         }
12672
12673         if ((i % 2) == 1) {
12674             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12675             i++;
12676         } else {
12677             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12678             i++;
12679             if (commentList[i] != NULL) {
12680                 fprintf(f, "\n");
12681                 continue;
12682             }
12683             if (i >= forwardMostMove) {
12684                 fprintf(f, "\n");
12685                 break;
12686             }
12687             fprintf(f, "%s\n", parseList[i]);
12688             i++;
12689         }
12690     }
12691
12692     if (commentList[i] != NULL) {
12693         fprintf(f, "[%s]\n", commentList[i]);
12694     }
12695
12696     /* This isn't really the old style, but it's close enough */
12697     if (gameInfo.resultDetails != NULL &&
12698         gameInfo.resultDetails[0] != NULLCHAR) {
12699         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12700                 gameInfo.resultDetails);
12701     } else {
12702         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12703     }
12704
12705     fclose(f);
12706     return TRUE;
12707 }
12708
12709 /* Save the current game to open file f and close the file */
12710 int
12711 SaveGame (FILE *f, int dummy, char *dummy2)
12712 {
12713     if (gameMode == EditPosition) EditPositionDone(TRUE);
12714     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12715     if (appData.oldSaveStyle)
12716       return SaveGameOldStyle(f);
12717     else
12718       return SaveGamePGN(f);
12719 }
12720
12721 /* Save the current position to the given file */
12722 int
12723 SavePositionToFile (char *filename)
12724 {
12725     FILE *f;
12726     char buf[MSG_SIZ];
12727
12728     if (strcmp(filename, "-") == 0) {
12729         return SavePosition(stdout, 0, NULL);
12730     } else {
12731         f = fopen(filename, "a");
12732         if (f == NULL) {
12733             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12734             DisplayError(buf, errno);
12735             return FALSE;
12736         } else {
12737             safeStrCpy(buf, lastMsg, MSG_SIZ);
12738             DisplayMessage(_("Waiting for access to save file"), "");
12739             flock(fileno(f), LOCK_EX); // [HGM] lock
12740             DisplayMessage(_("Saving position"), "");
12741             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12742             SavePosition(f, 0, NULL);
12743             DisplayMessage(buf, "");
12744             return TRUE;
12745         }
12746     }
12747 }
12748
12749 /* Save the current position to the given open file and close the file */
12750 int
12751 SavePosition (FILE *f, int dummy, char *dummy2)
12752 {
12753     time_t tm;
12754     char *fen;
12755
12756     if (gameMode == EditPosition) EditPositionDone(TRUE);
12757     if (appData.oldSaveStyle) {
12758         tm = time((time_t *) NULL);
12759
12760         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12761         PrintOpponents(f);
12762         fprintf(f, "[--------------\n");
12763         PrintPosition(f, currentMove);
12764         fprintf(f, "--------------]\n");
12765     } else {
12766         fen = PositionToFEN(currentMove, NULL);
12767         fprintf(f, "%s\n", fen);
12768         free(fen);
12769     }
12770     fclose(f);
12771     return TRUE;
12772 }
12773
12774 void
12775 ReloadCmailMsgEvent (int unregister)
12776 {
12777 #if !WIN32
12778     static char *inFilename = NULL;
12779     static char *outFilename;
12780     int i;
12781     struct stat inbuf, outbuf;
12782     int status;
12783
12784     /* Any registered moves are unregistered if unregister is set, */
12785     /* i.e. invoked by the signal handler */
12786     if (unregister) {
12787         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12788             cmailMoveRegistered[i] = FALSE;
12789             if (cmailCommentList[i] != NULL) {
12790                 free(cmailCommentList[i]);
12791                 cmailCommentList[i] = NULL;
12792             }
12793         }
12794         nCmailMovesRegistered = 0;
12795     }
12796
12797     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12798         cmailResult[i] = CMAIL_NOT_RESULT;
12799     }
12800     nCmailResults = 0;
12801
12802     if (inFilename == NULL) {
12803         /* Because the filenames are static they only get malloced once  */
12804         /* and they never get freed                                      */
12805         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12806         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12807
12808         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12809         sprintf(outFilename, "%s.out", appData.cmailGameName);
12810     }
12811
12812     status = stat(outFilename, &outbuf);
12813     if (status < 0) {
12814         cmailMailedMove = FALSE;
12815     } else {
12816         status = stat(inFilename, &inbuf);
12817         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12818     }
12819
12820     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12821        counts the games, notes how each one terminated, etc.
12822
12823        It would be nice to remove this kludge and instead gather all
12824        the information while building the game list.  (And to keep it
12825        in the game list nodes instead of having a bunch of fixed-size
12826        parallel arrays.)  Note this will require getting each game's
12827        termination from the PGN tags, as the game list builder does
12828        not process the game moves.  --mann
12829        */
12830     cmailMsgLoaded = TRUE;
12831     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12832
12833     /* Load first game in the file or popup game menu */
12834     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12835
12836 #endif /* !WIN32 */
12837     return;
12838 }
12839
12840 int
12841 RegisterMove ()
12842 {
12843     FILE *f;
12844     char string[MSG_SIZ];
12845
12846     if (   cmailMailedMove
12847         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12848         return TRUE;            /* Allow free viewing  */
12849     }
12850
12851     /* Unregister move to ensure that we don't leave RegisterMove        */
12852     /* with the move registered when the conditions for registering no   */
12853     /* longer hold                                                       */
12854     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12855         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12856         nCmailMovesRegistered --;
12857
12858         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12859           {
12860               free(cmailCommentList[lastLoadGameNumber - 1]);
12861               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12862           }
12863     }
12864
12865     if (cmailOldMove == -1) {
12866         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12867         return FALSE;
12868     }
12869
12870     if (currentMove > cmailOldMove + 1) {
12871         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12872         return FALSE;
12873     }
12874
12875     if (currentMove < cmailOldMove) {
12876         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12877         return FALSE;
12878     }
12879
12880     if (forwardMostMove > currentMove) {
12881         /* Silently truncate extra moves */
12882         TruncateGame();
12883     }
12884
12885     if (   (currentMove == cmailOldMove + 1)
12886         || (   (currentMove == cmailOldMove)
12887             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12888                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12889         if (gameInfo.result != GameUnfinished) {
12890             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12891         }
12892
12893         if (commentList[currentMove] != NULL) {
12894             cmailCommentList[lastLoadGameNumber - 1]
12895               = StrSave(commentList[currentMove]);
12896         }
12897         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12898
12899         if (appData.debugMode)
12900           fprintf(debugFP, "Saving %s for game %d\n",
12901                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12902
12903         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12904
12905         f = fopen(string, "w");
12906         if (appData.oldSaveStyle) {
12907             SaveGameOldStyle(f); /* also closes the file */
12908
12909             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12910             f = fopen(string, "w");
12911             SavePosition(f, 0, NULL); /* also closes the file */
12912         } else {
12913             fprintf(f, "{--------------\n");
12914             PrintPosition(f, currentMove);
12915             fprintf(f, "--------------}\n\n");
12916
12917             SaveGame(f, 0, NULL); /* also closes the file*/
12918         }
12919
12920         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12921         nCmailMovesRegistered ++;
12922     } else if (nCmailGames == 1) {
12923         DisplayError(_("You have not made a move yet"), 0);
12924         return FALSE;
12925     }
12926
12927     return TRUE;
12928 }
12929
12930 void
12931 MailMoveEvent ()
12932 {
12933 #if !WIN32
12934     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12935     FILE *commandOutput;
12936     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12937     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12938     int nBuffers;
12939     int i;
12940     int archived;
12941     char *arcDir;
12942
12943     if (! cmailMsgLoaded) {
12944         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12945         return;
12946     }
12947
12948     if (nCmailGames == nCmailResults) {
12949         DisplayError(_("No unfinished games"), 0);
12950         return;
12951     }
12952
12953 #if CMAIL_PROHIBIT_REMAIL
12954     if (cmailMailedMove) {
12955       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);
12956         DisplayError(msg, 0);
12957         return;
12958     }
12959 #endif
12960
12961     if (! (cmailMailedMove || RegisterMove())) return;
12962
12963     if (   cmailMailedMove
12964         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12965       snprintf(string, MSG_SIZ, partCommandString,
12966                appData.debugMode ? " -v" : "", appData.cmailGameName);
12967         commandOutput = popen(string, "r");
12968
12969         if (commandOutput == NULL) {
12970             DisplayError(_("Failed to invoke cmail"), 0);
12971         } else {
12972             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12973                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12974             }
12975             if (nBuffers > 1) {
12976                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12977                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12978                 nBytes = MSG_SIZ - 1;
12979             } else {
12980                 (void) memcpy(msg, buffer, nBytes);
12981             }
12982             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12983
12984             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12985                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12986
12987                 archived = TRUE;
12988                 for (i = 0; i < nCmailGames; i ++) {
12989                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12990                         archived = FALSE;
12991                     }
12992                 }
12993                 if (   archived
12994                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12995                         != NULL)) {
12996                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12997                            arcDir,
12998                            appData.cmailGameName,
12999                            gameInfo.date);
13000                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13001                     cmailMsgLoaded = FALSE;
13002                 }
13003             }
13004
13005             DisplayInformation(msg);
13006             pclose(commandOutput);
13007         }
13008     } else {
13009         if ((*cmailMsg) != '\0') {
13010             DisplayInformation(cmailMsg);
13011         }
13012     }
13013
13014     return;
13015 #endif /* !WIN32 */
13016 }
13017
13018 char *
13019 CmailMsg ()
13020 {
13021 #if WIN32
13022     return NULL;
13023 #else
13024     int  prependComma = 0;
13025     char number[5];
13026     char string[MSG_SIZ];       /* Space for game-list */
13027     int  i;
13028
13029     if (!cmailMsgLoaded) return "";
13030
13031     if (cmailMailedMove) {
13032       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13033     } else {
13034         /* Create a list of games left */
13035       snprintf(string, MSG_SIZ, "[");
13036         for (i = 0; i < nCmailGames; i ++) {
13037             if (! (   cmailMoveRegistered[i]
13038                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13039                 if (prependComma) {
13040                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13041                 } else {
13042                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13043                     prependComma = 1;
13044                 }
13045
13046                 strcat(string, number);
13047             }
13048         }
13049         strcat(string, "]");
13050
13051         if (nCmailMovesRegistered + nCmailResults == 0) {
13052             switch (nCmailGames) {
13053               case 1:
13054                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13055                 break;
13056
13057               case 2:
13058                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13059                 break;
13060
13061               default:
13062                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13063                          nCmailGames);
13064                 break;
13065             }
13066         } else {
13067             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13068               case 1:
13069                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13070                          string);
13071                 break;
13072
13073               case 0:
13074                 if (nCmailResults == nCmailGames) {
13075                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13076                 } else {
13077                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13078                 }
13079                 break;
13080
13081               default:
13082                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13083                          string);
13084             }
13085         }
13086     }
13087     return cmailMsg;
13088 #endif /* WIN32 */
13089 }
13090
13091 void
13092 ResetGameEvent ()
13093 {
13094     if (gameMode == Training)
13095       SetTrainingModeOff();
13096
13097     Reset(TRUE, TRUE);
13098     cmailMsgLoaded = FALSE;
13099     if (appData.icsActive) {
13100       SendToICS(ics_prefix);
13101       SendToICS("refresh\n");
13102     }
13103 }
13104
13105 void
13106 ExitEvent (int status)
13107 {
13108     exiting++;
13109     if (exiting > 2) {
13110       /* Give up on clean exit */
13111       exit(status);
13112     }
13113     if (exiting > 1) {
13114       /* Keep trying for clean exit */
13115       return;
13116     }
13117
13118     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13119
13120     if (telnetISR != NULL) {
13121       RemoveInputSource(telnetISR);
13122     }
13123     if (icsPR != NoProc) {
13124       DestroyChildProcess(icsPR, TRUE);
13125     }
13126
13127     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13128     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13129
13130     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13131     /* make sure this other one finishes before killing it!                  */
13132     if(endingGame) { int count = 0;
13133         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13134         while(endingGame && count++ < 10) DoSleep(1);
13135         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13136     }
13137
13138     /* Kill off chess programs */
13139     if (first.pr != NoProc) {
13140         ExitAnalyzeMode();
13141
13142         DoSleep( appData.delayBeforeQuit );
13143         SendToProgram("quit\n", &first);
13144         DoSleep( appData.delayAfterQuit );
13145         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13146     }
13147     if (second.pr != NoProc) {
13148         DoSleep( appData.delayBeforeQuit );
13149         SendToProgram("quit\n", &second);
13150         DoSleep( appData.delayAfterQuit );
13151         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13152     }
13153     if (first.isr != NULL) {
13154         RemoveInputSource(first.isr);
13155     }
13156     if (second.isr != NULL) {
13157         RemoveInputSource(second.isr);
13158     }
13159
13160     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13161     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13162
13163     ShutDownFrontEnd();
13164     exit(status);
13165 }
13166
13167 void
13168 PauseEvent ()
13169 {
13170     if (appData.debugMode)
13171         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13172     if (pausing) {
13173         pausing = FALSE;
13174         ModeHighlight();
13175         if (gameMode == MachinePlaysWhite ||
13176             gameMode == MachinePlaysBlack) {
13177             StartClocks();
13178         } else {
13179             DisplayBothClocks();
13180         }
13181         if (gameMode == PlayFromGameFile) {
13182             if (appData.timeDelay >= 0)
13183                 AutoPlayGameLoop();
13184         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13185             Reset(FALSE, TRUE);
13186             SendToICS(ics_prefix);
13187             SendToICS("refresh\n");
13188         } else if (currentMove < forwardMostMove) {
13189             ForwardInner(forwardMostMove);
13190         }
13191         pauseExamInvalid = FALSE;
13192     } else {
13193         switch (gameMode) {
13194           default:
13195             return;
13196           case IcsExamining:
13197             pauseExamForwardMostMove = forwardMostMove;
13198             pauseExamInvalid = FALSE;
13199             /* fall through */
13200           case IcsObserving:
13201           case IcsPlayingWhite:
13202           case IcsPlayingBlack:
13203             pausing = TRUE;
13204             ModeHighlight();
13205             return;
13206           case PlayFromGameFile:
13207             (void) StopLoadGameTimer();
13208             pausing = TRUE;
13209             ModeHighlight();
13210             break;
13211           case BeginningOfGame:
13212             if (appData.icsActive) return;
13213             /* else fall through */
13214           case MachinePlaysWhite:
13215           case MachinePlaysBlack:
13216           case TwoMachinesPlay:
13217             if (forwardMostMove == 0)
13218               return;           /* don't pause if no one has moved */
13219             if ((gameMode == MachinePlaysWhite &&
13220                  !WhiteOnMove(forwardMostMove)) ||
13221                 (gameMode == MachinePlaysBlack &&
13222                  WhiteOnMove(forwardMostMove))) {
13223                 StopClocks();
13224             }
13225             pausing = TRUE;
13226             ModeHighlight();
13227             break;
13228         }
13229     }
13230 }
13231
13232 void
13233 EditCommentEvent ()
13234 {
13235     char title[MSG_SIZ];
13236
13237     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13238       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13239     } else {
13240       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13241                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13242                parseList[currentMove - 1]);
13243     }
13244
13245     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13246 }
13247
13248
13249 void
13250 EditTagsEvent ()
13251 {
13252     char *tags = PGNTags(&gameInfo);
13253     bookUp = FALSE;
13254     EditTagsPopUp(tags, NULL);
13255     free(tags);
13256 }
13257
13258 void
13259 AnalyzeModeEvent ()
13260 {
13261     if (appData.noChessProgram || gameMode == AnalyzeMode)
13262       return;
13263
13264     if (gameMode != AnalyzeFile) {
13265         if (!appData.icsEngineAnalyze) {
13266                EditGameEvent();
13267                if (gameMode != EditGame) return;
13268         }
13269         ResurrectChessProgram();
13270         SendToProgram("analyze\n", &first);
13271         first.analyzing = TRUE;
13272         /*first.maybeThinking = TRUE;*/
13273         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13274         EngineOutputPopUp();
13275     }
13276     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13277     pausing = FALSE;
13278     ModeHighlight();
13279     SetGameInfo();
13280
13281     StartAnalysisClock();
13282     GetTimeMark(&lastNodeCountTime);
13283     lastNodeCount = 0;
13284 }
13285
13286 void
13287 AnalyzeFileEvent ()
13288 {
13289     if (appData.noChessProgram || gameMode == AnalyzeFile)
13290       return;
13291
13292     if (gameMode != AnalyzeMode) {
13293         EditGameEvent();
13294         if (gameMode != EditGame) return;
13295         ResurrectChessProgram();
13296         SendToProgram("analyze\n", &first);
13297         first.analyzing = TRUE;
13298         /*first.maybeThinking = TRUE;*/
13299         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13300         EngineOutputPopUp();
13301     }
13302     gameMode = AnalyzeFile;
13303     pausing = FALSE;
13304     ModeHighlight();
13305     SetGameInfo();
13306
13307     StartAnalysisClock();
13308     GetTimeMark(&lastNodeCountTime);
13309     lastNodeCount = 0;
13310     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13311 }
13312
13313 void
13314 MachineWhiteEvent ()
13315 {
13316     char buf[MSG_SIZ];
13317     char *bookHit = NULL;
13318
13319     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13320       return;
13321
13322
13323     if (gameMode == PlayFromGameFile ||
13324         gameMode == TwoMachinesPlay  ||
13325         gameMode == Training         ||
13326         gameMode == AnalyzeMode      ||
13327         gameMode == EndOfGame)
13328         EditGameEvent();
13329
13330     if (gameMode == EditPosition)
13331         EditPositionDone(TRUE);
13332
13333     if (!WhiteOnMove(currentMove)) {
13334         DisplayError(_("It is not White's turn"), 0);
13335         return;
13336     }
13337
13338     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13339       ExitAnalyzeMode();
13340
13341     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13342         gameMode == AnalyzeFile)
13343         TruncateGame();
13344
13345     ResurrectChessProgram();    /* in case it isn't running */
13346     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13347         gameMode = MachinePlaysWhite;
13348         ResetClocks();
13349     } else
13350     gameMode = MachinePlaysWhite;
13351     pausing = FALSE;
13352     ModeHighlight();
13353     SetGameInfo();
13354     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13355     DisplayTitle(buf);
13356     if (first.sendName) {
13357       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13358       SendToProgram(buf, &first);
13359     }
13360     if (first.sendTime) {
13361       if (first.useColors) {
13362         SendToProgram("black\n", &first); /*gnu kludge*/
13363       }
13364       SendTimeRemaining(&first, TRUE);
13365     }
13366     if (first.useColors) {
13367       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13368     }
13369     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13370     SetMachineThinkingEnables();
13371     first.maybeThinking = TRUE;
13372     StartClocks();
13373     firstMove = FALSE;
13374
13375     if (appData.autoFlipView && !flipView) {
13376       flipView = !flipView;
13377       DrawPosition(FALSE, NULL);
13378       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13379     }
13380
13381     if(bookHit) { // [HGM] book: simulate book reply
13382         static char bookMove[MSG_SIZ]; // a bit generous?
13383
13384         programStats.nodes = programStats.depth = programStats.time =
13385         programStats.score = programStats.got_only_move = 0;
13386         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13387
13388         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13389         strcat(bookMove, bookHit);
13390         HandleMachineMove(bookMove, &first);
13391     }
13392 }
13393
13394 void
13395 MachineBlackEvent ()
13396 {
13397   char buf[MSG_SIZ];
13398   char *bookHit = NULL;
13399
13400     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13401         return;
13402
13403
13404     if (gameMode == PlayFromGameFile ||
13405         gameMode == TwoMachinesPlay  ||
13406         gameMode == Training         ||
13407         gameMode == AnalyzeMode      ||
13408         gameMode == EndOfGame)
13409         EditGameEvent();
13410
13411     if (gameMode == EditPosition)
13412         EditPositionDone(TRUE);
13413
13414     if (WhiteOnMove(currentMove)) {
13415         DisplayError(_("It is not Black's turn"), 0);
13416         return;
13417     }
13418
13419     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13420       ExitAnalyzeMode();
13421
13422     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13423         gameMode == AnalyzeFile)
13424         TruncateGame();
13425
13426     ResurrectChessProgram();    /* in case it isn't running */
13427     gameMode = MachinePlaysBlack;
13428     pausing = FALSE;
13429     ModeHighlight();
13430     SetGameInfo();
13431     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13432     DisplayTitle(buf);
13433     if (first.sendName) {
13434       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13435       SendToProgram(buf, &first);
13436     }
13437     if (first.sendTime) {
13438       if (first.useColors) {
13439         SendToProgram("white\n", &first); /*gnu kludge*/
13440       }
13441       SendTimeRemaining(&first, FALSE);
13442     }
13443     if (first.useColors) {
13444       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13445     }
13446     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13447     SetMachineThinkingEnables();
13448     first.maybeThinking = TRUE;
13449     StartClocks();
13450
13451     if (appData.autoFlipView && flipView) {
13452       flipView = !flipView;
13453       DrawPosition(FALSE, NULL);
13454       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13455     }
13456     if(bookHit) { // [HGM] book: simulate book reply
13457         static char bookMove[MSG_SIZ]; // a bit generous?
13458
13459         programStats.nodes = programStats.depth = programStats.time =
13460         programStats.score = programStats.got_only_move = 0;
13461         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13462
13463         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13464         strcat(bookMove, bookHit);
13465         HandleMachineMove(bookMove, &first);
13466     }
13467 }
13468
13469
13470 void
13471 DisplayTwoMachinesTitle ()
13472 {
13473     char buf[MSG_SIZ];
13474     if (appData.matchGames > 0) {
13475         if(appData.tourneyFile[0]) {
13476           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13477                    gameInfo.white, _("vs."), gameInfo.black,
13478                    nextGame+1, appData.matchGames+1,
13479                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13480         } else 
13481         if (first.twoMachinesColor[0] == 'w') {
13482           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13483                    gameInfo.white, _("vs."),  gameInfo.black,
13484                    first.matchWins, second.matchWins,
13485                    matchGame - 1 - (first.matchWins + second.matchWins));
13486         } else {
13487           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13488                    gameInfo.white, _("vs."), gameInfo.black,
13489                    second.matchWins, first.matchWins,
13490                    matchGame - 1 - (first.matchWins + second.matchWins));
13491         }
13492     } else {
13493       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13494     }
13495     DisplayTitle(buf);
13496 }
13497
13498 void
13499 SettingsMenuIfReady ()
13500 {
13501   if (second.lastPing != second.lastPong) {
13502     DisplayMessage("", _("Waiting for second chess program"));
13503     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13504     return;
13505   }
13506   ThawUI();
13507   DisplayMessage("", "");
13508   SettingsPopUp(&second);
13509 }
13510
13511 int
13512 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13513 {
13514     char buf[MSG_SIZ];
13515     if (cps->pr == NoProc) {
13516         StartChessProgram(cps);
13517         if (cps->protocolVersion == 1) {
13518           retry();
13519         } else {
13520           /* kludge: allow timeout for initial "feature" command */
13521           FreezeUI();
13522           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13523           DisplayMessage("", buf);
13524           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13525         }
13526         return 1;
13527     }
13528     return 0;
13529 }
13530
13531 void
13532 TwoMachinesEvent P((void))
13533 {
13534     int i;
13535     char buf[MSG_SIZ];
13536     ChessProgramState *onmove;
13537     char *bookHit = NULL;
13538     static int stalling = 0;
13539     TimeMark now;
13540     long wait;
13541
13542     if (appData.noChessProgram) return;
13543
13544     switch (gameMode) {
13545       case TwoMachinesPlay:
13546         return;
13547       case MachinePlaysWhite:
13548       case MachinePlaysBlack:
13549         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13550             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13551             return;
13552         }
13553         /* fall through */
13554       case BeginningOfGame:
13555       case PlayFromGameFile:
13556       case EndOfGame:
13557         EditGameEvent();
13558         if (gameMode != EditGame) return;
13559         break;
13560       case EditPosition:
13561         EditPositionDone(TRUE);
13562         break;
13563       case AnalyzeMode:
13564       case AnalyzeFile:
13565         ExitAnalyzeMode();
13566         break;
13567       case EditGame:
13568       default:
13569         break;
13570     }
13571
13572 //    forwardMostMove = currentMove;
13573     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13574
13575     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13576
13577     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13578     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13579       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13580       return;
13581     }
13582     if(!stalling) {
13583       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13584       SendToProgram("force\n", &second);
13585       stalling = 1;
13586       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13587       return;
13588     }
13589     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13590     if(appData.matchPause>10000 || appData.matchPause<10)
13591                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13592     wait = SubtractTimeMarks(&now, &pauseStart);
13593     if(wait < appData.matchPause) {
13594         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13595         return;
13596     }
13597     // we are now committed to starting the game
13598     stalling = 0;
13599     DisplayMessage("", "");
13600     if (startedFromSetupPosition) {
13601         SendBoard(&second, backwardMostMove);
13602     if (appData.debugMode) {
13603         fprintf(debugFP, "Two Machines\n");
13604     }
13605     }
13606     for (i = backwardMostMove; i < forwardMostMove; i++) {
13607         SendMoveToProgram(i, &second);
13608     }
13609
13610     gameMode = TwoMachinesPlay;
13611     pausing = FALSE;
13612     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13613     SetGameInfo();
13614     DisplayTwoMachinesTitle();
13615     firstMove = TRUE;
13616     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13617         onmove = &first;
13618     } else {
13619         onmove = &second;
13620     }
13621     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13622     SendToProgram(first.computerString, &first);
13623     if (first.sendName) {
13624       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13625       SendToProgram(buf, &first);
13626     }
13627     SendToProgram(second.computerString, &second);
13628     if (second.sendName) {
13629       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13630       SendToProgram(buf, &second);
13631     }
13632
13633     ResetClocks();
13634     if (!first.sendTime || !second.sendTime) {
13635         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13636         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13637     }
13638     if (onmove->sendTime) {
13639       if (onmove->useColors) {
13640         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13641       }
13642       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13643     }
13644     if (onmove->useColors) {
13645       SendToProgram(onmove->twoMachinesColor, onmove);
13646     }
13647     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13648 //    SendToProgram("go\n", onmove);
13649     onmove->maybeThinking = TRUE;
13650     SetMachineThinkingEnables();
13651
13652     StartClocks();
13653
13654     if(bookHit) { // [HGM] book: simulate book reply
13655         static char bookMove[MSG_SIZ]; // a bit generous?
13656
13657         programStats.nodes = programStats.depth = programStats.time =
13658         programStats.score = programStats.got_only_move = 0;
13659         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13660
13661         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13662         strcat(bookMove, bookHit);
13663         savedMessage = bookMove; // args for deferred call
13664         savedState = onmove;
13665         ScheduleDelayedEvent(DeferredBookMove, 1);
13666     }
13667 }
13668
13669 void
13670 TrainingEvent ()
13671 {
13672     if (gameMode == Training) {
13673       SetTrainingModeOff();
13674       gameMode = PlayFromGameFile;
13675       DisplayMessage("", _("Training mode off"));
13676     } else {
13677       gameMode = Training;
13678       animateTraining = appData.animate;
13679
13680       /* make sure we are not already at the end of the game */
13681       if (currentMove < forwardMostMove) {
13682         SetTrainingModeOn();
13683         DisplayMessage("", _("Training mode on"));
13684       } else {
13685         gameMode = PlayFromGameFile;
13686         DisplayError(_("Already at end of game"), 0);
13687       }
13688     }
13689     ModeHighlight();
13690 }
13691
13692 void
13693 IcsClientEvent ()
13694 {
13695     if (!appData.icsActive) return;
13696     switch (gameMode) {
13697       case IcsPlayingWhite:
13698       case IcsPlayingBlack:
13699       case IcsObserving:
13700       case IcsIdle:
13701       case BeginningOfGame:
13702       case IcsExamining:
13703         return;
13704
13705       case EditGame:
13706         break;
13707
13708       case EditPosition:
13709         EditPositionDone(TRUE);
13710         break;
13711
13712       case AnalyzeMode:
13713       case AnalyzeFile:
13714         ExitAnalyzeMode();
13715         break;
13716
13717       default:
13718         EditGameEvent();
13719         break;
13720     }
13721
13722     gameMode = IcsIdle;
13723     ModeHighlight();
13724     return;
13725 }
13726
13727 void
13728 EditGameEvent ()
13729 {
13730     int i;
13731
13732     switch (gameMode) {
13733       case Training:
13734         SetTrainingModeOff();
13735         break;
13736       case MachinePlaysWhite:
13737       case MachinePlaysBlack:
13738       case BeginningOfGame:
13739         SendToProgram("force\n", &first);
13740         SetUserThinkingEnables();
13741         break;
13742       case PlayFromGameFile:
13743         (void) StopLoadGameTimer();
13744         if (gameFileFP != NULL) {
13745             gameFileFP = NULL;
13746         }
13747         break;
13748       case EditPosition:
13749         EditPositionDone(TRUE);
13750         break;
13751       case AnalyzeMode:
13752       case AnalyzeFile:
13753         ExitAnalyzeMode();
13754         SendToProgram("force\n", &first);
13755         break;
13756       case TwoMachinesPlay:
13757         GameEnds(EndOfFile, NULL, GE_PLAYER);
13758         ResurrectChessProgram();
13759         SetUserThinkingEnables();
13760         break;
13761       case EndOfGame:
13762         ResurrectChessProgram();
13763         break;
13764       case IcsPlayingBlack:
13765       case IcsPlayingWhite:
13766         DisplayError(_("Warning: You are still playing a game"), 0);
13767         break;
13768       case IcsObserving:
13769         DisplayError(_("Warning: You are still observing a game"), 0);
13770         break;
13771       case IcsExamining:
13772         DisplayError(_("Warning: You are still examining a game"), 0);
13773         break;
13774       case IcsIdle:
13775         break;
13776       case EditGame:
13777       default:
13778         return;
13779     }
13780
13781     pausing = FALSE;
13782     StopClocks();
13783     first.offeredDraw = second.offeredDraw = 0;
13784
13785     if (gameMode == PlayFromGameFile) {
13786         whiteTimeRemaining = timeRemaining[0][currentMove];
13787         blackTimeRemaining = timeRemaining[1][currentMove];
13788         DisplayTitle("");
13789     }
13790
13791     if (gameMode == MachinePlaysWhite ||
13792         gameMode == MachinePlaysBlack ||
13793         gameMode == TwoMachinesPlay ||
13794         gameMode == EndOfGame) {
13795         i = forwardMostMove;
13796         while (i > currentMove) {
13797             SendToProgram("undo\n", &first);
13798             i--;
13799         }
13800         if(!adjustedClock) {
13801         whiteTimeRemaining = timeRemaining[0][currentMove];
13802         blackTimeRemaining = timeRemaining[1][currentMove];
13803         DisplayBothClocks();
13804         }
13805         if (whiteFlag || blackFlag) {
13806             whiteFlag = blackFlag = 0;
13807         }
13808         DisplayTitle("");
13809     }
13810
13811     gameMode = EditGame;
13812     ModeHighlight();
13813     SetGameInfo();
13814 }
13815
13816
13817 void
13818 EditPositionEvent ()
13819 {
13820     if (gameMode == EditPosition) {
13821         EditGameEvent();
13822         return;
13823     }
13824
13825     EditGameEvent();
13826     if (gameMode != EditGame) return;
13827
13828     gameMode = EditPosition;
13829     ModeHighlight();
13830     SetGameInfo();
13831     if (currentMove > 0)
13832       CopyBoard(boards[0], boards[currentMove]);
13833
13834     blackPlaysFirst = !WhiteOnMove(currentMove);
13835     ResetClocks();
13836     currentMove = forwardMostMove = backwardMostMove = 0;
13837     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13838     DisplayMove(-1);
13839     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13840 }
13841
13842 void
13843 ExitAnalyzeMode ()
13844 {
13845     /* [DM] icsEngineAnalyze - possible call from other functions */
13846     if (appData.icsEngineAnalyze) {
13847         appData.icsEngineAnalyze = FALSE;
13848
13849         DisplayMessage("",_("Close ICS engine analyze..."));
13850     }
13851     if (first.analysisSupport && first.analyzing) {
13852       SendToProgram("exit\n", &first);
13853       first.analyzing = FALSE;
13854     }
13855     thinkOutput[0] = NULLCHAR;
13856 }
13857
13858 void
13859 EditPositionDone (Boolean fakeRights)
13860 {
13861     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13862
13863     startedFromSetupPosition = TRUE;
13864     InitChessProgram(&first, FALSE);
13865     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13866       boards[0][EP_STATUS] = EP_NONE;
13867       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13868     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13869         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13870         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13871       } else boards[0][CASTLING][2] = NoRights;
13872     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13873         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13874         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13875       } else boards[0][CASTLING][5] = NoRights;
13876     }
13877     SendToProgram("force\n", &first);
13878     if (blackPlaysFirst) {
13879         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13880         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13881         currentMove = forwardMostMove = backwardMostMove = 1;
13882         CopyBoard(boards[1], boards[0]);
13883     } else {
13884         currentMove = forwardMostMove = backwardMostMove = 0;
13885     }
13886     SendBoard(&first, forwardMostMove);
13887     if (appData.debugMode) {
13888         fprintf(debugFP, "EditPosDone\n");
13889     }
13890     DisplayTitle("");
13891     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13892     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13893     gameMode = EditGame;
13894     ModeHighlight();
13895     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13896     ClearHighlights(); /* [AS] */
13897 }
13898
13899 /* Pause for `ms' milliseconds */
13900 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13901 void
13902 TimeDelay (long ms)
13903 {
13904     TimeMark m1, m2;
13905
13906     GetTimeMark(&m1);
13907     do {
13908         GetTimeMark(&m2);
13909     } while (SubtractTimeMarks(&m2, &m1) < ms);
13910 }
13911
13912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13913 void
13914 SendMultiLineToICS (char *buf)
13915 {
13916     char temp[MSG_SIZ+1], *p;
13917     int len;
13918
13919     len = strlen(buf);
13920     if (len > MSG_SIZ)
13921       len = MSG_SIZ;
13922
13923     strncpy(temp, buf, len);
13924     temp[len] = 0;
13925
13926     p = temp;
13927     while (*p) {
13928         if (*p == '\n' || *p == '\r')
13929           *p = ' ';
13930         ++p;
13931     }
13932
13933     strcat(temp, "\n");
13934     SendToICS(temp);
13935     SendToPlayer(temp, strlen(temp));
13936 }
13937
13938 void
13939 SetWhiteToPlayEvent ()
13940 {
13941     if (gameMode == EditPosition) {
13942         blackPlaysFirst = FALSE;
13943         DisplayBothClocks();    /* works because currentMove is 0 */
13944     } else if (gameMode == IcsExamining) {
13945         SendToICS(ics_prefix);
13946         SendToICS("tomove white\n");
13947     }
13948 }
13949
13950 void
13951 SetBlackToPlayEvent ()
13952 {
13953     if (gameMode == EditPosition) {
13954         blackPlaysFirst = TRUE;
13955         currentMove = 1;        /* kludge */
13956         DisplayBothClocks();
13957         currentMove = 0;
13958     } else if (gameMode == IcsExamining) {
13959         SendToICS(ics_prefix);
13960         SendToICS("tomove black\n");
13961     }
13962 }
13963
13964 void
13965 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13966 {
13967     char buf[MSG_SIZ];
13968     ChessSquare piece = boards[0][y][x];
13969
13970     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13971
13972     switch (selection) {
13973       case ClearBoard:
13974         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13975             SendToICS(ics_prefix);
13976             SendToICS("bsetup clear\n");
13977         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13978             SendToICS(ics_prefix);
13979             SendToICS("clearboard\n");
13980         } else {
13981             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13982                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13983                 for (y = 0; y < BOARD_HEIGHT; y++) {
13984                     if (gameMode == IcsExamining) {
13985                         if (boards[currentMove][y][x] != EmptySquare) {
13986                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13987                                     AAA + x, ONE + y);
13988                             SendToICS(buf);
13989                         }
13990                     } else {
13991                         boards[0][y][x] = p;
13992                     }
13993                 }
13994             }
13995         }
13996         if (gameMode == EditPosition) {
13997             DrawPosition(FALSE, boards[0]);
13998         }
13999         break;
14000
14001       case WhitePlay:
14002         SetWhiteToPlayEvent();
14003         break;
14004
14005       case BlackPlay:
14006         SetBlackToPlayEvent();
14007         break;
14008
14009       case EmptySquare:
14010         if (gameMode == IcsExamining) {
14011             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14012             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14013             SendToICS(buf);
14014         } else {
14015             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14016                 if(x == BOARD_LEFT-2) {
14017                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14018                     boards[0][y][1] = 0;
14019                 } else
14020                 if(x == BOARD_RGHT+1) {
14021                     if(y >= gameInfo.holdingsSize) break;
14022                     boards[0][y][BOARD_WIDTH-2] = 0;
14023                 } else break;
14024             }
14025             boards[0][y][x] = EmptySquare;
14026             DrawPosition(FALSE, boards[0]);
14027         }
14028         break;
14029
14030       case PromotePiece:
14031         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14032            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14033             selection = (ChessSquare) (PROMOTED piece);
14034         } else if(piece == EmptySquare) selection = WhiteSilver;
14035         else selection = (ChessSquare)((int)piece - 1);
14036         goto defaultlabel;
14037
14038       case DemotePiece:
14039         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14040            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14041             selection = (ChessSquare) (DEMOTED piece);
14042         } else if(piece == EmptySquare) selection = BlackSilver;
14043         else selection = (ChessSquare)((int)piece + 1);
14044         goto defaultlabel;
14045
14046       case WhiteQueen:
14047       case BlackQueen:
14048         if(gameInfo.variant == VariantShatranj ||
14049            gameInfo.variant == VariantXiangqi  ||
14050            gameInfo.variant == VariantCourier  ||
14051            gameInfo.variant == VariantMakruk     )
14052             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14053         goto defaultlabel;
14054
14055       case WhiteKing:
14056       case BlackKing:
14057         if(gameInfo.variant == VariantXiangqi)
14058             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14059         if(gameInfo.variant == VariantKnightmate)
14060             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14061       default:
14062         defaultlabel:
14063         if (gameMode == IcsExamining) {
14064             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14065             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14066                      PieceToChar(selection), AAA + x, ONE + y);
14067             SendToICS(buf);
14068         } else {
14069             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14070                 int n;
14071                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14072                     n = PieceToNumber(selection - BlackPawn);
14073                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14074                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14075                     boards[0][BOARD_HEIGHT-1-n][1]++;
14076                 } else
14077                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14078                     n = PieceToNumber(selection);
14079                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14080                     boards[0][n][BOARD_WIDTH-1] = selection;
14081                     boards[0][n][BOARD_WIDTH-2]++;
14082                 }
14083             } else
14084             boards[0][y][x] = selection;
14085             DrawPosition(TRUE, boards[0]);
14086             ClearHighlights();
14087             fromX = fromY = -1;
14088         }
14089         break;
14090     }
14091 }
14092
14093
14094 void
14095 DropMenuEvent (ChessSquare selection, int x, int y)
14096 {
14097     ChessMove moveType;
14098
14099     switch (gameMode) {
14100       case IcsPlayingWhite:
14101       case MachinePlaysBlack:
14102         if (!WhiteOnMove(currentMove)) {
14103             DisplayMoveError(_("It is Black's turn"));
14104             return;
14105         }
14106         moveType = WhiteDrop;
14107         break;
14108       case IcsPlayingBlack:
14109       case MachinePlaysWhite:
14110         if (WhiteOnMove(currentMove)) {
14111             DisplayMoveError(_("It is White's turn"));
14112             return;
14113         }
14114         moveType = BlackDrop;
14115         break;
14116       case EditGame:
14117         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14118         break;
14119       default:
14120         return;
14121     }
14122
14123     if (moveType == BlackDrop && selection < BlackPawn) {
14124       selection = (ChessSquare) ((int) selection
14125                                  + (int) BlackPawn - (int) WhitePawn);
14126     }
14127     if (boards[currentMove][y][x] != EmptySquare) {
14128         DisplayMoveError(_("That square is occupied"));
14129         return;
14130     }
14131
14132     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14133 }
14134
14135 void
14136 AcceptEvent ()
14137 {
14138     /* Accept a pending offer of any kind from opponent */
14139
14140     if (appData.icsActive) {
14141         SendToICS(ics_prefix);
14142         SendToICS("accept\n");
14143     } else if (cmailMsgLoaded) {
14144         if (currentMove == cmailOldMove &&
14145             commentList[cmailOldMove] != NULL &&
14146             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14147                    "Black offers a draw" : "White offers a draw")) {
14148             TruncateGame();
14149             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14151         } else {
14152             DisplayError(_("There is no pending offer on this move"), 0);
14153             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14154         }
14155     } else {
14156         /* Not used for offers from chess program */
14157     }
14158 }
14159
14160 void
14161 DeclineEvent ()
14162 {
14163     /* Decline a pending offer of any kind from opponent */
14164
14165     if (appData.icsActive) {
14166         SendToICS(ics_prefix);
14167         SendToICS("decline\n");
14168     } else if (cmailMsgLoaded) {
14169         if (currentMove == cmailOldMove &&
14170             commentList[cmailOldMove] != NULL &&
14171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14172                    "Black offers a draw" : "White offers a draw")) {
14173 #ifdef NOTDEF
14174             AppendComment(cmailOldMove, "Draw declined", TRUE);
14175             DisplayComment(cmailOldMove - 1, "Draw declined");
14176 #endif /*NOTDEF*/
14177         } else {
14178             DisplayError(_("There is no pending offer on this move"), 0);
14179         }
14180     } else {
14181         /* Not used for offers from chess program */
14182     }
14183 }
14184
14185 void
14186 RematchEvent ()
14187 {
14188     /* Issue ICS rematch command */
14189     if (appData.icsActive) {
14190         SendToICS(ics_prefix);
14191         SendToICS("rematch\n");
14192     }
14193 }
14194
14195 void
14196 CallFlagEvent ()
14197 {
14198     /* Call your opponent's flag (claim a win on time) */
14199     if (appData.icsActive) {
14200         SendToICS(ics_prefix);
14201         SendToICS("flag\n");
14202     } else {
14203         switch (gameMode) {
14204           default:
14205             return;
14206           case MachinePlaysWhite:
14207             if (whiteFlag) {
14208                 if (blackFlag)
14209                   GameEnds(GameIsDrawn, "Both players ran out of time",
14210                            GE_PLAYER);
14211                 else
14212                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14213             } else {
14214                 DisplayError(_("Your opponent is not out of time"), 0);
14215             }
14216             break;
14217           case MachinePlaysBlack:
14218             if (blackFlag) {
14219                 if (whiteFlag)
14220                   GameEnds(GameIsDrawn, "Both players ran out of time",
14221                            GE_PLAYER);
14222                 else
14223                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14224             } else {
14225                 DisplayError(_("Your opponent is not out of time"), 0);
14226             }
14227             break;
14228         }
14229     }
14230 }
14231
14232 void
14233 ClockClick (int which)
14234 {       // [HGM] code moved to back-end from winboard.c
14235         if(which) { // black clock
14236           if (gameMode == EditPosition || gameMode == IcsExamining) {
14237             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14238             SetBlackToPlayEvent();
14239           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14240           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14241           } else if (shiftKey) {
14242             AdjustClock(which, -1);
14243           } else if (gameMode == IcsPlayingWhite ||
14244                      gameMode == MachinePlaysBlack) {
14245             CallFlagEvent();
14246           }
14247         } else { // white clock
14248           if (gameMode == EditPosition || gameMode == IcsExamining) {
14249             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14250             SetWhiteToPlayEvent();
14251           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14252           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14253           } else if (shiftKey) {
14254             AdjustClock(which, -1);
14255           } else if (gameMode == IcsPlayingBlack ||
14256                    gameMode == MachinePlaysWhite) {
14257             CallFlagEvent();
14258           }
14259         }
14260 }
14261
14262 void
14263 DrawEvent ()
14264 {
14265     /* Offer draw or accept pending draw offer from opponent */
14266
14267     if (appData.icsActive) {
14268         /* Note: tournament rules require draw offers to be
14269            made after you make your move but before you punch
14270            your clock.  Currently ICS doesn't let you do that;
14271            instead, you immediately punch your clock after making
14272            a move, but you can offer a draw at any time. */
14273
14274         SendToICS(ics_prefix);
14275         SendToICS("draw\n");
14276         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14277     } else if (cmailMsgLoaded) {
14278         if (currentMove == cmailOldMove &&
14279             commentList[cmailOldMove] != NULL &&
14280             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14281                    "Black offers a draw" : "White offers a draw")) {
14282             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14284         } else if (currentMove == cmailOldMove + 1) {
14285             char *offer = WhiteOnMove(cmailOldMove) ?
14286               "White offers a draw" : "Black offers a draw";
14287             AppendComment(currentMove, offer, TRUE);
14288             DisplayComment(currentMove - 1, offer);
14289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14290         } else {
14291             DisplayError(_("You must make your move before offering a draw"), 0);
14292             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14293         }
14294     } else if (first.offeredDraw) {
14295         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14296     } else {
14297         if (first.sendDrawOffers) {
14298             SendToProgram("draw\n", &first);
14299             userOfferedDraw = TRUE;
14300         }
14301     }
14302 }
14303
14304 void
14305 AdjournEvent ()
14306 {
14307     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14308
14309     if (appData.icsActive) {
14310         SendToICS(ics_prefix);
14311         SendToICS("adjourn\n");
14312     } else {
14313         /* Currently GNU Chess doesn't offer or accept Adjourns */
14314     }
14315 }
14316
14317
14318 void
14319 AbortEvent ()
14320 {
14321     /* Offer Abort or accept pending Abort offer from opponent */
14322
14323     if (appData.icsActive) {
14324         SendToICS(ics_prefix);
14325         SendToICS("abort\n");
14326     } else {
14327         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14328     }
14329 }
14330
14331 void
14332 ResignEvent ()
14333 {
14334     /* Resign.  You can do this even if it's not your turn. */
14335
14336     if (appData.icsActive) {
14337         SendToICS(ics_prefix);
14338         SendToICS("resign\n");
14339     } else {
14340         switch (gameMode) {
14341           case MachinePlaysWhite:
14342             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14343             break;
14344           case MachinePlaysBlack:
14345             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14346             break;
14347           case EditGame:
14348             if (cmailMsgLoaded) {
14349                 TruncateGame();
14350                 if (WhiteOnMove(cmailOldMove)) {
14351                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14352                 } else {
14353                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14354                 }
14355                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14356             }
14357             break;
14358           default:
14359             break;
14360         }
14361     }
14362 }
14363
14364
14365 void
14366 StopObservingEvent ()
14367 {
14368     /* Stop observing current games */
14369     SendToICS(ics_prefix);
14370     SendToICS("unobserve\n");
14371 }
14372
14373 void
14374 StopExaminingEvent ()
14375 {
14376     /* Stop observing current game */
14377     SendToICS(ics_prefix);
14378     SendToICS("unexamine\n");
14379 }
14380
14381 void
14382 ForwardInner (int target)
14383 {
14384     int limit; int oldSeekGraphUp = seekGraphUp;
14385
14386     if (appData.debugMode)
14387         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14388                 target, currentMove, forwardMostMove);
14389
14390     if (gameMode == EditPosition)
14391       return;
14392
14393     seekGraphUp = FALSE;
14394     MarkTargetSquares(1);
14395
14396     if (gameMode == PlayFromGameFile && !pausing)
14397       PauseEvent();
14398
14399     if (gameMode == IcsExamining && pausing)
14400       limit = pauseExamForwardMostMove;
14401     else
14402       limit = forwardMostMove;
14403
14404     if (target > limit) target = limit;
14405
14406     if (target > 0 && moveList[target - 1][0]) {
14407         int fromX, fromY, toX, toY;
14408         toX = moveList[target - 1][2] - AAA;
14409         toY = moveList[target - 1][3] - ONE;
14410         if (moveList[target - 1][1] == '@') {
14411             if (appData.highlightLastMove) {
14412                 SetHighlights(-1, -1, toX, toY);
14413             }
14414         } else {
14415             fromX = moveList[target - 1][0] - AAA;
14416             fromY = moveList[target - 1][1] - ONE;
14417             if (target == currentMove + 1) {
14418                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14419             }
14420             if (appData.highlightLastMove) {
14421                 SetHighlights(fromX, fromY, toX, toY);
14422             }
14423         }
14424     }
14425     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14426         gameMode == Training || gameMode == PlayFromGameFile ||
14427         gameMode == AnalyzeFile) {
14428         while (currentMove < target) {
14429             SendMoveToProgram(currentMove++, &first);
14430         }
14431     } else {
14432         currentMove = target;
14433     }
14434
14435     if (gameMode == EditGame || gameMode == EndOfGame) {
14436         whiteTimeRemaining = timeRemaining[0][currentMove];
14437         blackTimeRemaining = timeRemaining[1][currentMove];
14438     }
14439     DisplayBothClocks();
14440     DisplayMove(currentMove - 1);
14441     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14442     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14443     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14444         DisplayComment(currentMove - 1, commentList[currentMove]);
14445     }
14446     ClearMap(); // [HGM] exclude: invalidate map
14447 }
14448
14449
14450 void
14451 ForwardEvent ()
14452 {
14453     if (gameMode == IcsExamining && !pausing) {
14454         SendToICS(ics_prefix);
14455         SendToICS("forward\n");
14456     } else {
14457         ForwardInner(currentMove + 1);
14458     }
14459 }
14460
14461 void
14462 ToEndEvent ()
14463 {
14464     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14465         /* to optimze, we temporarily turn off analysis mode while we feed
14466          * the remaining moves to the engine. Otherwise we get analysis output
14467          * after each move.
14468          */
14469         if (first.analysisSupport) {
14470           SendToProgram("exit\nforce\n", &first);
14471           first.analyzing = FALSE;
14472         }
14473     }
14474
14475     if (gameMode == IcsExamining && !pausing) {
14476         SendToICS(ics_prefix);
14477         SendToICS("forward 999999\n");
14478     } else {
14479         ForwardInner(forwardMostMove);
14480     }
14481
14482     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14483         /* we have fed all the moves, so reactivate analysis mode */
14484         SendToProgram("analyze\n", &first);
14485         first.analyzing = TRUE;
14486         /*first.maybeThinking = TRUE;*/
14487         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14488     }
14489 }
14490
14491 void
14492 BackwardInner (int target)
14493 {
14494     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14495
14496     if (appData.debugMode)
14497         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14498                 target, currentMove, forwardMostMove);
14499
14500     if (gameMode == EditPosition) return;
14501     seekGraphUp = FALSE;
14502     MarkTargetSquares(1);
14503     if (currentMove <= backwardMostMove) {
14504         ClearHighlights();
14505         DrawPosition(full_redraw, boards[currentMove]);
14506         return;
14507     }
14508     if (gameMode == PlayFromGameFile && !pausing)
14509       PauseEvent();
14510
14511     if (moveList[target][0]) {
14512         int fromX, fromY, toX, toY;
14513         toX = moveList[target][2] - AAA;
14514         toY = moveList[target][3] - ONE;
14515         if (moveList[target][1] == '@') {
14516             if (appData.highlightLastMove) {
14517                 SetHighlights(-1, -1, toX, toY);
14518             }
14519         } else {
14520             fromX = moveList[target][0] - AAA;
14521             fromY = moveList[target][1] - ONE;
14522             if (target == currentMove - 1) {
14523                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14524             }
14525             if (appData.highlightLastMove) {
14526                 SetHighlights(fromX, fromY, toX, toY);
14527             }
14528         }
14529     }
14530     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14531         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14532         while (currentMove > target) {
14533             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14534                 // null move cannot be undone. Reload program with move history before it.
14535                 int i;
14536                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14537                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14538                 }
14539                 SendBoard(&first, i); 
14540                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14541                 break;
14542             }
14543             SendToProgram("undo\n", &first);
14544             currentMove--;
14545         }
14546     } else {
14547         currentMove = target;
14548     }
14549
14550     if (gameMode == EditGame || gameMode == EndOfGame) {
14551         whiteTimeRemaining = timeRemaining[0][currentMove];
14552         blackTimeRemaining = timeRemaining[1][currentMove];
14553     }
14554     DisplayBothClocks();
14555     DisplayMove(currentMove - 1);
14556     DrawPosition(full_redraw, boards[currentMove]);
14557     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14558     // [HGM] PV info: routine tests if comment empty
14559     DisplayComment(currentMove - 1, commentList[currentMove]);
14560     ClearMap(); // [HGM] exclude: invalidate map
14561 }
14562
14563 void
14564 BackwardEvent ()
14565 {
14566     if (gameMode == IcsExamining && !pausing) {
14567         SendToICS(ics_prefix);
14568         SendToICS("backward\n");
14569     } else {
14570         BackwardInner(currentMove - 1);
14571     }
14572 }
14573
14574 void
14575 ToStartEvent ()
14576 {
14577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14578         /* to optimize, we temporarily turn off analysis mode while we undo
14579          * all the moves. Otherwise we get analysis output after each undo.
14580          */
14581         if (first.analysisSupport) {
14582           SendToProgram("exit\nforce\n", &first);
14583           first.analyzing = FALSE;
14584         }
14585     }
14586
14587     if (gameMode == IcsExamining && !pausing) {
14588         SendToICS(ics_prefix);
14589         SendToICS("backward 999999\n");
14590     } else {
14591         BackwardInner(backwardMostMove);
14592     }
14593
14594     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14595         /* we have fed all the moves, so reactivate analysis mode */
14596         SendToProgram("analyze\n", &first);
14597         first.analyzing = TRUE;
14598         /*first.maybeThinking = TRUE;*/
14599         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14600     }
14601 }
14602
14603 void
14604 ToNrEvent (int to)
14605 {
14606   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14607   if (to >= forwardMostMove) to = forwardMostMove;
14608   if (to <= backwardMostMove) to = backwardMostMove;
14609   if (to < currentMove) {
14610     BackwardInner(to);
14611   } else {
14612     ForwardInner(to);
14613   }
14614 }
14615
14616 void
14617 RevertEvent (Boolean annotate)
14618 {
14619     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14620         return;
14621     }
14622     if (gameMode != IcsExamining) {
14623         DisplayError(_("You are not examining a game"), 0);
14624         return;
14625     }
14626     if (pausing) {
14627         DisplayError(_("You can't revert while pausing"), 0);
14628         return;
14629     }
14630     SendToICS(ics_prefix);
14631     SendToICS("revert\n");
14632 }
14633
14634 void
14635 RetractMoveEvent ()
14636 {
14637     switch (gameMode) {
14638       case MachinePlaysWhite:
14639       case MachinePlaysBlack:
14640         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14641             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14642             return;
14643         }
14644         if (forwardMostMove < 2) return;
14645         currentMove = forwardMostMove = forwardMostMove - 2;
14646         whiteTimeRemaining = timeRemaining[0][currentMove];
14647         blackTimeRemaining = timeRemaining[1][currentMove];
14648         DisplayBothClocks();
14649         DisplayMove(currentMove - 1);
14650         ClearHighlights();/*!! could figure this out*/
14651         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14652         SendToProgram("remove\n", &first);
14653         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14654         break;
14655
14656       case BeginningOfGame:
14657       default:
14658         break;
14659
14660       case IcsPlayingWhite:
14661       case IcsPlayingBlack:
14662         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14663             SendToICS(ics_prefix);
14664             SendToICS("takeback 2\n");
14665         } else {
14666             SendToICS(ics_prefix);
14667             SendToICS("takeback 1\n");
14668         }
14669         break;
14670     }
14671 }
14672
14673 void
14674 MoveNowEvent ()
14675 {
14676     ChessProgramState *cps;
14677
14678     switch (gameMode) {
14679       case MachinePlaysWhite:
14680         if (!WhiteOnMove(forwardMostMove)) {
14681             DisplayError(_("It is your turn"), 0);
14682             return;
14683         }
14684         cps = &first;
14685         break;
14686       case MachinePlaysBlack:
14687         if (WhiteOnMove(forwardMostMove)) {
14688             DisplayError(_("It is your turn"), 0);
14689             return;
14690         }
14691         cps = &first;
14692         break;
14693       case TwoMachinesPlay:
14694         if (WhiteOnMove(forwardMostMove) ==
14695             (first.twoMachinesColor[0] == 'w')) {
14696             cps = &first;
14697         } else {
14698             cps = &second;
14699         }
14700         break;
14701       case BeginningOfGame:
14702       default:
14703         return;
14704     }
14705     SendToProgram("?\n", cps);
14706 }
14707
14708 void
14709 TruncateGameEvent ()
14710 {
14711     EditGameEvent();
14712     if (gameMode != EditGame) return;
14713     TruncateGame();
14714 }
14715
14716 void
14717 TruncateGame ()
14718 {
14719     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14720     if (forwardMostMove > currentMove) {
14721         if (gameInfo.resultDetails != NULL) {
14722             free(gameInfo.resultDetails);
14723             gameInfo.resultDetails = NULL;
14724             gameInfo.result = GameUnfinished;
14725         }
14726         forwardMostMove = currentMove;
14727         HistorySet(parseList, backwardMostMove, forwardMostMove,
14728                    currentMove-1);
14729     }
14730 }
14731
14732 void
14733 HintEvent ()
14734 {
14735     if (appData.noChessProgram) return;
14736     switch (gameMode) {
14737       case MachinePlaysWhite:
14738         if (WhiteOnMove(forwardMostMove)) {
14739             DisplayError(_("Wait until your turn"), 0);
14740             return;
14741         }
14742         break;
14743       case BeginningOfGame:
14744       case MachinePlaysBlack:
14745         if (!WhiteOnMove(forwardMostMove)) {
14746             DisplayError(_("Wait until your turn"), 0);
14747             return;
14748         }
14749         break;
14750       default:
14751         DisplayError(_("No hint available"), 0);
14752         return;
14753     }
14754     SendToProgram("hint\n", &first);
14755     hintRequested = TRUE;
14756 }
14757
14758 void
14759 BookEvent ()
14760 {
14761     if (appData.noChessProgram) return;
14762     switch (gameMode) {
14763       case MachinePlaysWhite:
14764         if (WhiteOnMove(forwardMostMove)) {
14765             DisplayError(_("Wait until your turn"), 0);
14766             return;
14767         }
14768         break;
14769       case BeginningOfGame:
14770       case MachinePlaysBlack:
14771         if (!WhiteOnMove(forwardMostMove)) {
14772             DisplayError(_("Wait until your turn"), 0);
14773             return;
14774         }
14775         break;
14776       case EditPosition:
14777         EditPositionDone(TRUE);
14778         break;
14779       case TwoMachinesPlay:
14780         return;
14781       default:
14782         break;
14783     }
14784     SendToProgram("bk\n", &first);
14785     bookOutput[0] = NULLCHAR;
14786     bookRequested = TRUE;
14787 }
14788
14789 void
14790 AboutGameEvent ()
14791 {
14792     char *tags = PGNTags(&gameInfo);
14793     TagsPopUp(tags, CmailMsg());
14794     free(tags);
14795 }
14796
14797 /* end button procedures */
14798
14799 void
14800 PrintPosition (FILE *fp, int move)
14801 {
14802     int i, j;
14803
14804     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14805         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14806             char c = PieceToChar(boards[move][i][j]);
14807             fputc(c == 'x' ? '.' : c, fp);
14808             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14809         }
14810     }
14811     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14812       fprintf(fp, "white to play\n");
14813     else
14814       fprintf(fp, "black to play\n");
14815 }
14816
14817 void
14818 PrintOpponents (FILE *fp)
14819 {
14820     if (gameInfo.white != NULL) {
14821         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14822     } else {
14823         fprintf(fp, "\n");
14824     }
14825 }
14826
14827 /* Find last component of program's own name, using some heuristics */
14828 void
14829 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14830 {
14831     char *p, *q, c;
14832     int local = (strcmp(host, "localhost") == 0);
14833     while (!local && (p = strchr(prog, ';')) != NULL) {
14834         p++;
14835         while (*p == ' ') p++;
14836         prog = p;
14837     }
14838     if (*prog == '"' || *prog == '\'') {
14839         q = strchr(prog + 1, *prog);
14840     } else {
14841         q = strchr(prog, ' ');
14842     }
14843     if (q == NULL) q = prog + strlen(prog);
14844     p = q;
14845     while (p >= prog && *p != '/' && *p != '\\') p--;
14846     p++;
14847     if(p == prog && *p == '"') p++;
14848     c = *q; *q = 0;
14849     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14850     memcpy(buf, p, q - p);
14851     buf[q - p] = NULLCHAR;
14852     if (!local) {
14853         strcat(buf, "@");
14854         strcat(buf, host);
14855     }
14856 }
14857
14858 char *
14859 TimeControlTagValue ()
14860 {
14861     char buf[MSG_SIZ];
14862     if (!appData.clockMode) {
14863       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14864     } else if (movesPerSession > 0) {
14865       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14866     } else if (timeIncrement == 0) {
14867       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14868     } else {
14869       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14870     }
14871     return StrSave(buf);
14872 }
14873
14874 void
14875 SetGameInfo ()
14876 {
14877     /* This routine is used only for certain modes */
14878     VariantClass v = gameInfo.variant;
14879     ChessMove r = GameUnfinished;
14880     char *p = NULL;
14881
14882     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14883         r = gameInfo.result;
14884         p = gameInfo.resultDetails;
14885         gameInfo.resultDetails = NULL;
14886     }
14887     ClearGameInfo(&gameInfo);
14888     gameInfo.variant = v;
14889
14890     switch (gameMode) {
14891       case MachinePlaysWhite:
14892         gameInfo.event = StrSave( appData.pgnEventHeader );
14893         gameInfo.site = StrSave(HostName());
14894         gameInfo.date = PGNDate();
14895         gameInfo.round = StrSave("-");
14896         gameInfo.white = StrSave(first.tidy);
14897         gameInfo.black = StrSave(UserName());
14898         gameInfo.timeControl = TimeControlTagValue();
14899         break;
14900
14901       case MachinePlaysBlack:
14902         gameInfo.event = StrSave( appData.pgnEventHeader );
14903         gameInfo.site = StrSave(HostName());
14904         gameInfo.date = PGNDate();
14905         gameInfo.round = StrSave("-");
14906         gameInfo.white = StrSave(UserName());
14907         gameInfo.black = StrSave(first.tidy);
14908         gameInfo.timeControl = TimeControlTagValue();
14909         break;
14910
14911       case TwoMachinesPlay:
14912         gameInfo.event = StrSave( appData.pgnEventHeader );
14913         gameInfo.site = StrSave(HostName());
14914         gameInfo.date = PGNDate();
14915         if (roundNr > 0) {
14916             char buf[MSG_SIZ];
14917             snprintf(buf, MSG_SIZ, "%d", roundNr);
14918             gameInfo.round = StrSave(buf);
14919         } else {
14920             gameInfo.round = StrSave("-");
14921         }
14922         if (first.twoMachinesColor[0] == 'w') {
14923             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14924             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14925         } else {
14926             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14927             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14928         }
14929         gameInfo.timeControl = TimeControlTagValue();
14930         break;
14931
14932       case EditGame:
14933         gameInfo.event = StrSave("Edited game");
14934         gameInfo.site = StrSave(HostName());
14935         gameInfo.date = PGNDate();
14936         gameInfo.round = StrSave("-");
14937         gameInfo.white = StrSave("-");
14938         gameInfo.black = StrSave("-");
14939         gameInfo.result = r;
14940         gameInfo.resultDetails = p;
14941         break;
14942
14943       case EditPosition:
14944         gameInfo.event = StrSave("Edited position");
14945         gameInfo.site = StrSave(HostName());
14946         gameInfo.date = PGNDate();
14947         gameInfo.round = StrSave("-");
14948         gameInfo.white = StrSave("-");
14949         gameInfo.black = StrSave("-");
14950         break;
14951
14952       case IcsPlayingWhite:
14953       case IcsPlayingBlack:
14954       case IcsObserving:
14955       case IcsExamining:
14956         break;
14957
14958       case PlayFromGameFile:
14959         gameInfo.event = StrSave("Game from non-PGN file");
14960         gameInfo.site = StrSave(HostName());
14961         gameInfo.date = PGNDate();
14962         gameInfo.round = StrSave("-");
14963         gameInfo.white = StrSave("?");
14964         gameInfo.black = StrSave("?");
14965         break;
14966
14967       default:
14968         break;
14969     }
14970 }
14971
14972 void
14973 ReplaceComment (int index, char *text)
14974 {
14975     int len;
14976     char *p;
14977     float score;
14978
14979     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14980        pvInfoList[index-1].depth == len &&
14981        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14982        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14983     while (*text == '\n') text++;
14984     len = strlen(text);
14985     while (len > 0 && text[len - 1] == '\n') len--;
14986
14987     if (commentList[index] != NULL)
14988       free(commentList[index]);
14989
14990     if (len == 0) {
14991         commentList[index] = NULL;
14992         return;
14993     }
14994   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14995       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14996       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14997     commentList[index] = (char *) malloc(len + 2);
14998     strncpy(commentList[index], text, len);
14999     commentList[index][len] = '\n';
15000     commentList[index][len + 1] = NULLCHAR;
15001   } else {
15002     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15003     char *p;
15004     commentList[index] = (char *) malloc(len + 7);
15005     safeStrCpy(commentList[index], "{\n", 3);
15006     safeStrCpy(commentList[index]+2, text, len+1);
15007     commentList[index][len+2] = NULLCHAR;
15008     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15009     strcat(commentList[index], "\n}\n");
15010   }
15011 }
15012
15013 void
15014 CrushCRs (char *text)
15015 {
15016   char *p = text;
15017   char *q = text;
15018   char ch;
15019
15020   do {
15021     ch = *p++;
15022     if (ch == '\r') continue;
15023     *q++ = ch;
15024   } while (ch != '\0');
15025 }
15026
15027 void
15028 AppendComment (int index, char *text, Boolean addBraces)
15029 /* addBraces  tells if we should add {} */
15030 {
15031     int oldlen, len;
15032     char *old;
15033
15034 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15035     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15036
15037     CrushCRs(text);
15038     while (*text == '\n') text++;
15039     len = strlen(text);
15040     while (len > 0 && text[len - 1] == '\n') len--;
15041     text[len] = NULLCHAR;
15042
15043     if (len == 0) return;
15044
15045     if (commentList[index] != NULL) {
15046       Boolean addClosingBrace = addBraces;
15047         old = commentList[index];
15048         oldlen = strlen(old);
15049         while(commentList[index][oldlen-1] ==  '\n')
15050           commentList[index][--oldlen] = NULLCHAR;
15051         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15052         safeStrCpy(commentList[index], old, oldlen + len + 6);
15053         free(old);
15054         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15055         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15056           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15057           while (*text == '\n') { text++; len--; }
15058           commentList[index][--oldlen] = NULLCHAR;
15059       }
15060         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15061         else          strcat(commentList[index], "\n");
15062         strcat(commentList[index], text);
15063         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15064         else          strcat(commentList[index], "\n");
15065     } else {
15066         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15067         if(addBraces)
15068           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15069         else commentList[index][0] = NULLCHAR;
15070         strcat(commentList[index], text);
15071         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15072         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15073     }
15074 }
15075
15076 static char *
15077 FindStr (char * text, char * sub_text)
15078 {
15079     char * result = strstr( text, sub_text );
15080
15081     if( result != NULL ) {
15082         result += strlen( sub_text );
15083     }
15084
15085     return result;
15086 }
15087
15088 /* [AS] Try to extract PV info from PGN comment */
15089 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15090 char *
15091 GetInfoFromComment (int index, char * text)
15092 {
15093     char * sep = text, *p;
15094
15095     if( text != NULL && index > 0 ) {
15096         int score = 0;
15097         int depth = 0;
15098         int time = -1, sec = 0, deci;
15099         char * s_eval = FindStr( text, "[%eval " );
15100         char * s_emt = FindStr( text, "[%emt " );
15101
15102         if( s_eval != NULL || s_emt != NULL ) {
15103             /* New style */
15104             char delim;
15105
15106             if( s_eval != NULL ) {
15107                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15108                     return text;
15109                 }
15110
15111                 if( delim != ']' ) {
15112                     return text;
15113                 }
15114             }
15115
15116             if( s_emt != NULL ) {
15117             }
15118                 return text;
15119         }
15120         else {
15121             /* We expect something like: [+|-]nnn.nn/dd */
15122             int score_lo = 0;
15123
15124             if(*text != '{') return text; // [HGM] braces: must be normal comment
15125
15126             sep = strchr( text, '/' );
15127             if( sep == NULL || sep < (text+4) ) {
15128                 return text;
15129             }
15130
15131             p = text;
15132             if(p[1] == '(') { // comment starts with PV
15133                p = strchr(p, ')'); // locate end of PV
15134                if(p == NULL || sep < p+5) return text;
15135                // at this point we have something like "{(.*) +0.23/6 ..."
15136                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15137                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15138                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15139             }
15140             time = -1; sec = -1; deci = -1;
15141             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15142                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15143                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15144                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15145                 return text;
15146             }
15147
15148             if( score_lo < 0 || score_lo >= 100 ) {
15149                 return text;
15150             }
15151
15152             if(sec >= 0) time = 600*time + 10*sec; else
15153             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15154
15155             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15156
15157             /* [HGM] PV time: now locate end of PV info */
15158             while( *++sep >= '0' && *sep <= '9'); // strip depth
15159             if(time >= 0)
15160             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15161             if(sec >= 0)
15162             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15163             if(deci >= 0)
15164             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15165             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15166         }
15167
15168         if( depth <= 0 ) {
15169             return text;
15170         }
15171
15172         if( time < 0 ) {
15173             time = -1;
15174         }
15175
15176         pvInfoList[index-1].depth = depth;
15177         pvInfoList[index-1].score = score;
15178         pvInfoList[index-1].time  = 10*time; // centi-sec
15179         if(*sep == '}') *sep = 0; else *--sep = '{';
15180         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15181     }
15182     return sep;
15183 }
15184
15185 void
15186 SendToProgram (char *message, ChessProgramState *cps)
15187 {
15188     int count, outCount, error;
15189     char buf[MSG_SIZ];
15190
15191     if (cps->pr == NoProc) return;
15192     Attention(cps);
15193
15194     if (appData.debugMode) {
15195         TimeMark now;
15196         GetTimeMark(&now);
15197         fprintf(debugFP, "%ld >%-6s: %s",
15198                 SubtractTimeMarks(&now, &programStartTime),
15199                 cps->which, message);
15200         if(serverFP)
15201             fprintf(serverFP, "%ld >%-6s: %s",
15202                 SubtractTimeMarks(&now, &programStartTime),
15203                 cps->which, message), fflush(serverFP);
15204     }
15205
15206     count = strlen(message);
15207     outCount = OutputToProcess(cps->pr, message, count, &error);
15208     if (outCount < count && !exiting
15209                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15210       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15211       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15212         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15213             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15214                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15215                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15216                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15217             } else {
15218                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15219                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15220                 gameInfo.result = res;
15221             }
15222             gameInfo.resultDetails = StrSave(buf);
15223         }
15224         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15225         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15226     }
15227 }
15228
15229 void
15230 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15231 {
15232     char *end_str;
15233     char buf[MSG_SIZ];
15234     ChessProgramState *cps = (ChessProgramState *)closure;
15235
15236     if (isr != cps->isr) return; /* Killed intentionally */
15237     if (count <= 0) {
15238         if (count == 0) {
15239             RemoveInputSource(cps->isr);
15240             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15241             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15242                     _(cps->which), cps->program);
15243         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15244                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15245                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15246                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15247                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15248                 } else {
15249                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15250                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15251                     gameInfo.result = res;
15252                 }
15253                 gameInfo.resultDetails = StrSave(buf);
15254             }
15255             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15256             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15257         } else {
15258             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15259                     _(cps->which), cps->program);
15260             RemoveInputSource(cps->isr);
15261
15262             /* [AS] Program is misbehaving badly... kill it */
15263             if( count == -2 ) {
15264                 DestroyChildProcess( cps->pr, 9 );
15265                 cps->pr = NoProc;
15266             }
15267
15268             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15269         }
15270         return;
15271     }
15272
15273     if ((end_str = strchr(message, '\r')) != NULL)
15274       *end_str = NULLCHAR;
15275     if ((end_str = strchr(message, '\n')) != NULL)
15276       *end_str = NULLCHAR;
15277
15278     if (appData.debugMode) {
15279         TimeMark now; int print = 1;
15280         char *quote = ""; char c; int i;
15281
15282         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15283                 char start = message[0];
15284                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15285                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15286                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15287                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15288                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15289                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15290                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15291                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15292                    sscanf(message, "hint: %c", &c)!=1 && 
15293                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15294                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15295                     print = (appData.engineComments >= 2);
15296                 }
15297                 message[0] = start; // restore original message
15298         }
15299         if(print) {
15300                 GetTimeMark(&now);
15301                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15302                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15303                         quote,
15304                         message);
15305                 if(serverFP)
15306                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15307                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15308                         quote,
15309                         message), fflush(serverFP);
15310         }
15311     }
15312
15313     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15314     if (appData.icsEngineAnalyze) {
15315         if (strstr(message, "whisper") != NULL ||
15316              strstr(message, "kibitz") != NULL ||
15317             strstr(message, "tellics") != NULL) return;
15318     }
15319
15320     HandleMachineMove(message, cps);
15321 }
15322
15323
15324 void
15325 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15326 {
15327     char buf[MSG_SIZ];
15328     int seconds;
15329
15330     if( timeControl_2 > 0 ) {
15331         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15332             tc = timeControl_2;
15333         }
15334     }
15335     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15336     inc /= cps->timeOdds;
15337     st  /= cps->timeOdds;
15338
15339     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15340
15341     if (st > 0) {
15342       /* Set exact time per move, normally using st command */
15343       if (cps->stKludge) {
15344         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15345         seconds = st % 60;
15346         if (seconds == 0) {
15347           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15348         } else {
15349           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15350         }
15351       } else {
15352         snprintf(buf, MSG_SIZ, "st %d\n", st);
15353       }
15354     } else {
15355       /* Set conventional or incremental time control, using level command */
15356       if (seconds == 0) {
15357         /* Note old gnuchess bug -- minutes:seconds used to not work.
15358            Fixed in later versions, but still avoid :seconds
15359            when seconds is 0. */
15360         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15361       } else {
15362         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15363                  seconds, inc/1000.);
15364       }
15365     }
15366     SendToProgram(buf, cps);
15367
15368     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15369     /* Orthogonally, limit search to given depth */
15370     if (sd > 0) {
15371       if (cps->sdKludge) {
15372         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15373       } else {
15374         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15375       }
15376       SendToProgram(buf, cps);
15377     }
15378
15379     if(cps->nps >= 0) { /* [HGM] nps */
15380         if(cps->supportsNPS == FALSE)
15381           cps->nps = -1; // don't use if engine explicitly says not supported!
15382         else {
15383           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15384           SendToProgram(buf, cps);
15385         }
15386     }
15387 }
15388
15389 ChessProgramState *
15390 WhitePlayer ()
15391 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15392 {
15393     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15394        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15395         return &second;
15396     return &first;
15397 }
15398
15399 void
15400 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15401 {
15402     char message[MSG_SIZ];
15403     long time, otime;
15404
15405     /* Note: this routine must be called when the clocks are stopped
15406        or when they have *just* been set or switched; otherwise
15407        it will be off by the time since the current tick started.
15408     */
15409     if (machineWhite) {
15410         time = whiteTimeRemaining / 10;
15411         otime = blackTimeRemaining / 10;
15412     } else {
15413         time = blackTimeRemaining / 10;
15414         otime = whiteTimeRemaining / 10;
15415     }
15416     /* [HGM] translate opponent's time by time-odds factor */
15417     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15418
15419     if (time <= 0) time = 1;
15420     if (otime <= 0) otime = 1;
15421
15422     snprintf(message, MSG_SIZ, "time %ld\n", time);
15423     SendToProgram(message, cps);
15424
15425     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15426     SendToProgram(message, cps);
15427 }
15428
15429 int
15430 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15431 {
15432   char buf[MSG_SIZ];
15433   int len = strlen(name);
15434   int val;
15435
15436   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15437     (*p) += len + 1;
15438     sscanf(*p, "%d", &val);
15439     *loc = (val != 0);
15440     while (**p && **p != ' ')
15441       (*p)++;
15442     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15443     SendToProgram(buf, cps);
15444     return TRUE;
15445   }
15446   return FALSE;
15447 }
15448
15449 int
15450 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15451 {
15452   char buf[MSG_SIZ];
15453   int len = strlen(name);
15454   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15455     (*p) += len + 1;
15456     sscanf(*p, "%d", loc);
15457     while (**p && **p != ' ') (*p)++;
15458     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15459     SendToProgram(buf, cps);
15460     return TRUE;
15461   }
15462   return FALSE;
15463 }
15464
15465 int
15466 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15467 {
15468   char buf[MSG_SIZ];
15469   int len = strlen(name);
15470   if (strncmp((*p), name, len) == 0
15471       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15472     (*p) += len + 2;
15473     sscanf(*p, "%[^\"]", loc);
15474     while (**p && **p != '\"') (*p)++;
15475     if (**p == '\"') (*p)++;
15476     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15477     SendToProgram(buf, cps);
15478     return TRUE;
15479   }
15480   return FALSE;
15481 }
15482
15483 int
15484 ParseOption (Option *opt, ChessProgramState *cps)
15485 // [HGM] options: process the string that defines an engine option, and determine
15486 // name, type, default value, and allowed value range
15487 {
15488         char *p, *q, buf[MSG_SIZ];
15489         int n, min = (-1)<<31, max = 1<<31, def;
15490
15491         if(p = strstr(opt->name, " -spin ")) {
15492             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15493             if(max < min) max = min; // enforce consistency
15494             if(def < min) def = min;
15495             if(def > max) def = max;
15496             opt->value = def;
15497             opt->min = min;
15498             opt->max = max;
15499             opt->type = Spin;
15500         } else if((p = strstr(opt->name, " -slider "))) {
15501             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15502             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15503             if(max < min) max = min; // enforce consistency
15504             if(def < min) def = min;
15505             if(def > max) def = max;
15506             opt->value = def;
15507             opt->min = min;
15508             opt->max = max;
15509             opt->type = Spin; // Slider;
15510         } else if((p = strstr(opt->name, " -string "))) {
15511             opt->textValue = p+9;
15512             opt->type = TextBox;
15513         } else if((p = strstr(opt->name, " -file "))) {
15514             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15515             opt->textValue = p+7;
15516             opt->type = FileName; // FileName;
15517         } else if((p = strstr(opt->name, " -path "))) {
15518             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15519             opt->textValue = p+7;
15520             opt->type = PathName; // PathName;
15521         } else if(p = strstr(opt->name, " -check ")) {
15522             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15523             opt->value = (def != 0);
15524             opt->type = CheckBox;
15525         } else if(p = strstr(opt->name, " -combo ")) {
15526             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15527             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15528             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15529             opt->value = n = 0;
15530             while(q = StrStr(q, " /// ")) {
15531                 n++; *q = 0;    // count choices, and null-terminate each of them
15532                 q += 5;
15533                 if(*q == '*') { // remember default, which is marked with * prefix
15534                     q++;
15535                     opt->value = n;
15536                 }
15537                 cps->comboList[cps->comboCnt++] = q;
15538             }
15539             cps->comboList[cps->comboCnt++] = NULL;
15540             opt->max = n + 1;
15541             opt->type = ComboBox;
15542         } else if(p = strstr(opt->name, " -button")) {
15543             opt->type = Button;
15544         } else if(p = strstr(opt->name, " -save")) {
15545             opt->type = SaveButton;
15546         } else return FALSE;
15547         *p = 0; // terminate option name
15548         // now look if the command-line options define a setting for this engine option.
15549         if(cps->optionSettings && cps->optionSettings[0])
15550             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15551         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15552           snprintf(buf, MSG_SIZ, "option %s", p);
15553                 if(p = strstr(buf, ",")) *p = 0;
15554                 if(q = strchr(buf, '=')) switch(opt->type) {
15555                     case ComboBox:
15556                         for(n=0; n<opt->max; n++)
15557                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15558                         break;
15559                     case TextBox:
15560                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15561                         break;
15562                     case Spin:
15563                     case CheckBox:
15564                         opt->value = atoi(q+1);
15565                     default:
15566                         break;
15567                 }
15568                 strcat(buf, "\n");
15569                 SendToProgram(buf, cps);
15570         }
15571         return TRUE;
15572 }
15573
15574 void
15575 FeatureDone (ChessProgramState *cps, int val)
15576 {
15577   DelayedEventCallback cb = GetDelayedEvent();
15578   if ((cb == InitBackEnd3 && cps == &first) ||
15579       (cb == SettingsMenuIfReady && cps == &second) ||
15580       (cb == LoadEngine) ||
15581       (cb == TwoMachinesEventIfReady)) {
15582     CancelDelayedEvent();
15583     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15584   }
15585   cps->initDone = val;
15586 }
15587
15588 /* Parse feature command from engine */
15589 void
15590 ParseFeatures (char *args, ChessProgramState *cps)
15591 {
15592   char *p = args;
15593   char *q;
15594   int val;
15595   char buf[MSG_SIZ];
15596
15597   for (;;) {
15598     while (*p == ' ') p++;
15599     if (*p == NULLCHAR) return;
15600
15601     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15602     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15603     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15604     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15605     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15606     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15607     if (BoolFeature(&p, "reuse", &val, cps)) {
15608       /* Engine can disable reuse, but can't enable it if user said no */
15609       if (!val) cps->reuse = FALSE;
15610       continue;
15611     }
15612     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15613     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15614       if (gameMode == TwoMachinesPlay) {
15615         DisplayTwoMachinesTitle();
15616       } else {
15617         DisplayTitle("");
15618       }
15619       continue;
15620     }
15621     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15622     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15623     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15624     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15625     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15626     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15627     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15628     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15629     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15630     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15631     if (IntFeature(&p, "done", &val, cps)) {
15632       FeatureDone(cps, val);
15633       continue;
15634     }
15635     /* Added by Tord: */
15636     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15637     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15638     /* End of additions by Tord */
15639
15640     /* [HGM] added features: */
15641     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15642     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15643     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15644     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15645     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15646     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15647     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15648         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15649           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15650             SendToProgram(buf, cps);
15651             continue;
15652         }
15653         if(cps->nrOptions >= MAX_OPTIONS) {
15654             cps->nrOptions--;
15655             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15656             DisplayError(buf, 0);
15657         }
15658         continue;
15659     }
15660     /* End of additions by HGM */
15661
15662     /* unknown feature: complain and skip */
15663     q = p;
15664     while (*q && *q != '=') q++;
15665     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15666     SendToProgram(buf, cps);
15667     p = q;
15668     if (*p == '=') {
15669       p++;
15670       if (*p == '\"') {
15671         p++;
15672         while (*p && *p != '\"') p++;
15673         if (*p == '\"') p++;
15674       } else {
15675         while (*p && *p != ' ') p++;
15676       }
15677     }
15678   }
15679
15680 }
15681
15682 void
15683 PeriodicUpdatesEvent (int newState)
15684 {
15685     if (newState == appData.periodicUpdates)
15686       return;
15687
15688     appData.periodicUpdates=newState;
15689
15690     /* Display type changes, so update it now */
15691 //    DisplayAnalysis();
15692
15693     /* Get the ball rolling again... */
15694     if (newState) {
15695         AnalysisPeriodicEvent(1);
15696         StartAnalysisClock();
15697     }
15698 }
15699
15700 void
15701 PonderNextMoveEvent (int newState)
15702 {
15703     if (newState == appData.ponderNextMove) return;
15704     if (gameMode == EditPosition) EditPositionDone(TRUE);
15705     if (newState) {
15706         SendToProgram("hard\n", &first);
15707         if (gameMode == TwoMachinesPlay) {
15708             SendToProgram("hard\n", &second);
15709         }
15710     } else {
15711         SendToProgram("easy\n", &first);
15712         thinkOutput[0] = NULLCHAR;
15713         if (gameMode == TwoMachinesPlay) {
15714             SendToProgram("easy\n", &second);
15715         }
15716     }
15717     appData.ponderNextMove = newState;
15718 }
15719
15720 void
15721 NewSettingEvent (int option, int *feature, char *command, int value)
15722 {
15723     char buf[MSG_SIZ];
15724
15725     if (gameMode == EditPosition) EditPositionDone(TRUE);
15726     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15727     if(feature == NULL || *feature) SendToProgram(buf, &first);
15728     if (gameMode == TwoMachinesPlay) {
15729         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15730     }
15731 }
15732
15733 void
15734 ShowThinkingEvent ()
15735 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15736 {
15737     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15738     int newState = appData.showThinking
15739         // [HGM] thinking: other features now need thinking output as well
15740         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15741
15742     if (oldState == newState) return;
15743     oldState = newState;
15744     if (gameMode == EditPosition) EditPositionDone(TRUE);
15745     if (oldState) {
15746         SendToProgram("post\n", &first);
15747         if (gameMode == TwoMachinesPlay) {
15748             SendToProgram("post\n", &second);
15749         }
15750     } else {
15751         SendToProgram("nopost\n", &first);
15752         thinkOutput[0] = NULLCHAR;
15753         if (gameMode == TwoMachinesPlay) {
15754             SendToProgram("nopost\n", &second);
15755         }
15756     }
15757 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15758 }
15759
15760 void
15761 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15762 {
15763   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15764   if (pr == NoProc) return;
15765   AskQuestion(title, question, replyPrefix, pr);
15766 }
15767
15768 void
15769 TypeInEvent (char firstChar)
15770 {
15771     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15772         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15773         gameMode == AnalyzeMode || gameMode == EditGame || 
15774         gameMode == EditPosition || gameMode == IcsExamining ||
15775         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15776         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15777                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15778                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15779         gameMode == Training) PopUpMoveDialog(firstChar);
15780 }
15781
15782 void
15783 TypeInDoneEvent (char *move)
15784 {
15785         Board board;
15786         int n, fromX, fromY, toX, toY;
15787         char promoChar;
15788         ChessMove moveType;
15789
15790         // [HGM] FENedit
15791         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15792                 EditPositionPasteFEN(move);
15793                 return;
15794         }
15795         // [HGM] movenum: allow move number to be typed in any mode
15796         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15797           ToNrEvent(2*n-1);
15798           return;
15799         }
15800         // undocumented kludge: allow command-line option to be typed in!
15801         // (potentially fatal, and does not implement the effect of the option.)
15802         // should only be used for options that are values on which future decisions will be made,
15803         // and definitely not on options that would be used during initialization.
15804         if(strstr(move, "!!! -") == move) {
15805             ParseArgsFromString(move+4);
15806             return;
15807         }
15808
15809       if (gameMode != EditGame && currentMove != forwardMostMove && 
15810         gameMode != Training) {
15811         DisplayMoveError(_("Displayed move is not current"));
15812       } else {
15813         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15814           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15815         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15816         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15817           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15818           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15819         } else {
15820           DisplayMoveError(_("Could not parse move"));
15821         }
15822       }
15823 }
15824
15825 void
15826 DisplayMove (int moveNumber)
15827 {
15828     char message[MSG_SIZ];
15829     char res[MSG_SIZ];
15830     char cpThinkOutput[MSG_SIZ];
15831
15832     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15833
15834     if (moveNumber == forwardMostMove - 1 ||
15835         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15836
15837         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15838
15839         if (strchr(cpThinkOutput, '\n')) {
15840             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15841         }
15842     } else {
15843         *cpThinkOutput = NULLCHAR;
15844     }
15845
15846     /* [AS] Hide thinking from human user */
15847     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15848         *cpThinkOutput = NULLCHAR;
15849         if( thinkOutput[0] != NULLCHAR ) {
15850             int i;
15851
15852             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15853                 cpThinkOutput[i] = '.';
15854             }
15855             cpThinkOutput[i] = NULLCHAR;
15856             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15857         }
15858     }
15859
15860     if (moveNumber == forwardMostMove - 1 &&
15861         gameInfo.resultDetails != NULL) {
15862         if (gameInfo.resultDetails[0] == NULLCHAR) {
15863           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15864         } else {
15865           snprintf(res, MSG_SIZ, " {%s} %s",
15866                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15867         }
15868     } else {
15869         res[0] = NULLCHAR;
15870     }
15871
15872     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15873         DisplayMessage(res, cpThinkOutput);
15874     } else {
15875       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15876                 WhiteOnMove(moveNumber) ? " " : ".. ",
15877                 parseList[moveNumber], res);
15878         DisplayMessage(message, cpThinkOutput);
15879     }
15880 }
15881
15882 void
15883 DisplayComment (int moveNumber, char *text)
15884 {
15885     char title[MSG_SIZ];
15886
15887     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15888       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15889     } else {
15890       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15891               WhiteOnMove(moveNumber) ? " " : ".. ",
15892               parseList[moveNumber]);
15893     }
15894     if (text != NULL && (appData.autoDisplayComment || commentUp))
15895         CommentPopUp(title, text);
15896 }
15897
15898 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15899  * might be busy thinking or pondering.  It can be omitted if your
15900  * gnuchess is configured to stop thinking immediately on any user
15901  * input.  However, that gnuchess feature depends on the FIONREAD
15902  * ioctl, which does not work properly on some flavors of Unix.
15903  */
15904 void
15905 Attention (ChessProgramState *cps)
15906 {
15907 #if ATTENTION
15908     if (!cps->useSigint) return;
15909     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15910     switch (gameMode) {
15911       case MachinePlaysWhite:
15912       case MachinePlaysBlack:
15913       case TwoMachinesPlay:
15914       case IcsPlayingWhite:
15915       case IcsPlayingBlack:
15916       case AnalyzeMode:
15917       case AnalyzeFile:
15918         /* Skip if we know it isn't thinking */
15919         if (!cps->maybeThinking) return;
15920         if (appData.debugMode)
15921           fprintf(debugFP, "Interrupting %s\n", cps->which);
15922         InterruptChildProcess(cps->pr);
15923         cps->maybeThinking = FALSE;
15924         break;
15925       default:
15926         break;
15927     }
15928 #endif /*ATTENTION*/
15929 }
15930
15931 int
15932 CheckFlags ()
15933 {
15934     if (whiteTimeRemaining <= 0) {
15935         if (!whiteFlag) {
15936             whiteFlag = TRUE;
15937             if (appData.icsActive) {
15938                 if (appData.autoCallFlag &&
15939                     gameMode == IcsPlayingBlack && !blackFlag) {
15940                   SendToICS(ics_prefix);
15941                   SendToICS("flag\n");
15942                 }
15943             } else {
15944                 if (blackFlag) {
15945                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15946                 } else {
15947                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15948                     if (appData.autoCallFlag) {
15949                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15950                         return TRUE;
15951                     }
15952                 }
15953             }
15954         }
15955     }
15956     if (blackTimeRemaining <= 0) {
15957         if (!blackFlag) {
15958             blackFlag = TRUE;
15959             if (appData.icsActive) {
15960                 if (appData.autoCallFlag &&
15961                     gameMode == IcsPlayingWhite && !whiteFlag) {
15962                   SendToICS(ics_prefix);
15963                   SendToICS("flag\n");
15964                 }
15965             } else {
15966                 if (whiteFlag) {
15967                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15968                 } else {
15969                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15970                     if (appData.autoCallFlag) {
15971                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15972                         return TRUE;
15973                     }
15974                 }
15975             }
15976         }
15977     }
15978     return FALSE;
15979 }
15980
15981 void
15982 CheckTimeControl ()
15983 {
15984     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15985         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15986
15987     /*
15988      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15989      */
15990     if ( !WhiteOnMove(forwardMostMove) ) {
15991         /* White made time control */
15992         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15993         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15994         /* [HGM] time odds: correct new time quota for time odds! */
15995                                             / WhitePlayer()->timeOdds;
15996         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15997     } else {
15998         lastBlack -= blackTimeRemaining;
15999         /* Black made time control */
16000         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16001                                             / WhitePlayer()->other->timeOdds;
16002         lastWhite = whiteTimeRemaining;
16003     }
16004 }
16005
16006 void
16007 DisplayBothClocks ()
16008 {
16009     int wom = gameMode == EditPosition ?
16010       !blackPlaysFirst : WhiteOnMove(currentMove);
16011     DisplayWhiteClock(whiteTimeRemaining, wom);
16012     DisplayBlackClock(blackTimeRemaining, !wom);
16013 }
16014
16015
16016 /* Timekeeping seems to be a portability nightmare.  I think everyone
16017    has ftime(), but I'm really not sure, so I'm including some ifdefs
16018    to use other calls if you don't.  Clocks will be less accurate if
16019    you have neither ftime nor gettimeofday.
16020 */
16021
16022 /* VS 2008 requires the #include outside of the function */
16023 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16024 #include <sys/timeb.h>
16025 #endif
16026
16027 /* Get the current time as a TimeMark */
16028 void
16029 GetTimeMark (TimeMark *tm)
16030 {
16031 #if HAVE_GETTIMEOFDAY
16032
16033     struct timeval timeVal;
16034     struct timezone timeZone;
16035
16036     gettimeofday(&timeVal, &timeZone);
16037     tm->sec = (long) timeVal.tv_sec;
16038     tm->ms = (int) (timeVal.tv_usec / 1000L);
16039
16040 #else /*!HAVE_GETTIMEOFDAY*/
16041 #if HAVE_FTIME
16042
16043 // include <sys/timeb.h> / moved to just above start of function
16044     struct timeb timeB;
16045
16046     ftime(&timeB);
16047     tm->sec = (long) timeB.time;
16048     tm->ms = (int) timeB.millitm;
16049
16050 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16051     tm->sec = (long) time(NULL);
16052     tm->ms = 0;
16053 #endif
16054 #endif
16055 }
16056
16057 /* Return the difference in milliseconds between two
16058    time marks.  We assume the difference will fit in a long!
16059 */
16060 long
16061 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16062 {
16063     return 1000L*(tm2->sec - tm1->sec) +
16064            (long) (tm2->ms - tm1->ms);
16065 }
16066
16067
16068 /*
16069  * Code to manage the game clocks.
16070  *
16071  * In tournament play, black starts the clock and then white makes a move.
16072  * We give the human user a slight advantage if he is playing white---the
16073  * clocks don't run until he makes his first move, so it takes zero time.
16074  * Also, we don't account for network lag, so we could get out of sync
16075  * with GNU Chess's clock -- but then, referees are always right.
16076  */
16077
16078 static TimeMark tickStartTM;
16079 static long intendedTickLength;
16080
16081 long
16082 NextTickLength (long timeRemaining)
16083 {
16084     long nominalTickLength, nextTickLength;
16085
16086     if (timeRemaining > 0L && timeRemaining <= 10000L)
16087       nominalTickLength = 100L;
16088     else
16089       nominalTickLength = 1000L;
16090     nextTickLength = timeRemaining % nominalTickLength;
16091     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16092
16093     return nextTickLength;
16094 }
16095
16096 /* Adjust clock one minute up or down */
16097 void
16098 AdjustClock (Boolean which, int dir)
16099 {
16100     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16101     if(which) blackTimeRemaining += 60000*dir;
16102     else      whiteTimeRemaining += 60000*dir;
16103     DisplayBothClocks();
16104     adjustedClock = TRUE;
16105 }
16106
16107 /* Stop clocks and reset to a fresh time control */
16108 void
16109 ResetClocks ()
16110 {
16111     (void) StopClockTimer();
16112     if (appData.icsActive) {
16113         whiteTimeRemaining = blackTimeRemaining = 0;
16114     } else if (searchTime) {
16115         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16116         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16117     } else { /* [HGM] correct new time quote for time odds */
16118         whiteTC = blackTC = fullTimeControlString;
16119         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16120         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16121     }
16122     if (whiteFlag || blackFlag) {
16123         DisplayTitle("");
16124         whiteFlag = blackFlag = FALSE;
16125     }
16126     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16127     DisplayBothClocks();
16128     adjustedClock = FALSE;
16129 }
16130
16131 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16132
16133 /* Decrement running clock by amount of time that has passed */
16134 void
16135 DecrementClocks ()
16136 {
16137     long timeRemaining;
16138     long lastTickLength, fudge;
16139     TimeMark now;
16140
16141     if (!appData.clockMode) return;
16142     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16143
16144     GetTimeMark(&now);
16145
16146     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16147
16148     /* Fudge if we woke up a little too soon */
16149     fudge = intendedTickLength - lastTickLength;
16150     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16151
16152     if (WhiteOnMove(forwardMostMove)) {
16153         if(whiteNPS >= 0) lastTickLength = 0;
16154         timeRemaining = whiteTimeRemaining -= lastTickLength;
16155         if(timeRemaining < 0 && !appData.icsActive) {
16156             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16157             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16158                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16159                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16160             }
16161         }
16162         DisplayWhiteClock(whiteTimeRemaining - fudge,
16163                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16164     } else {
16165         if(blackNPS >= 0) lastTickLength = 0;
16166         timeRemaining = blackTimeRemaining -= lastTickLength;
16167         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16168             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16169             if(suddenDeath) {
16170                 blackStartMove = forwardMostMove;
16171                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16172             }
16173         }
16174         DisplayBlackClock(blackTimeRemaining - fudge,
16175                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16176     }
16177     if (CheckFlags()) return;
16178
16179     tickStartTM = now;
16180     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16181     StartClockTimer(intendedTickLength);
16182
16183     /* if the time remaining has fallen below the alarm threshold, sound the
16184      * alarm. if the alarm has sounded and (due to a takeback or time control
16185      * with increment) the time remaining has increased to a level above the
16186      * threshold, reset the alarm so it can sound again.
16187      */
16188
16189     if (appData.icsActive && appData.icsAlarm) {
16190
16191         /* make sure we are dealing with the user's clock */
16192         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16193                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16194            )) return;
16195
16196         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16197             alarmSounded = FALSE;
16198         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16199             PlayAlarmSound();
16200             alarmSounded = TRUE;
16201         }
16202     }
16203 }
16204
16205
16206 /* A player has just moved, so stop the previously running
16207    clock and (if in clock mode) start the other one.
16208    We redisplay both clocks in case we're in ICS mode, because
16209    ICS gives us an update to both clocks after every move.
16210    Note that this routine is called *after* forwardMostMove
16211    is updated, so the last fractional tick must be subtracted
16212    from the color that is *not* on move now.
16213 */
16214 void
16215 SwitchClocks (int newMoveNr)
16216 {
16217     long lastTickLength;
16218     TimeMark now;
16219     int flagged = FALSE;
16220
16221     GetTimeMark(&now);
16222
16223     if (StopClockTimer() && appData.clockMode) {
16224         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16225         if (!WhiteOnMove(forwardMostMove)) {
16226             if(blackNPS >= 0) lastTickLength = 0;
16227             blackTimeRemaining -= lastTickLength;
16228            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16229 //         if(pvInfoList[forwardMostMove].time == -1)
16230                  pvInfoList[forwardMostMove].time =               // use GUI time
16231                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16232         } else {
16233            if(whiteNPS >= 0) lastTickLength = 0;
16234            whiteTimeRemaining -= lastTickLength;
16235            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16236 //         if(pvInfoList[forwardMostMove].time == -1)
16237                  pvInfoList[forwardMostMove].time =
16238                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16239         }
16240         flagged = CheckFlags();
16241     }
16242     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16243     CheckTimeControl();
16244
16245     if (flagged || !appData.clockMode) return;
16246
16247     switch (gameMode) {
16248       case MachinePlaysBlack:
16249       case MachinePlaysWhite:
16250       case BeginningOfGame:
16251         if (pausing) return;
16252         break;
16253
16254       case EditGame:
16255       case PlayFromGameFile:
16256       case IcsExamining:
16257         return;
16258
16259       default:
16260         break;
16261     }
16262
16263     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16264         if(WhiteOnMove(forwardMostMove))
16265              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16266         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16267     }
16268
16269     tickStartTM = now;
16270     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16271       whiteTimeRemaining : blackTimeRemaining);
16272     StartClockTimer(intendedTickLength);
16273 }
16274
16275
16276 /* Stop both clocks */
16277 void
16278 StopClocks ()
16279 {
16280     long lastTickLength;
16281     TimeMark now;
16282
16283     if (!StopClockTimer()) return;
16284     if (!appData.clockMode) return;
16285
16286     GetTimeMark(&now);
16287
16288     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16289     if (WhiteOnMove(forwardMostMove)) {
16290         if(whiteNPS >= 0) lastTickLength = 0;
16291         whiteTimeRemaining -= lastTickLength;
16292         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16293     } else {
16294         if(blackNPS >= 0) lastTickLength = 0;
16295         blackTimeRemaining -= lastTickLength;
16296         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16297     }
16298     CheckFlags();
16299 }
16300
16301 /* Start clock of player on move.  Time may have been reset, so
16302    if clock is already running, stop and restart it. */
16303 void
16304 StartClocks ()
16305 {
16306     (void) StopClockTimer(); /* in case it was running already */
16307     DisplayBothClocks();
16308     if (CheckFlags()) return;
16309
16310     if (!appData.clockMode) return;
16311     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16312
16313     GetTimeMark(&tickStartTM);
16314     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16315       whiteTimeRemaining : blackTimeRemaining);
16316
16317    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16318     whiteNPS = blackNPS = -1;
16319     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16320        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16321         whiteNPS = first.nps;
16322     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16323        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16324         blackNPS = first.nps;
16325     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16326         whiteNPS = second.nps;
16327     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16328         blackNPS = second.nps;
16329     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16330
16331     StartClockTimer(intendedTickLength);
16332 }
16333
16334 char *
16335 TimeString (long ms)
16336 {
16337     long second, minute, hour, day;
16338     char *sign = "";
16339     static char buf[32];
16340
16341     if (ms > 0 && ms <= 9900) {
16342       /* convert milliseconds to tenths, rounding up */
16343       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16344
16345       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16346       return buf;
16347     }
16348
16349     /* convert milliseconds to seconds, rounding up */
16350     /* use floating point to avoid strangeness of integer division
16351        with negative dividends on many machines */
16352     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16353
16354     if (second < 0) {
16355         sign = "-";
16356         second = -second;
16357     }
16358
16359     day = second / (60 * 60 * 24);
16360     second = second % (60 * 60 * 24);
16361     hour = second / (60 * 60);
16362     second = second % (60 * 60);
16363     minute = second / 60;
16364     second = second % 60;
16365
16366     if (day > 0)
16367       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16368               sign, day, hour, minute, second);
16369     else if (hour > 0)
16370       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16371     else
16372       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16373
16374     return buf;
16375 }
16376
16377
16378 /*
16379  * This is necessary because some C libraries aren't ANSI C compliant yet.
16380  */
16381 char *
16382 StrStr (char *string, char *match)
16383 {
16384     int i, length;
16385
16386     length = strlen(match);
16387
16388     for (i = strlen(string) - length; i >= 0; i--, string++)
16389       if (!strncmp(match, string, length))
16390         return string;
16391
16392     return NULL;
16393 }
16394
16395 char *
16396 StrCaseStr (char *string, char *match)
16397 {
16398     int i, j, length;
16399
16400     length = strlen(match);
16401
16402     for (i = strlen(string) - length; i >= 0; i--, string++) {
16403         for (j = 0; j < length; j++) {
16404             if (ToLower(match[j]) != ToLower(string[j]))
16405               break;
16406         }
16407         if (j == length) return string;
16408     }
16409
16410     return NULL;
16411 }
16412
16413 #ifndef _amigados
16414 int
16415 StrCaseCmp (char *s1, char *s2)
16416 {
16417     char c1, c2;
16418
16419     for (;;) {
16420         c1 = ToLower(*s1++);
16421         c2 = ToLower(*s2++);
16422         if (c1 > c2) return 1;
16423         if (c1 < c2) return -1;
16424         if (c1 == NULLCHAR) return 0;
16425     }
16426 }
16427
16428
16429 int
16430 ToLower (int c)
16431 {
16432     return isupper(c) ? tolower(c) : c;
16433 }
16434
16435
16436 int
16437 ToUpper (int c)
16438 {
16439     return islower(c) ? toupper(c) : c;
16440 }
16441 #endif /* !_amigados    */
16442
16443 char *
16444 StrSave (char *s)
16445 {
16446   char *ret;
16447
16448   if ((ret = (char *) malloc(strlen(s) + 1)))
16449     {
16450       safeStrCpy(ret, s, strlen(s)+1);
16451     }
16452   return ret;
16453 }
16454
16455 char *
16456 StrSavePtr (char *s, char **savePtr)
16457 {
16458     if (*savePtr) {
16459         free(*savePtr);
16460     }
16461     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16462       safeStrCpy(*savePtr, s, strlen(s)+1);
16463     }
16464     return(*savePtr);
16465 }
16466
16467 char *
16468 PGNDate ()
16469 {
16470     time_t clock;
16471     struct tm *tm;
16472     char buf[MSG_SIZ];
16473
16474     clock = time((time_t *)NULL);
16475     tm = localtime(&clock);
16476     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16477             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16478     return StrSave(buf);
16479 }
16480
16481
16482 char *
16483 PositionToFEN (int move, char *overrideCastling)
16484 {
16485     int i, j, fromX, fromY, toX, toY;
16486     int whiteToPlay;
16487     char buf[MSG_SIZ];
16488     char *p, *q;
16489     int emptycount;
16490     ChessSquare piece;
16491
16492     whiteToPlay = (gameMode == EditPosition) ?
16493       !blackPlaysFirst : (move % 2 == 0);
16494     p = buf;
16495
16496     /* Piece placement data */
16497     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16498         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16499         emptycount = 0;
16500         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16501             if (boards[move][i][j] == EmptySquare) {
16502                 emptycount++;
16503             } else { ChessSquare piece = boards[move][i][j];
16504                 if (emptycount > 0) {
16505                     if(emptycount<10) /* [HGM] can be >= 10 */
16506                         *p++ = '0' + emptycount;
16507                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16508                     emptycount = 0;
16509                 }
16510                 if(PieceToChar(piece) == '+') {
16511                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16512                     *p++ = '+';
16513                     piece = (ChessSquare)(DEMOTED piece);
16514                 }
16515                 *p++ = PieceToChar(piece);
16516                 if(p[-1] == '~') {
16517                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16518                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16519                     *p++ = '~';
16520                 }
16521             }
16522         }
16523         if (emptycount > 0) {
16524             if(emptycount<10) /* [HGM] can be >= 10 */
16525                 *p++ = '0' + emptycount;
16526             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16527             emptycount = 0;
16528         }
16529         *p++ = '/';
16530     }
16531     *(p - 1) = ' ';
16532
16533     /* [HGM] print Crazyhouse or Shogi holdings */
16534     if( gameInfo.holdingsWidth ) {
16535         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16536         q = p;
16537         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16538             piece = boards[move][i][BOARD_WIDTH-1];
16539             if( piece != EmptySquare )
16540               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16541                   *p++ = PieceToChar(piece);
16542         }
16543         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16544             piece = boards[move][BOARD_HEIGHT-i-1][0];
16545             if( piece != EmptySquare )
16546               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16547                   *p++ = PieceToChar(piece);
16548         }
16549
16550         if( q == p ) *p++ = '-';
16551         *p++ = ']';
16552         *p++ = ' ';
16553     }
16554
16555     /* Active color */
16556     *p++ = whiteToPlay ? 'w' : 'b';
16557     *p++ = ' ';
16558
16559   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16560     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16561   } else {
16562   if(nrCastlingRights) {
16563      q = p;
16564      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16565        /* [HGM] write directly from rights */
16566            if(boards[move][CASTLING][2] != NoRights &&
16567               boards[move][CASTLING][0] != NoRights   )
16568                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16569            if(boards[move][CASTLING][2] != NoRights &&
16570               boards[move][CASTLING][1] != NoRights   )
16571                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16572            if(boards[move][CASTLING][5] != NoRights &&
16573               boards[move][CASTLING][3] != NoRights   )
16574                 *p++ = boards[move][CASTLING][3] + AAA;
16575            if(boards[move][CASTLING][5] != NoRights &&
16576               boards[move][CASTLING][4] != NoRights   )
16577                 *p++ = boards[move][CASTLING][4] + AAA;
16578      } else {
16579
16580         /* [HGM] write true castling rights */
16581         if( nrCastlingRights == 6 ) {
16582             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16583                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16584             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16585                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16586             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16587                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16588             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16589                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16590         }
16591      }
16592      if (q == p) *p++ = '-'; /* No castling rights */
16593      *p++ = ' ';
16594   }
16595
16596   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16597      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16598     /* En passant target square */
16599     if (move > backwardMostMove) {
16600         fromX = moveList[move - 1][0] - AAA;
16601         fromY = moveList[move - 1][1] - ONE;
16602         toX = moveList[move - 1][2] - AAA;
16603         toY = moveList[move - 1][3] - ONE;
16604         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16605             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16606             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16607             fromX == toX) {
16608             /* 2-square pawn move just happened */
16609             *p++ = toX + AAA;
16610             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16611         } else {
16612             *p++ = '-';
16613         }
16614     } else if(move == backwardMostMove) {
16615         // [HGM] perhaps we should always do it like this, and forget the above?
16616         if((signed char)boards[move][EP_STATUS] >= 0) {
16617             *p++ = boards[move][EP_STATUS] + AAA;
16618             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16619         } else {
16620             *p++ = '-';
16621         }
16622     } else {
16623         *p++ = '-';
16624     }
16625     *p++ = ' ';
16626   }
16627   }
16628
16629     /* [HGM] find reversible plies */
16630     {   int i = 0, j=move;
16631
16632         if (appData.debugMode) { int k;
16633             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16634             for(k=backwardMostMove; k<=forwardMostMove; k++)
16635                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16636
16637         }
16638
16639         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16640         if( j == backwardMostMove ) i += initialRulePlies;
16641         sprintf(p, "%d ", i);
16642         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16643     }
16644     /* Fullmove number */
16645     sprintf(p, "%d", (move / 2) + 1);
16646
16647     return StrSave(buf);
16648 }
16649
16650 Boolean
16651 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16652 {
16653     int i, j;
16654     char *p, c;
16655     int emptycount;
16656     ChessSquare piece;
16657
16658     p = fen;
16659
16660     /* [HGM] by default clear Crazyhouse holdings, if present */
16661     if(gameInfo.holdingsWidth) {
16662        for(i=0; i<BOARD_HEIGHT; i++) {
16663            board[i][0]             = EmptySquare; /* black holdings */
16664            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16665            board[i][1]             = (ChessSquare) 0; /* black counts */
16666            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16667        }
16668     }
16669
16670     /* Piece placement data */
16671     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16672         j = 0;
16673         for (;;) {
16674             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16675                 if (*p == '/') p++;
16676                 emptycount = gameInfo.boardWidth - j;
16677                 while (emptycount--)
16678                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16679                 break;
16680 #if(BOARD_FILES >= 10)
16681             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16682                 p++; emptycount=10;
16683                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16684                 while (emptycount--)
16685                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16686 #endif
16687             } else if (isdigit(*p)) {
16688                 emptycount = *p++ - '0';
16689                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16690                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16691                 while (emptycount--)
16692                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16693             } else if (*p == '+' || isalpha(*p)) {
16694                 if (j >= gameInfo.boardWidth) return FALSE;
16695                 if(*p=='+') {
16696                     piece = CharToPiece(*++p);
16697                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16698                     piece = (ChessSquare) (PROMOTED piece ); p++;
16699                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16700                 } else piece = CharToPiece(*p++);
16701
16702                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16703                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16704                     piece = (ChessSquare) (PROMOTED piece);
16705                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16706                     p++;
16707                 }
16708                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16709             } else {
16710                 return FALSE;
16711             }
16712         }
16713     }
16714     while (*p == '/' || *p == ' ') p++;
16715
16716     /* [HGM] look for Crazyhouse holdings here */
16717     while(*p==' ') p++;
16718     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16719         if(*p == '[') p++;
16720         if(*p == '-' ) p++; /* empty holdings */ else {
16721             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16722             /* if we would allow FEN reading to set board size, we would   */
16723             /* have to add holdings and shift the board read so far here   */
16724             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16725                 p++;
16726                 if((int) piece >= (int) BlackPawn ) {
16727                     i = (int)piece - (int)BlackPawn;
16728                     i = PieceToNumber((ChessSquare)i);
16729                     if( i >= gameInfo.holdingsSize ) return FALSE;
16730                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16731                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16732                 } else {
16733                     i = (int)piece - (int)WhitePawn;
16734                     i = PieceToNumber((ChessSquare)i);
16735                     if( i >= gameInfo.holdingsSize ) return FALSE;
16736                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16737                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16738                 }
16739             }
16740         }
16741         if(*p == ']') p++;
16742     }
16743
16744     while(*p == ' ') p++;
16745
16746     /* Active color */
16747     c = *p++;
16748     if(appData.colorNickNames) {
16749       if( c == appData.colorNickNames[0] ) c = 'w'; else
16750       if( c == appData.colorNickNames[1] ) c = 'b';
16751     }
16752     switch (c) {
16753       case 'w':
16754         *blackPlaysFirst = FALSE;
16755         break;
16756       case 'b':
16757         *blackPlaysFirst = TRUE;
16758         break;
16759       default:
16760         return FALSE;
16761     }
16762
16763     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16764     /* return the extra info in global variiables             */
16765
16766     /* set defaults in case FEN is incomplete */
16767     board[EP_STATUS] = EP_UNKNOWN;
16768     for(i=0; i<nrCastlingRights; i++ ) {
16769         board[CASTLING][i] =
16770             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16771     }   /* assume possible unless obviously impossible */
16772     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16773     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16774     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16775                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16776     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16777     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16778     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16779                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16780     FENrulePlies = 0;
16781
16782     while(*p==' ') p++;
16783     if(nrCastlingRights) {
16784       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16785           /* castling indicator present, so default becomes no castlings */
16786           for(i=0; i<nrCastlingRights; i++ ) {
16787                  board[CASTLING][i] = NoRights;
16788           }
16789       }
16790       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16791              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16792              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16793              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16794         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16795
16796         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16797             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16798             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16799         }
16800         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16801             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16802         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16803                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16804         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16805                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16806         switch(c) {
16807           case'K':
16808               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16809               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16810               board[CASTLING][2] = whiteKingFile;
16811               break;
16812           case'Q':
16813               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16814               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16815               board[CASTLING][2] = whiteKingFile;
16816               break;
16817           case'k':
16818               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16819               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16820               board[CASTLING][5] = blackKingFile;
16821               break;
16822           case'q':
16823               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16824               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16825               board[CASTLING][5] = blackKingFile;
16826           case '-':
16827               break;
16828           default: /* FRC castlings */
16829               if(c >= 'a') { /* black rights */
16830                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16831                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16832                   if(i == BOARD_RGHT) break;
16833                   board[CASTLING][5] = i;
16834                   c -= AAA;
16835                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16836                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16837                   if(c > i)
16838                       board[CASTLING][3] = c;
16839                   else
16840                       board[CASTLING][4] = c;
16841               } else { /* white rights */
16842                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16843                     if(board[0][i] == WhiteKing) break;
16844                   if(i == BOARD_RGHT) break;
16845                   board[CASTLING][2] = i;
16846                   c -= AAA - 'a' + 'A';
16847                   if(board[0][c] >= WhiteKing) break;
16848                   if(c > i)
16849                       board[CASTLING][0] = c;
16850                   else
16851                       board[CASTLING][1] = c;
16852               }
16853         }
16854       }
16855       for(i=0; i<nrCastlingRights; i++)
16856         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16857     if (appData.debugMode) {
16858         fprintf(debugFP, "FEN castling rights:");
16859         for(i=0; i<nrCastlingRights; i++)
16860         fprintf(debugFP, " %d", board[CASTLING][i]);
16861         fprintf(debugFP, "\n");
16862     }
16863
16864       while(*p==' ') p++;
16865     }
16866
16867     /* read e.p. field in games that know e.p. capture */
16868     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16869        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16870       if(*p=='-') {
16871         p++; board[EP_STATUS] = EP_NONE;
16872       } else {
16873          char c = *p++ - AAA;
16874
16875          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16876          if(*p >= '0' && *p <='9') p++;
16877          board[EP_STATUS] = c;
16878       }
16879     }
16880
16881
16882     if(sscanf(p, "%d", &i) == 1) {
16883         FENrulePlies = i; /* 50-move ply counter */
16884         /* (The move number is still ignored)    */
16885     }
16886
16887     return TRUE;
16888 }
16889
16890 void
16891 EditPositionPasteFEN (char *fen)
16892 {
16893   if (fen != NULL) {
16894     Board initial_position;
16895
16896     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16897       DisplayError(_("Bad FEN position in clipboard"), 0);
16898       return ;
16899     } else {
16900       int savedBlackPlaysFirst = blackPlaysFirst;
16901       EditPositionEvent();
16902       blackPlaysFirst = savedBlackPlaysFirst;
16903       CopyBoard(boards[0], initial_position);
16904       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16905       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16906       DisplayBothClocks();
16907       DrawPosition(FALSE, boards[currentMove]);
16908     }
16909   }
16910 }
16911
16912 static char cseq[12] = "\\   ";
16913
16914 Boolean
16915 set_cont_sequence (char *new_seq)
16916 {
16917     int len;
16918     Boolean ret;
16919
16920     // handle bad attempts to set the sequence
16921         if (!new_seq)
16922                 return 0; // acceptable error - no debug
16923
16924     len = strlen(new_seq);
16925     ret = (len > 0) && (len < sizeof(cseq));
16926     if (ret)
16927       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16928     else if (appData.debugMode)
16929       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16930     return ret;
16931 }
16932
16933 /*
16934     reformat a source message so words don't cross the width boundary.  internal
16935     newlines are not removed.  returns the wrapped size (no null character unless
16936     included in source message).  If dest is NULL, only calculate the size required
16937     for the dest buffer.  lp argument indicats line position upon entry, and it's
16938     passed back upon exit.
16939 */
16940 int
16941 wrap (char *dest, char *src, int count, int width, int *lp)
16942 {
16943     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16944
16945     cseq_len = strlen(cseq);
16946     old_line = line = *lp;
16947     ansi = len = clen = 0;
16948
16949     for (i=0; i < count; i++)
16950     {
16951         if (src[i] == '\033')
16952             ansi = 1;
16953
16954         // if we hit the width, back up
16955         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16956         {
16957             // store i & len in case the word is too long
16958             old_i = i, old_len = len;
16959
16960             // find the end of the last word
16961             while (i && src[i] != ' ' && src[i] != '\n')
16962             {
16963                 i--;
16964                 len--;
16965             }
16966
16967             // word too long?  restore i & len before splitting it
16968             if ((old_i-i+clen) >= width)
16969             {
16970                 i = old_i;
16971                 len = old_len;
16972             }
16973
16974             // extra space?
16975             if (i && src[i-1] == ' ')
16976                 len--;
16977
16978             if (src[i] != ' ' && src[i] != '\n')
16979             {
16980                 i--;
16981                 if (len)
16982                     len--;
16983             }
16984
16985             // now append the newline and continuation sequence
16986             if (dest)
16987                 dest[len] = '\n';
16988             len++;
16989             if (dest)
16990                 strncpy(dest+len, cseq, cseq_len);
16991             len += cseq_len;
16992             line = cseq_len;
16993             clen = cseq_len;
16994             continue;
16995         }
16996
16997         if (dest)
16998             dest[len] = src[i];
16999         len++;
17000         if (!ansi)
17001             line++;
17002         if (src[i] == '\n')
17003             line = 0;
17004         if (src[i] == 'm')
17005             ansi = 0;
17006     }
17007     if (dest && appData.debugMode)
17008     {
17009         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17010             count, width, line, len, *lp);
17011         show_bytes(debugFP, src, count);
17012         fprintf(debugFP, "\ndest: ");
17013         show_bytes(debugFP, dest, len);
17014         fprintf(debugFP, "\n");
17015     }
17016     *lp = dest ? line : old_line;
17017
17018     return len;
17019 }
17020
17021 // [HGM] vari: routines for shelving variations
17022 Boolean modeRestore = FALSE;
17023
17024 void
17025 PushInner (int firstMove, int lastMove)
17026 {
17027         int i, j, nrMoves = lastMove - firstMove;
17028
17029         // push current tail of game on stack
17030         savedResult[storedGames] = gameInfo.result;
17031         savedDetails[storedGames] = gameInfo.resultDetails;
17032         gameInfo.resultDetails = NULL;
17033         savedFirst[storedGames] = firstMove;
17034         savedLast [storedGames] = lastMove;
17035         savedFramePtr[storedGames] = framePtr;
17036         framePtr -= nrMoves; // reserve space for the boards
17037         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17038             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17039             for(j=0; j<MOVE_LEN; j++)
17040                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17041             for(j=0; j<2*MOVE_LEN; j++)
17042                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17043             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17044             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17045             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17046             pvInfoList[firstMove+i-1].depth = 0;
17047             commentList[framePtr+i] = commentList[firstMove+i];
17048             commentList[firstMove+i] = NULL;
17049         }
17050
17051         storedGames++;
17052         forwardMostMove = firstMove; // truncate game so we can start variation
17053 }
17054
17055 void
17056 PushTail (int firstMove, int lastMove)
17057 {
17058         if(appData.icsActive) { // only in local mode
17059                 forwardMostMove = currentMove; // mimic old ICS behavior
17060                 return;
17061         }
17062         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17063
17064         PushInner(firstMove, lastMove);
17065         if(storedGames == 1) GreyRevert(FALSE);
17066         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17067 }
17068
17069 void
17070 PopInner (Boolean annotate)
17071 {
17072         int i, j, nrMoves;
17073         char buf[8000], moveBuf[20];
17074
17075         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17076         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17077         nrMoves = savedLast[storedGames] - currentMove;
17078         if(annotate) {
17079                 int cnt = 10;
17080                 if(!WhiteOnMove(currentMove))
17081                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17082                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17083                 for(i=currentMove; i<forwardMostMove; i++) {
17084                         if(WhiteOnMove(i))
17085                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17086                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17087                         strcat(buf, moveBuf);
17088                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17089                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17090                 }
17091                 strcat(buf, ")");
17092         }
17093         for(i=1; i<=nrMoves; i++) { // copy last variation back
17094             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17095             for(j=0; j<MOVE_LEN; j++)
17096                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17097             for(j=0; j<2*MOVE_LEN; j++)
17098                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17099             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17100             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17101             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17102             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17103             commentList[currentMove+i] = commentList[framePtr+i];
17104             commentList[framePtr+i] = NULL;
17105         }
17106         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17107         framePtr = savedFramePtr[storedGames];
17108         gameInfo.result = savedResult[storedGames];
17109         if(gameInfo.resultDetails != NULL) {
17110             free(gameInfo.resultDetails);
17111       }
17112         gameInfo.resultDetails = savedDetails[storedGames];
17113         forwardMostMove = currentMove + nrMoves;
17114 }
17115
17116 Boolean
17117 PopTail (Boolean annotate)
17118 {
17119         if(appData.icsActive) return FALSE; // only in local mode
17120         if(!storedGames) return FALSE; // sanity
17121         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17122
17123         PopInner(annotate);
17124         if(currentMove < forwardMostMove) ForwardEvent(); else
17125         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17126
17127         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17128         return TRUE;
17129 }
17130
17131 void
17132 CleanupTail ()
17133 {       // remove all shelved variations
17134         int i;
17135         for(i=0; i<storedGames; i++) {
17136             if(savedDetails[i])
17137                 free(savedDetails[i]);
17138             savedDetails[i] = NULL;
17139         }
17140         for(i=framePtr; i<MAX_MOVES; i++) {
17141                 if(commentList[i]) free(commentList[i]);
17142                 commentList[i] = NULL;
17143         }
17144         framePtr = MAX_MOVES-1;
17145         storedGames = 0;
17146 }
17147
17148 void
17149 LoadVariation (int index, char *text)
17150 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17151         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17152         int level = 0, move;
17153
17154         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17155         // first find outermost bracketing variation
17156         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17157             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17158                 if(*p == '{') wait = '}'; else
17159                 if(*p == '[') wait = ']'; else
17160                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17161                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17162             }
17163             if(*p == wait) wait = NULLCHAR; // closing ]} found
17164             p++;
17165         }
17166         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17167         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17168         end[1] = NULLCHAR; // clip off comment beyond variation
17169         ToNrEvent(currentMove-1);
17170         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17171         // kludge: use ParsePV() to append variation to game
17172         move = currentMove;
17173         ParsePV(start, TRUE, TRUE);
17174         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17175         ClearPremoveHighlights();
17176         CommentPopDown();
17177         ToNrEvent(currentMove+1);
17178 }
17179