4ceb598a7e22b07315d54b463578094883218904
[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
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
924     } else appData.directory[i] = ".";
925     if(params[0]) {
926         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
927         snprintf(command, MSG_SIZ, "%s %s", p, params);
928         p = command;
929     }
930     appData.chessProgram[i] = strdup(p);
931     appData.isUCI[i] = isUCI;
932     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
933     appData.hasOwnBookUCI[i] = hasBook;
934     if(!nickName[0]) useNick = FALSE;
935     if(useNick) ASSIGN(appData.pgnName[i], nickName);
936     if(addToList) {
937         int len;
938         char quote;
939         q = firstChessProgramNames;
940         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
941         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
943                         quote, p, quote, appData.directory[i], 
944                         useNick ? " -fn \"" : "",
945                         useNick ? nickName : "",
946                         useNick ? "\"" : "",
947                         v1 ? " -firstProtocolVersion 1" : "",
948                         hasBook ? "" : " -fNoOwnBookUCI",
949                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
950                         storeVariant ? " -variant " : "",
951                         storeVariant ? VariantName(gameInfo.variant) : "");
952         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
953         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
954         if(q)   free(q);
955         FloatToFront(&appData.recentEngineList, buf);
956     }
957     ReplaceEngine(cps, i);
958 }
959
960 void
961 InitTimeControls ()
962 {
963     int matched, min, sec;
964     /*
965      * Parse timeControl resource
966      */
967     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
968                           appData.movesPerSession)) {
969         char buf[MSG_SIZ];
970         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
971         DisplayFatalError(buf, 0, 2);
972     }
973
974     /*
975      * Parse searchTime resource
976      */
977     if (*appData.searchTime != NULLCHAR) {
978         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
979         if (matched == 1) {
980             searchTime = min * 60;
981         } else if (matched == 2) {
982             searchTime = min * 60 + sec;
983         } else {
984             char buf[MSG_SIZ];
985             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
986             DisplayFatalError(buf, 0, 2);
987         }
988     }
989 }
990
991 void
992 InitBackEnd1 ()
993 {
994
995     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
996     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
997
998     GetTimeMark(&programStartTime);
999     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1000     appData.seedBase = random() + (random()<<15);
1001     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1002
1003     ClearProgramStats();
1004     programStats.ok_to_send = 1;
1005     programStats.seen_stat = 0;
1006
1007     /*
1008      * Initialize game list
1009      */
1010     ListNew(&gameList);
1011
1012
1013     /*
1014      * Internet chess server status
1015      */
1016     if (appData.icsActive) {
1017         appData.matchMode = FALSE;
1018         appData.matchGames = 0;
1019 #if ZIPPY
1020         appData.noChessProgram = !appData.zippyPlay;
1021 #else
1022         appData.zippyPlay = FALSE;
1023         appData.zippyTalk = FALSE;
1024         appData.noChessProgram = TRUE;
1025 #endif
1026         if (*appData.icsHelper != NULLCHAR) {
1027             appData.useTelnet = TRUE;
1028             appData.telnetProgram = appData.icsHelper;
1029         }
1030     } else {
1031         appData.zippyTalk = appData.zippyPlay = FALSE;
1032     }
1033
1034     /* [AS] Initialize pv info list [HGM] and game state */
1035     {
1036         int i, j;
1037
1038         for( i=0; i<=framePtr; i++ ) {
1039             pvInfoList[i].depth = -1;
1040             boards[i][EP_STATUS] = EP_NONE;
1041             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1042         }
1043     }
1044
1045     InitTimeControls();
1046
1047     /* [AS] Adjudication threshold */
1048     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1049
1050     InitEngine(&first, 0);
1051     InitEngine(&second, 1);
1052     CommonEngineInit();
1053
1054     pairing.which = "pairing"; // pairing engine
1055     pairing.pr = NoProc;
1056     pairing.isr = NULL;
1057     pairing.program = appData.pairingEngine;
1058     pairing.host = "localhost";
1059     pairing.dir = ".";
1060
1061     if (appData.icsActive) {
1062         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1063     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1064         appData.clockMode = FALSE;
1065         first.sendTime = second.sendTime = 0;
1066     }
1067
1068 #if ZIPPY
1069     /* Override some settings from environment variables, for backward
1070        compatibility.  Unfortunately it's not feasible to have the env
1071        vars just set defaults, at least in xboard.  Ugh.
1072     */
1073     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1074       ZippyInit();
1075     }
1076 #endif
1077
1078     if (!appData.icsActive) {
1079       char buf[MSG_SIZ];
1080       int len;
1081
1082       /* Check for variants that are supported only in ICS mode,
1083          or not at all.  Some that are accepted here nevertheless
1084          have bugs; see comments below.
1085       */
1086       VariantClass variant = StringToVariant(appData.variant);
1087       switch (variant) {
1088       case VariantBughouse:     /* need four players and two boards */
1089       case VariantKriegspiel:   /* need to hide pieces and move details */
1090         /* case VariantFischeRandom: (Fabien: moved below) */
1091         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1092         if( (len >= MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantUnknown:
1099       case VariantLoadable:
1100       case Variant29:
1101       case Variant30:
1102       case Variant31:
1103       case Variant32:
1104       case Variant33:
1105       case Variant34:
1106       case Variant35:
1107       case Variant36:
1108       default:
1109         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1110         if( (len >= MSG_SIZ) && appData.debugMode )
1111           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112
1113         DisplayFatalError(buf, 0, 2);
1114         return;
1115
1116       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1117       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1118       case VariantGothic:     /* [HGM] should work */
1119       case VariantCapablanca: /* [HGM] should work */
1120       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1121       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1122       case VariantKnightmate: /* [HGM] should work */
1123       case VariantCylinder:   /* [HGM] untested */
1124       case VariantFalcon:     /* [HGM] untested */
1125       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1126                                  offboard interposition not understood */
1127       case VariantNormal:     /* definitely works! */
1128       case VariantWildCastle: /* pieces not automatically shuffled */
1129       case VariantNoCastle:   /* pieces not automatically shuffled */
1130       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1131       case VariantLosers:     /* should work except for win condition,
1132                                  and doesn't know captures are mandatory */
1133       case VariantSuicide:    /* should work except for win condition,
1134                                  and doesn't know captures are mandatory */
1135       case VariantGiveaway:   /* should work except for win condition,
1136                                  and doesn't know captures are mandatory */
1137       case VariantTwoKings:   /* should work */
1138       case VariantAtomic:     /* should work except for win condition */
1139       case Variant3Check:     /* should work except for win condition */
1140       case VariantShatranj:   /* should work except for all win conditions */
1141       case VariantMakruk:     /* should work except for draw countdown */
1142       case VariantBerolina:   /* might work if TestLegality is off */
1143       case VariantCapaRandom: /* should work */
1144       case VariantJanus:      /* should work */
1145       case VariantSuper:      /* experimental */
1146       case VariantGreat:      /* experimental, requires legality testing to be off */
1147       case VariantSChess:     /* S-Chess, should work */
1148       case VariantGrand:      /* should work */
1149       case VariantSpartan:    /* should work */
1150         break;
1151       }
1152     }
1153
1154 }
1155
1156 int
1157 NextIntegerFromString (char ** str, long * value)
1158 {
1159     int result = -1;
1160     char * s = *str;
1161
1162     while( *s == ' ' || *s == '\t' ) {
1163         s++;
1164     }
1165
1166     *value = 0;
1167
1168     if( *s >= '0' && *s <= '9' ) {
1169         while( *s >= '0' && *s <= '9' ) {
1170             *value = *value * 10 + (*s - '0');
1171             s++;
1172         }
1173
1174         result = 0;
1175     }
1176
1177     *str = s;
1178
1179     return result;
1180 }
1181
1182 int
1183 NextTimeControlFromString (char ** str, long * value)
1184 {
1185     long temp;
1186     int result = NextIntegerFromString( str, &temp );
1187
1188     if( result == 0 ) {
1189         *value = temp * 60; /* Minutes */
1190         if( **str == ':' ) {
1191             (*str)++;
1192             result = NextIntegerFromString( str, &temp );
1193             *value += temp; /* Seconds */
1194         }
1195     }
1196
1197     return result;
1198 }
1199
1200 int
1201 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1202 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1203     int result = -1, type = 0; long temp, temp2;
1204
1205     if(**str != ':') return -1; // old params remain in force!
1206     (*str)++;
1207     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1208     if( NextIntegerFromString( str, &temp ) ) return -1;
1209     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1210
1211     if(**str != '/') {
1212         /* time only: incremental or sudden-death time control */
1213         if(**str == '+') { /* increment follows; read it */
1214             (*str)++;
1215             if(**str == '!') type = *(*str)++; // Bronstein TC
1216             if(result = NextIntegerFromString( str, &temp2)) return -1;
1217             *inc = temp2 * 1000;
1218             if(**str == '.') { // read fraction of increment
1219                 char *start = ++(*str);
1220                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1221                 temp2 *= 1000;
1222                 while(start++ < *str) temp2 /= 10;
1223                 *inc += temp2;
1224             }
1225         } else *inc = 0;
1226         *moves = 0; *tc = temp * 1000; *incType = type;
1227         return 0;
1228     }
1229
1230     (*str)++; /* classical time control */
1231     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1232
1233     if(result == 0) {
1234         *moves = temp;
1235         *tc    = temp2 * 1000;
1236         *inc   = 0;
1237         *incType = type;
1238     }
1239     return result;
1240 }
1241
1242 int
1243 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1244 {   /* [HGM] get time to add from the multi-session time-control string */
1245     int incType, moves=1; /* kludge to force reading of first session */
1246     long time, increment;
1247     char *s = tcString;
1248
1249     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1250     do {
1251         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1252         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1253         if(movenr == -1) return time;    /* last move before new session     */
1254         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1255         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1256         if(!moves) return increment;     /* current session is incremental   */
1257         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1258     } while(movenr >= -1);               /* try again for next session       */
1259
1260     return 0; // no new time quota on this move
1261 }
1262
1263 int
1264 ParseTimeControl (char *tc, float ti, int mps)
1265 {
1266   long tc1;
1267   long tc2;
1268   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1269   int min, sec=0;
1270
1271   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1272   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1273       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1274   if(ti > 0) {
1275
1276     if(mps)
1277       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1278     else 
1279       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1280   } else {
1281     if(mps)
1282       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1283     else 
1284       snprintf(buf, MSG_SIZ, ":%s", mytc);
1285   }
1286   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1287   
1288   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1289     return FALSE;
1290   }
1291
1292   if( *tc == '/' ) {
1293     /* Parse second time control */
1294     tc++;
1295
1296     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1297       return FALSE;
1298     }
1299
1300     if( tc2 == 0 ) {
1301       return FALSE;
1302     }
1303
1304     timeControl_2 = tc2 * 1000;
1305   }
1306   else {
1307     timeControl_2 = 0;
1308   }
1309
1310   if( tc1 == 0 ) {
1311     return FALSE;
1312   }
1313
1314   timeControl = tc1 * 1000;
1315
1316   if (ti >= 0) {
1317     timeIncrement = ti * 1000;  /* convert to ms */
1318     movesPerSession = 0;
1319   } else {
1320     timeIncrement = 0;
1321     movesPerSession = mps;
1322   }
1323   return TRUE;
1324 }
1325
1326 void
1327 InitBackEnd2 ()
1328 {
1329     if (appData.debugMode) {
1330         fprintf(debugFP, "%s\n", programVersion);
1331     }
1332     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1333
1334     set_cont_sequence(appData.wrapContSeq);
1335     if (appData.matchGames > 0) {
1336         appData.matchMode = TRUE;
1337     } else if (appData.matchMode) {
1338         appData.matchGames = 1;
1339     }
1340     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1341         appData.matchGames = appData.sameColorGames;
1342     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1343         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1344         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1345     }
1346     Reset(TRUE, FALSE);
1347     if (appData.noChessProgram || first.protocolVersion == 1) {
1348       InitBackEnd3();
1349     } else {
1350       /* kludge: allow timeout for initial "feature" commands */
1351       FreezeUI();
1352       DisplayMessage("", _("Starting chess program"));
1353       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1354     }
1355 }
1356
1357 int
1358 CalculateIndex (int index, int gameNr)
1359 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1360     int res;
1361     if(index > 0) return index; // fixed nmber
1362     if(index == 0) return 1;
1363     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1364     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1365     return res;
1366 }
1367
1368 int
1369 LoadGameOrPosition (int gameNr)
1370 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1371     if (*appData.loadGameFile != NULLCHAR) {
1372         if (!LoadGameFromFile(appData.loadGameFile,
1373                 CalculateIndex(appData.loadGameIndex, gameNr),
1374                               appData.loadGameFile, FALSE)) {
1375             DisplayFatalError(_("Bad game file"), 0, 1);
1376             return 0;
1377         }
1378     } else if (*appData.loadPositionFile != NULLCHAR) {
1379         if (!LoadPositionFromFile(appData.loadPositionFile,
1380                 CalculateIndex(appData.loadPositionIndex, gameNr),
1381                                   appData.loadPositionFile)) {
1382             DisplayFatalError(_("Bad position file"), 0, 1);
1383             return 0;
1384         }
1385     }
1386     return 1;
1387 }
1388
1389 void
1390 ReserveGame (int gameNr, char resChar)
1391 {
1392     FILE *tf = fopen(appData.tourneyFile, "r+");
1393     char *p, *q, c, buf[MSG_SIZ];
1394     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1395     safeStrCpy(buf, lastMsg, MSG_SIZ);
1396     DisplayMessage(_("Pick new game"), "");
1397     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1398     ParseArgsFromFile(tf);
1399     p = q = appData.results;
1400     if(appData.debugMode) {
1401       char *r = appData.participants;
1402       fprintf(debugFP, "results = '%s'\n", p);
1403       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1404       fprintf(debugFP, "\n");
1405     }
1406     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1407     nextGame = q - p;
1408     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1409     safeStrCpy(q, p, strlen(p) + 2);
1410     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1411     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1412     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1413         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1414         q[nextGame] = '*';
1415     }
1416     fseek(tf, -(strlen(p)+4), SEEK_END);
1417     c = fgetc(tf);
1418     if(c != '"') // depending on DOS or Unix line endings we can be one off
1419          fseek(tf, -(strlen(p)+2), SEEK_END);
1420     else fseek(tf, -(strlen(p)+3), SEEK_END);
1421     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1422     DisplayMessage(buf, "");
1423     free(p); appData.results = q;
1424     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1425        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1426       int round = appData.defaultMatchGames * appData.tourneyType;
1427       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1428          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1429         UnloadEngine(&first);  // next game belongs to other pairing;
1430         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1431     }
1432     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1433 }
1434
1435 void
1436 MatchEvent (int mode)
1437 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1438         int dummy;
1439         if(matchMode) { // already in match mode: switch it off
1440             abortMatch = TRUE;
1441             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1442             return;
1443         }
1444 //      if(gameMode != BeginningOfGame) {
1445 //          DisplayError(_("You can only start a match from the initial position."), 0);
1446 //          return;
1447 //      }
1448         abortMatch = FALSE;
1449         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1450         /* Set up machine vs. machine match */
1451         nextGame = 0;
1452         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1453         if(appData.tourneyFile[0]) {
1454             ReserveGame(-1, 0);
1455             if(nextGame > appData.matchGames) {
1456                 char buf[MSG_SIZ];
1457                 if(strchr(appData.results, '*') == NULL) {
1458                     FILE *f;
1459                     appData.tourneyCycles++;
1460                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1461                         fclose(f);
1462                         NextTourneyGame(-1, &dummy);
1463                         ReserveGame(-1, 0);
1464                         if(nextGame <= appData.matchGames) {
1465                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1466                             matchMode = mode;
1467                             ScheduleDelayedEvent(NextMatchGame, 10000);
1468                             return;
1469                         }
1470                     }
1471                 }
1472                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1473                 DisplayError(buf, 0);
1474                 appData.tourneyFile[0] = 0;
1475                 return;
1476             }
1477         } else
1478         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1479             DisplayFatalError(_("Can't have a match with no chess programs"),
1480                               0, 2);
1481             return;
1482         }
1483         matchMode = mode;
1484         matchGame = roundNr = 1;
1485         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1486         NextMatchGame();
1487 }
1488
1489 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1490
1491 void
1492 InitBackEnd3 P((void))
1493 {
1494     GameMode initialMode;
1495     char buf[MSG_SIZ];
1496     int err, len;
1497
1498     InitChessProgram(&first, startedFromSetupPosition);
1499
1500     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1501         free(programVersion);
1502         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1503         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1504         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1505     }
1506
1507     if (appData.icsActive) {
1508 #ifdef WIN32
1509         /* [DM] Make a console window if needed [HGM] merged ifs */
1510         ConsoleCreate();
1511 #endif
1512         err = establish();
1513         if (err != 0)
1514           {
1515             if (*appData.icsCommPort != NULLCHAR)
1516               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1517                              appData.icsCommPort);
1518             else
1519               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1520                         appData.icsHost, appData.icsPort);
1521
1522             if( (len >= MSG_SIZ) && appData.debugMode )
1523               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1524
1525             DisplayFatalError(buf, err, 1);
1526             return;
1527         }
1528         SetICSMode();
1529         telnetISR =
1530           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1531         fromUserISR =
1532           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1533         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1534             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1535     } else if (appData.noChessProgram) {
1536         SetNCPMode();
1537     } else {
1538         SetGNUMode();
1539     }
1540
1541     if (*appData.cmailGameName != NULLCHAR) {
1542         SetCmailMode();
1543         OpenLoopback(&cmailPR);
1544         cmailISR =
1545           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1546     }
1547
1548     ThawUI();
1549     DisplayMessage("", "");
1550     if (StrCaseCmp(appData.initialMode, "") == 0) {
1551       initialMode = BeginningOfGame;
1552       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1553         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1554         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1555         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1556         ModeHighlight();
1557       }
1558     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1559       initialMode = TwoMachinesPlay;
1560     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1561       initialMode = AnalyzeFile;
1562     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1563       initialMode = AnalyzeMode;
1564     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1565       initialMode = MachinePlaysWhite;
1566     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1567       initialMode = MachinePlaysBlack;
1568     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1569       initialMode = EditGame;
1570     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1571       initialMode = EditPosition;
1572     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1573       initialMode = Training;
1574     } else {
1575       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1576       if( (len >= MSG_SIZ) && appData.debugMode )
1577         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1578
1579       DisplayFatalError(buf, 0, 2);
1580       return;
1581     }
1582
1583     if (appData.matchMode) {
1584         if(appData.tourneyFile[0]) { // start tourney from command line
1585             FILE *f;
1586             if(f = fopen(appData.tourneyFile, "r")) {
1587                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1588                 fclose(f);
1589                 appData.clockMode = TRUE;
1590                 SetGNUMode();
1591             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1592         }
1593         MatchEvent(TRUE);
1594     } else if (*appData.cmailGameName != NULLCHAR) {
1595         /* Set up cmail mode */
1596         ReloadCmailMsgEvent(TRUE);
1597     } else {
1598         /* Set up other modes */
1599         if (initialMode == AnalyzeFile) {
1600           if (*appData.loadGameFile == NULLCHAR) {
1601             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1602             return;
1603           }
1604         }
1605         if (*appData.loadGameFile != NULLCHAR) {
1606             (void) LoadGameFromFile(appData.loadGameFile,
1607                                     appData.loadGameIndex,
1608                                     appData.loadGameFile, TRUE);
1609         } else if (*appData.loadPositionFile != NULLCHAR) {
1610             (void) LoadPositionFromFile(appData.loadPositionFile,
1611                                         appData.loadPositionIndex,
1612                                         appData.loadPositionFile);
1613             /* [HGM] try to make self-starting even after FEN load */
1614             /* to allow automatic setup of fairy variants with wtm */
1615             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1616                 gameMode = BeginningOfGame;
1617                 setboardSpoiledMachineBlack = 1;
1618             }
1619             /* [HGM] loadPos: make that every new game uses the setup */
1620             /* from file as long as we do not switch variant          */
1621             if(!blackPlaysFirst) {
1622                 startedFromPositionFile = TRUE;
1623                 CopyBoard(filePosition, boards[0]);
1624             }
1625         }
1626         if (initialMode == AnalyzeMode) {
1627           if (appData.noChessProgram) {
1628             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1629             return;
1630           }
1631           if (appData.icsActive) {
1632             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1633             return;
1634           }
1635           AnalyzeModeEvent();
1636         } else if (initialMode == AnalyzeFile) {
1637           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1638           ShowThinkingEvent();
1639           AnalyzeFileEvent();
1640           AnalysisPeriodicEvent(1);
1641         } else if (initialMode == MachinePlaysWhite) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           MachineWhiteEvent();
1653         } else if (initialMode == MachinePlaysBlack) {
1654           if (appData.noChessProgram) {
1655             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1656                               0, 2);
1657             return;
1658           }
1659           if (appData.icsActive) {
1660             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1661                               0, 2);
1662             return;
1663           }
1664           MachineBlackEvent();
1665         } else if (initialMode == TwoMachinesPlay) {
1666           if (appData.noChessProgram) {
1667             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1668                               0, 2);
1669             return;
1670           }
1671           if (appData.icsActive) {
1672             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1673                               0, 2);
1674             return;
1675           }
1676           TwoMachinesEvent();
1677         } else if (initialMode == EditGame) {
1678           EditGameEvent();
1679         } else if (initialMode == EditPosition) {
1680           EditPositionEvent();
1681         } else if (initialMode == Training) {
1682           if (*appData.loadGameFile == NULLCHAR) {
1683             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1684             return;
1685           }
1686           TrainingEvent();
1687         }
1688     }
1689 }
1690
1691 void
1692 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1693 {
1694     DisplayBook(current+1);
1695
1696     MoveHistorySet( movelist, first, last, current, pvInfoList );
1697
1698     EvalGraphSet( first, last, current, pvInfoList );
1699
1700     MakeEngineOutputTitle();
1701 }
1702
1703 /*
1704  * Establish will establish a contact to a remote host.port.
1705  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1706  *  used to talk to the host.
1707  * Returns 0 if okay, error code if not.
1708  */
1709 int
1710 establish ()
1711 {
1712     char buf[MSG_SIZ];
1713
1714     if (*appData.icsCommPort != NULLCHAR) {
1715         /* Talk to the host through a serial comm port */
1716         return OpenCommPort(appData.icsCommPort, &icsPR);
1717
1718     } else if (*appData.gateway != NULLCHAR) {
1719         if (*appData.remoteShell == NULLCHAR) {
1720             /* Use the rcmd protocol to run telnet program on a gateway host */
1721             snprintf(buf, sizeof(buf), "%s %s %s",
1722                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1723             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1724
1725         } else {
1726             /* Use the rsh program to run telnet program on a gateway host */
1727             if (*appData.remoteUser == NULLCHAR) {
1728                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1729                         appData.gateway, appData.telnetProgram,
1730                         appData.icsHost, appData.icsPort);
1731             } else {
1732                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1733                         appData.remoteShell, appData.gateway,
1734                         appData.remoteUser, appData.telnetProgram,
1735                         appData.icsHost, appData.icsPort);
1736             }
1737             return StartChildProcess(buf, "", &icsPR);
1738
1739         }
1740     } else if (appData.useTelnet) {
1741         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1742
1743     } else {
1744         /* TCP socket interface differs somewhat between
1745            Unix and NT; handle details in the front end.
1746            */
1747         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1748     }
1749 }
1750
1751 void
1752 EscapeExpand (char *p, char *q)
1753 {       // [HGM] initstring: routine to shape up string arguments
1754         while(*p++ = *q++) if(p[-1] == '\\')
1755             switch(*q++) {
1756                 case 'n': p[-1] = '\n'; break;
1757                 case 'r': p[-1] = '\r'; break;
1758                 case 't': p[-1] = '\t'; break;
1759                 case '\\': p[-1] = '\\'; break;
1760                 case 0: *p = 0; return;
1761                 default: p[-1] = q[-1]; break;
1762             }
1763 }
1764
1765 void
1766 show_bytes (FILE *fp, char *buf, int count)
1767 {
1768     while (count--) {
1769         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1770             fprintf(fp, "\\%03o", *buf & 0xff);
1771         } else {
1772             putc(*buf, fp);
1773         }
1774         buf++;
1775     }
1776     fflush(fp);
1777 }
1778
1779 /* Returns an errno value */
1780 int
1781 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1782 {
1783     char buf[8192], *p, *q, *buflim;
1784     int left, newcount, outcount;
1785
1786     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1787         *appData.gateway != NULLCHAR) {
1788         if (appData.debugMode) {
1789             fprintf(debugFP, ">ICS: ");
1790             show_bytes(debugFP, message, count);
1791             fprintf(debugFP, "\n");
1792         }
1793         return OutputToProcess(pr, message, count, outError);
1794     }
1795
1796     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1797     p = message;
1798     q = buf;
1799     left = count;
1800     newcount = 0;
1801     while (left) {
1802         if (q >= buflim) {
1803             if (appData.debugMode) {
1804                 fprintf(debugFP, ">ICS: ");
1805                 show_bytes(debugFP, buf, newcount);
1806                 fprintf(debugFP, "\n");
1807             }
1808             outcount = OutputToProcess(pr, buf, newcount, outError);
1809             if (outcount < newcount) return -1; /* to be sure */
1810             q = buf;
1811             newcount = 0;
1812         }
1813         if (*p == '\n') {
1814             *q++ = '\r';
1815             newcount++;
1816         } else if (((unsigned char) *p) == TN_IAC) {
1817             *q++ = (char) TN_IAC;
1818             newcount ++;
1819         }
1820         *q++ = *p++;
1821         newcount++;
1822         left--;
1823     }
1824     if (appData.debugMode) {
1825         fprintf(debugFP, ">ICS: ");
1826         show_bytes(debugFP, buf, newcount);
1827         fprintf(debugFP, "\n");
1828     }
1829     outcount = OutputToProcess(pr, buf, newcount, outError);
1830     if (outcount < newcount) return -1; /* to be sure */
1831     return count;
1832 }
1833
1834 void
1835 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1836 {
1837     int outError, outCount;
1838     static int gotEof = 0;
1839
1840     /* Pass data read from player on to ICS */
1841     if (count > 0) {
1842         gotEof = 0;
1843         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1844         if (outCount < count) {
1845             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846         }
1847     } else if (count < 0) {
1848         RemoveInputSource(isr);
1849         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1850     } else if (gotEof++ > 0) {
1851         RemoveInputSource(isr);
1852         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1853     }
1854 }
1855
1856 void
1857 KeepAlive ()
1858 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1859     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1860     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1861     SendToICS("date\n");
1862     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1863 }
1864
1865 /* added routine for printf style output to ics */
1866 void
1867 ics_printf (char *format, ...)
1868 {
1869     char buffer[MSG_SIZ];
1870     va_list args;
1871
1872     va_start(args, format);
1873     vsnprintf(buffer, sizeof(buffer), format, args);
1874     buffer[sizeof(buffer)-1] = '\0';
1875     SendToICS(buffer);
1876     va_end(args);
1877 }
1878
1879 void
1880 SendToICS (char *s)
1881 {
1882     int count, outCount, outError;
1883
1884     if (icsPR == NoProc) return;
1885
1886     count = strlen(s);
1887     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893 /* This is used for sending logon scripts to the ICS. Sending
1894    without a delay causes problems when using timestamp on ICC
1895    (at least on my machine). */
1896 void
1897 SendToICSDelayed (char *s, long msdelay)
1898 {
1899     int count, outCount, outError;
1900
1901     if (icsPR == NoProc) return;
1902
1903     count = strlen(s);
1904     if (appData.debugMode) {
1905         fprintf(debugFP, ">ICS: ");
1906         show_bytes(debugFP, s, count);
1907         fprintf(debugFP, "\n");
1908     }
1909     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1910                                       msdelay);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916
1917 /* Remove all highlighting escape sequences in s
1918    Also deletes any suffix starting with '('
1919    */
1920 char *
1921 StripHighlightAndTitle (char *s)
1922 {
1923     static char retbuf[MSG_SIZ];
1924     char *p = retbuf;
1925
1926     while (*s != NULLCHAR) {
1927         while (*s == '\033') {
1928             while (*s != NULLCHAR && !isalpha(*s)) s++;
1929             if (*s != NULLCHAR) s++;
1930         }
1931         while (*s != NULLCHAR && *s != '\033') {
1932             if (*s == '(' || *s == '[') {
1933                 *p = NULLCHAR;
1934                 return retbuf;
1935             }
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 /* Remove all highlighting escape sequences in s */
1944 char *
1945 StripHighlight (char *s)
1946 {
1947     static char retbuf[MSG_SIZ];
1948     char *p = retbuf;
1949
1950     while (*s != NULLCHAR) {
1951         while (*s == '\033') {
1952             while (*s != NULLCHAR && !isalpha(*s)) s++;
1953             if (*s != NULLCHAR) s++;
1954         }
1955         while (*s != NULLCHAR && *s != '\033') {
1956             *p++ = *s++;
1957         }
1958     }
1959     *p = NULLCHAR;
1960     return retbuf;
1961 }
1962
1963 char *variantNames[] = VARIANT_NAMES;
1964 char *
1965 VariantName (VariantClass v)
1966 {
1967     return variantNames[v];
1968 }
1969
1970
1971 /* Identify a variant from the strings the chess servers use or the
1972    PGN Variant tag names we use. */
1973 VariantClass
1974 StringToVariant (char *e)
1975 {
1976     char *p;
1977     int wnum = -1;
1978     VariantClass v = VariantNormal;
1979     int i, found = FALSE;
1980     char buf[MSG_SIZ];
1981     int len;
1982
1983     if (!e) return v;
1984
1985     /* [HGM] skip over optional board-size prefixes */
1986     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1987         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1988         while( *e++ != '_');
1989     }
1990
1991     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1992         v = VariantNormal;
1993         found = TRUE;
1994     } else
1995     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1996       if (StrCaseStr(e, variantNames[i])) {
1997         v = (VariantClass) i;
1998         found = TRUE;
1999         break;
2000       }
2001     }
2002
2003     if (!found) {
2004       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2005           || StrCaseStr(e, "wild/fr")
2006           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2007         v = VariantFischeRandom;
2008       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2009                  (i = 1, p = StrCaseStr(e, "w"))) {
2010         p += i;
2011         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2012         if (isdigit(*p)) {
2013           wnum = atoi(p);
2014         } else {
2015           wnum = -1;
2016         }
2017         switch (wnum) {
2018         case 0: /* FICS only, actually */
2019         case 1:
2020           /* Castling legal even if K starts on d-file */
2021           v = VariantWildCastle;
2022           break;
2023         case 2:
2024         case 3:
2025         case 4:
2026           /* Castling illegal even if K & R happen to start in
2027              normal positions. */
2028           v = VariantNoCastle;
2029           break;
2030         case 5:
2031         case 7:
2032         case 8:
2033         case 10:
2034         case 11:
2035         case 12:
2036         case 13:
2037         case 14:
2038         case 15:
2039         case 18:
2040         case 19:
2041           /* Castling legal iff K & R start in normal positions */
2042           v = VariantNormal;
2043           break;
2044         case 6:
2045         case 20:
2046         case 21:
2047           /* Special wilds for position setup; unclear what to do here */
2048           v = VariantLoadable;
2049           break;
2050         case 9:
2051           /* Bizarre ICC game */
2052           v = VariantTwoKings;
2053           break;
2054         case 16:
2055           v = VariantKriegspiel;
2056           break;
2057         case 17:
2058           v = VariantLosers;
2059           break;
2060         case 22:
2061           v = VariantFischeRandom;
2062           break;
2063         case 23:
2064           v = VariantCrazyhouse;
2065           break;
2066         case 24:
2067           v = VariantBughouse;
2068           break;
2069         case 25:
2070           v = Variant3Check;
2071           break;
2072         case 26:
2073           /* Not quite the same as FICS suicide! */
2074           v = VariantGiveaway;
2075           break;
2076         case 27:
2077           v = VariantAtomic;
2078           break;
2079         case 28:
2080           v = VariantShatranj;
2081           break;
2082
2083         /* Temporary names for future ICC types.  The name *will* change in
2084            the next xboard/WinBoard release after ICC defines it. */
2085         case 29:
2086           v = Variant29;
2087           break;
2088         case 30:
2089           v = Variant30;
2090           break;
2091         case 31:
2092           v = Variant31;
2093           break;
2094         case 32:
2095           v = Variant32;
2096           break;
2097         case 33:
2098           v = Variant33;
2099           break;
2100         case 34:
2101           v = Variant34;
2102           break;
2103         case 35:
2104           v = Variant35;
2105           break;
2106         case 36:
2107           v = Variant36;
2108           break;
2109         case 37:
2110           v = VariantShogi;
2111           break;
2112         case 38:
2113           v = VariantXiangqi;
2114           break;
2115         case 39:
2116           v = VariantCourier;
2117           break;
2118         case 40:
2119           v = VariantGothic;
2120           break;
2121         case 41:
2122           v = VariantCapablanca;
2123           break;
2124         case 42:
2125           v = VariantKnightmate;
2126           break;
2127         case 43:
2128           v = VariantFairy;
2129           break;
2130         case 44:
2131           v = VariantCylinder;
2132           break;
2133         case 45:
2134           v = VariantFalcon;
2135           break;
2136         case 46:
2137           v = VariantCapaRandom;
2138           break;
2139         case 47:
2140           v = VariantBerolina;
2141           break;
2142         case 48:
2143           v = VariantJanus;
2144           break;
2145         case 49:
2146           v = VariantSuper;
2147           break;
2148         case 50:
2149           v = VariantGreat;
2150           break;
2151         case -1:
2152           /* Found "wild" or "w" in the string but no number;
2153              must assume it's normal chess. */
2154           v = VariantNormal;
2155           break;
2156         default:
2157           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2158           if( (len >= MSG_SIZ) && appData.debugMode )
2159             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2160
2161           DisplayError(buf, 0);
2162           v = VariantUnknown;
2163           break;
2164         }
2165       }
2166     }
2167     if (appData.debugMode) {
2168       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2169               e, wnum, VariantName(v));
2170     }
2171     return v;
2172 }
2173
2174 static int leftover_start = 0, leftover_len = 0;
2175 char star_match[STAR_MATCH_N][MSG_SIZ];
2176
2177 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2178    advance *index beyond it, and set leftover_start to the new value of
2179    *index; else return FALSE.  If pattern contains the character '*', it
2180    matches any sequence of characters not containing '\r', '\n', or the
2181    character following the '*' (if any), and the matched sequence(s) are
2182    copied into star_match.
2183    */
2184 int
2185 looking_at ( char *buf, int *index, char *pattern)
2186 {
2187     char *bufp = &buf[*index], *patternp = pattern;
2188     int star_count = 0;
2189     char *matchp = star_match[0];
2190
2191     for (;;) {
2192         if (*patternp == NULLCHAR) {
2193             *index = leftover_start = bufp - buf;
2194             *matchp = NULLCHAR;
2195             return TRUE;
2196         }
2197         if (*bufp == NULLCHAR) return FALSE;
2198         if (*patternp == '*') {
2199             if (*bufp == *(patternp + 1)) {
2200                 *matchp = NULLCHAR;
2201                 matchp = star_match[++star_count];
2202                 patternp += 2;
2203                 bufp++;
2204                 continue;
2205             } else if (*bufp == '\n' || *bufp == '\r') {
2206                 patternp++;
2207                 if (*patternp == NULLCHAR)
2208                   continue;
2209                 else
2210                   return FALSE;
2211             } else {
2212                 *matchp++ = *bufp++;
2213                 continue;
2214             }
2215         }
2216         if (*patternp != *bufp) return FALSE;
2217         patternp++;
2218         bufp++;
2219     }
2220 }
2221
2222 void
2223 SendToPlayer (char *data, int length)
2224 {
2225     int error, outCount;
2226     outCount = OutputToProcess(NoProc, data, length, &error);
2227     if (outCount < length) {
2228         DisplayFatalError(_("Error writing to display"), error, 1);
2229     }
2230 }
2231
2232 void
2233 PackHolding (char packed[], char *holding)
2234 {
2235     char *p = holding;
2236     char *q = packed;
2237     int runlength = 0;
2238     int curr = 9999;
2239     do {
2240         if (*p == curr) {
2241             runlength++;
2242         } else {
2243             switch (runlength) {
2244               case 0:
2245                 break;
2246               case 1:
2247                 *q++ = curr;
2248                 break;
2249               case 2:
2250                 *q++ = curr;
2251                 *q++ = curr;
2252                 break;
2253               default:
2254                 sprintf(q, "%d", runlength);
2255                 while (*q) q++;
2256                 *q++ = curr;
2257                 break;
2258             }
2259             runlength = 1;
2260             curr = *p;
2261         }
2262     } while (*p++);
2263     *q = NULLCHAR;
2264 }
2265
2266 /* Telnet protocol requests from the front end */
2267 void
2268 TelnetRequest (unsigned char ddww, unsigned char option)
2269 {
2270     unsigned char msg[3];
2271     int outCount, outError;
2272
2273     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2274
2275     if (appData.debugMode) {
2276         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2277         switch (ddww) {
2278           case TN_DO:
2279             ddwwStr = "DO";
2280             break;
2281           case TN_DONT:
2282             ddwwStr = "DONT";
2283             break;
2284           case TN_WILL:
2285             ddwwStr = "WILL";
2286             break;
2287           case TN_WONT:
2288             ddwwStr = "WONT";
2289             break;
2290           default:
2291             ddwwStr = buf1;
2292             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2293             break;
2294         }
2295         switch (option) {
2296           case TN_ECHO:
2297             optionStr = "ECHO";
2298             break;
2299           default:
2300             optionStr = buf2;
2301             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2302             break;
2303         }
2304         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2305     }
2306     msg[0] = TN_IAC;
2307     msg[1] = ddww;
2308     msg[2] = option;
2309     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2310     if (outCount < 3) {
2311         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2312     }
2313 }
2314
2315 void
2316 DoEcho ()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DO, TN_ECHO);
2320 }
2321
2322 void
2323 DontEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DONT, TN_ECHO);
2327 }
2328
2329 void
2330 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2331 {
2332     /* put the holdings sent to us by the server on the board holdings area */
2333     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2334     char p;
2335     ChessSquare piece;
2336
2337     if(gameInfo.holdingsWidth < 2)  return;
2338     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2339         return; // prevent overwriting by pre-board holdings
2340
2341     if( (int)lowestPiece >= BlackPawn ) {
2342         holdingsColumn = 0;
2343         countsColumn = 1;
2344         holdingsStartRow = BOARD_HEIGHT-1;
2345         direction = -1;
2346     } else {
2347         holdingsColumn = BOARD_WIDTH-1;
2348         countsColumn = BOARD_WIDTH-2;
2349         holdingsStartRow = 0;
2350         direction = 1;
2351     }
2352
2353     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2354         board[i][holdingsColumn] = EmptySquare;
2355         board[i][countsColumn]   = (ChessSquare) 0;
2356     }
2357     while( (p=*holdings++) != NULLCHAR ) {
2358         piece = CharToPiece( ToUpper(p) );
2359         if(piece == EmptySquare) continue;
2360         /*j = (int) piece - (int) WhitePawn;*/
2361         j = PieceToNumber(piece);
2362         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2363         if(j < 0) continue;               /* should not happen */
2364         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2365         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2366         board[holdingsStartRow+j*direction][countsColumn]++;
2367     }
2368 }
2369
2370
2371 void
2372 VariantSwitch (Board board, VariantClass newVariant)
2373 {
2374    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2375    static Board oldBoard;
2376
2377    startedFromPositionFile = FALSE;
2378    if(gameInfo.variant == newVariant) return;
2379
2380    /* [HGM] This routine is called each time an assignment is made to
2381     * gameInfo.variant during a game, to make sure the board sizes
2382     * are set to match the new variant. If that means adding or deleting
2383     * holdings, we shift the playing board accordingly
2384     * This kludge is needed because in ICS observe mode, we get boards
2385     * of an ongoing game without knowing the variant, and learn about the
2386     * latter only later. This can be because of the move list we requested,
2387     * in which case the game history is refilled from the beginning anyway,
2388     * but also when receiving holdings of a crazyhouse game. In the latter
2389     * case we want to add those holdings to the already received position.
2390     */
2391
2392
2393    if (appData.debugMode) {
2394      fprintf(debugFP, "Switch board from %s to %s\n",
2395              VariantName(gameInfo.variant), VariantName(newVariant));
2396      setbuf(debugFP, NULL);
2397    }
2398    shuffleOpenings = 0;       /* [HGM] shuffle */
2399    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2400    switch(newVariant)
2401      {
2402      case VariantShogi:
2403        newWidth = 9;  newHeight = 9;
2404        gameInfo.holdingsSize = 7;
2405      case VariantBughouse:
2406      case VariantCrazyhouse:
2407        newHoldingsWidth = 2; break;
2408      case VariantGreat:
2409        newWidth = 10;
2410      case VariantSuper:
2411        newHoldingsWidth = 2;
2412        gameInfo.holdingsSize = 8;
2413        break;
2414      case VariantGothic:
2415      case VariantCapablanca:
2416      case VariantCapaRandom:
2417        newWidth = 10;
2418      default:
2419        newHoldingsWidth = gameInfo.holdingsSize = 0;
2420      };
2421
2422    if(newWidth  != gameInfo.boardWidth  ||
2423       newHeight != gameInfo.boardHeight ||
2424       newHoldingsWidth != gameInfo.holdingsWidth ) {
2425
2426      /* shift position to new playing area, if needed */
2427      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432        for(i=0; i<newHeight; i++) {
2433          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2434          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2435        }
2436      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2437        for(i=0; i<BOARD_HEIGHT; i++)
2438          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2439            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440              board[i][j];
2441      }
2442      gameInfo.boardWidth  = newWidth;
2443      gameInfo.boardHeight = newHeight;
2444      gameInfo.holdingsWidth = newHoldingsWidth;
2445      gameInfo.variant = newVariant;
2446      InitDrawingSizes(-2, 0);
2447    } else gameInfo.variant = newVariant;
2448    CopyBoard(oldBoard, board);   // remember correctly formatted board
2449      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2450    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2451 }
2452
2453 static int loggedOn = FALSE;
2454
2455 /*-- Game start info cache: --*/
2456 int gs_gamenum;
2457 char gs_kind[MSG_SIZ];
2458 static char player1Name[128] = "";
2459 static char player2Name[128] = "";
2460 static char cont_seq[] = "\n\\   ";
2461 static int player1Rating = -1;
2462 static int player2Rating = -1;
2463 /*----------------------------*/
2464
2465 ColorClass curColor = ColorNormal;
2466 int suppressKibitz = 0;
2467
2468 // [HGM] seekgraph
2469 Boolean soughtPending = FALSE;
2470 Boolean seekGraphUp;
2471 #define MAX_SEEK_ADS 200
2472 #define SQUARE 0x80
2473 char *seekAdList[MAX_SEEK_ADS];
2474 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2475 float tcList[MAX_SEEK_ADS];
2476 char colorList[MAX_SEEK_ADS];
2477 int nrOfSeekAds = 0;
2478 int minRating = 1010, maxRating = 2800;
2479 int hMargin = 10, vMargin = 20, h, w;
2480 extern int squareSize, lineGap;
2481
2482 void
2483 PlotSeekAd (int i)
2484 {
2485         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2486         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2487         if(r < minRating+100 && r >=0 ) r = minRating+100;
2488         if(r > maxRating) r = maxRating;
2489         if(tc < 1.) tc = 1.;
2490         if(tc > 95.) tc = 95.;
2491         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2492         y = ((double)r - minRating)/(maxRating - minRating)
2493             * (h-vMargin-squareSize/8-1) + vMargin;
2494         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2495         if(strstr(seekAdList[i], " u ")) color = 1;
2496         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2497            !strstr(seekAdList[i], "bullet") &&
2498            !strstr(seekAdList[i], "blitz") &&
2499            !strstr(seekAdList[i], "standard") ) color = 2;
2500         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2501         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2502 }
2503
2504 void
2505 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2506 {
2507         char buf[MSG_SIZ], *ext = "";
2508         VariantClass v = StringToVariant(type);
2509         if(strstr(type, "wild")) {
2510             ext = type + 4; // append wild number
2511             if(v == VariantFischeRandom) type = "chess960"; else
2512             if(v == VariantLoadable) type = "setup"; else
2513             type = VariantName(v);
2514         }
2515         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2516         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2517             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2518             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2519             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2520             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2521             seekNrList[nrOfSeekAds] = nr;
2522             zList[nrOfSeekAds] = 0;
2523             seekAdList[nrOfSeekAds++] = StrSave(buf);
2524             if(plot) PlotSeekAd(nrOfSeekAds-1);
2525         }
2526 }
2527
2528 void
2529 EraseSeekDot (int i)
2530 {
2531     int x = xList[i], y = yList[i], d=squareSize/4, k;
2532     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2533     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2534     // now replot every dot that overlapped
2535     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2536         int xx = xList[k], yy = yList[k];
2537         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2538             DrawSeekDot(xx, yy, colorList[k]);
2539     }
2540 }
2541
2542 void
2543 RemoveSeekAd (int nr)
2544 {
2545         int i;
2546         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2547             EraseSeekDot(i);
2548             if(seekAdList[i]) free(seekAdList[i]);
2549             seekAdList[i] = seekAdList[--nrOfSeekAds];
2550             seekNrList[i] = seekNrList[nrOfSeekAds];
2551             ratingList[i] = ratingList[nrOfSeekAds];
2552             colorList[i]  = colorList[nrOfSeekAds];
2553             tcList[i] = tcList[nrOfSeekAds];
2554             xList[i]  = xList[nrOfSeekAds];
2555             yList[i]  = yList[nrOfSeekAds];
2556             zList[i]  = zList[nrOfSeekAds];
2557             seekAdList[nrOfSeekAds] = NULL;
2558             break;
2559         }
2560 }
2561
2562 Boolean
2563 MatchSoughtLine (char *line)
2564 {
2565     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2566     int nr, base, inc, u=0; char dummy;
2567
2568     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2570        (u=1) &&
2571        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2573         // match: compact and save the line
2574         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2575         return TRUE;
2576     }
2577     return FALSE;
2578 }
2579
2580 int
2581 DrawSeekGraph ()
2582 {
2583     int i;
2584     if(!seekGraphUp) return FALSE;
2585     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2586     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2587
2588     DrawSeekBackground(0, 0, w, h);
2589     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2590     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2591     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2592         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2593         yy = h-1-yy;
2594         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2595         if(i%500 == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2599         }
2600     }
2601     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2602     for(i=1; i<100; i+=(i<10?1:5)) {
2603         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2604         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2605         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2606             char buf[MSG_SIZ];
2607             snprintf(buf, MSG_SIZ, "%d", i);
2608             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2609         }
2610     }
2611     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2612     return TRUE;
2613 }
2614
2615 int
2616 SeekGraphClick (ClickType click, int x, int y, int moving)
2617 {
2618     static int lastDown = 0, displayed = 0, lastSecond;
2619     if(y < 0) return FALSE;
2620     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2621         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2622         if(!seekGraphUp) return FALSE;
2623         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2624         DrawPosition(TRUE, NULL);
2625         return TRUE;
2626     }
2627     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2628         if(click == Release || moving) return FALSE;
2629         nrOfSeekAds = 0;
2630         soughtPending = TRUE;
2631         SendToICS(ics_prefix);
2632         SendToICS("sought\n"); // should this be "sought all"?
2633     } else { // issue challenge based on clicked ad
2634         int dist = 10000; int i, closest = 0, second = 0;
2635         for(i=0; i<nrOfSeekAds; i++) {
2636             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2637             if(d < dist) { dist = d; closest = i; }
2638             second += (d - zList[i] < 120); // count in-range ads
2639             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2640         }
2641         if(dist < 120) {
2642             char buf[MSG_SIZ];
2643             second = (second > 1);
2644             if(displayed != closest || second != lastSecond) {
2645                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2646                 lastSecond = second; displayed = closest;
2647             }
2648             if(click == Press) {
2649                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2650                 lastDown = closest;
2651                 return TRUE;
2652             } // on press 'hit', only show info
2653             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2654             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2655             SendToICS(ics_prefix);
2656             SendToICS(buf);
2657             return TRUE; // let incoming board of started game pop down the graph
2658         } else if(click == Release) { // release 'miss' is ignored
2659             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2660             if(moving == 2) { // right up-click
2661                 nrOfSeekAds = 0; // refresh graph
2662                 soughtPending = TRUE;
2663                 SendToICS(ics_prefix);
2664                 SendToICS("sought\n"); // should this be "sought all"?
2665             }
2666             return TRUE;
2667         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2668         // press miss or release hit 'pop down' seek graph
2669         seekGraphUp = FALSE;
2670         DrawPosition(TRUE, NULL);
2671     }
2672     return TRUE;
2673 }
2674
2675 void
2676 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2677 {
2678 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2679 #define STARTED_NONE 0
2680 #define STARTED_MOVES 1
2681 #define STARTED_BOARD 2
2682 #define STARTED_OBSERVE 3
2683 #define STARTED_HOLDINGS 4
2684 #define STARTED_CHATTER 5
2685 #define STARTED_COMMENT 6
2686 #define STARTED_MOVES_NOHIDE 7
2687
2688     static int started = STARTED_NONE;
2689     static char parse[20000];
2690     static int parse_pos = 0;
2691     static char buf[BUF_SIZE + 1];
2692     static int firstTime = TRUE, intfSet = FALSE;
2693     static ColorClass prevColor = ColorNormal;
2694     static int savingComment = FALSE;
2695     static int cmatch = 0; // continuation sequence match
2696     char *bp;
2697     char str[MSG_SIZ];
2698     int i, oldi;
2699     int buf_len;
2700     int next_out;
2701     int tkind;
2702     int backup;    /* [DM] For zippy color lines */
2703     char *p;
2704     char talker[MSG_SIZ]; // [HGM] chat
2705     int channel;
2706
2707     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2708
2709     if (appData.debugMode) {
2710       if (!error) {
2711         fprintf(debugFP, "<ICS: ");
2712         show_bytes(debugFP, data, count);
2713         fprintf(debugFP, "\n");
2714       }
2715     }
2716
2717     if (appData.debugMode) { int f = forwardMostMove;
2718         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2719                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2720                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2721     }
2722     if (count > 0) {
2723         /* If last read ended with a partial line that we couldn't parse,
2724            prepend it to the new read and try again. */
2725         if (leftover_len > 0) {
2726             for (i=0; i<leftover_len; i++)
2727               buf[i] = buf[leftover_start + i];
2728         }
2729
2730     /* copy new characters into the buffer */
2731     bp = buf + leftover_len;
2732     buf_len=leftover_len;
2733     for (i=0; i<count; i++)
2734     {
2735         // ignore these
2736         if (data[i] == '\r')
2737             continue;
2738
2739         // join lines split by ICS?
2740         if (!appData.noJoin)
2741         {
2742             /*
2743                 Joining just consists of finding matches against the
2744                 continuation sequence, and discarding that sequence
2745                 if found instead of copying it.  So, until a match
2746                 fails, there's nothing to do since it might be the
2747                 complete sequence, and thus, something we don't want
2748                 copied.
2749             */
2750             if (data[i] == cont_seq[cmatch])
2751             {
2752                 cmatch++;
2753                 if (cmatch == strlen(cont_seq))
2754                 {
2755                     cmatch = 0; // complete match.  just reset the counter
2756
2757                     /*
2758                         it's possible for the ICS to not include the space
2759                         at the end of the last word, making our [correct]
2760                         join operation fuse two separate words.  the server
2761                         does this when the space occurs at the width setting.
2762                     */
2763                     if (!buf_len || buf[buf_len-1] != ' ')
2764                     {
2765                         *bp++ = ' ';
2766                         buf_len++;
2767                     }
2768                 }
2769                 continue;
2770             }
2771             else if (cmatch)
2772             {
2773                 /*
2774                     match failed, so we have to copy what matched before
2775                     falling through and copying this character.  In reality,
2776                     this will only ever be just the newline character, but
2777                     it doesn't hurt to be precise.
2778                 */
2779                 strncpy(bp, cont_seq, cmatch);
2780                 bp += cmatch;
2781                 buf_len += cmatch;
2782                 cmatch = 0;
2783             }
2784         }
2785
2786         // copy this char
2787         *bp++ = data[i];
2788         buf_len++;
2789     }
2790
2791         buf[buf_len] = NULLCHAR;
2792 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2793         next_out = 0;
2794         leftover_start = 0;
2795
2796         i = 0;
2797         while (i < buf_len) {
2798             /* Deal with part of the TELNET option negotiation
2799                protocol.  We refuse to do anything beyond the
2800                defaults, except that we allow the WILL ECHO option,
2801                which ICS uses to turn off password echoing when we are
2802                directly connected to it.  We reject this option
2803                if localLineEditing mode is on (always on in xboard)
2804                and we are talking to port 23, which might be a real
2805                telnet server that will try to keep WILL ECHO on permanently.
2806              */
2807             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2808                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2809                 unsigned char option;
2810                 oldi = i;
2811                 switch ((unsigned char) buf[++i]) {
2812                   case TN_WILL:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<WILL ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       case TN_ECHO:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "ECHO ");
2819                         /* Reply only if this is a change, according
2820                            to the protocol rules. */
2821                         if (remoteEchoOption) break;
2822                         if (appData.localLineEditing &&
2823                             atoi(appData.icsPort) == TN_PORT) {
2824                             TelnetRequest(TN_DONT, TN_ECHO);
2825                         } else {
2826                             EchoOff();
2827                             TelnetRequest(TN_DO, TN_ECHO);
2828                             remoteEchoOption = TRUE;
2829                         }
2830                         break;
2831                       default:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "%d ", option);
2834                         /* Whatever this is, we don't want it. */
2835                         TelnetRequest(TN_DONT, option);
2836                         break;
2837                     }
2838                     break;
2839                   case TN_WONT:
2840                     if (appData.debugMode)
2841                       fprintf(debugFP, "\n<WONT ");
2842                     switch (option = (unsigned char) buf[++i]) {
2843                       case TN_ECHO:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "ECHO ");
2846                         /* Reply only if this is a change, according
2847                            to the protocol rules. */
2848                         if (!remoteEchoOption) break;
2849                         EchoOn();
2850                         TelnetRequest(TN_DONT, TN_ECHO);
2851                         remoteEchoOption = FALSE;
2852                         break;
2853                       default:
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", (unsigned char) option);
2856                         /* Whatever this is, it must already be turned
2857                            off, because we never agree to turn on
2858                            anything non-default, so according to the
2859                            protocol rules, we don't reply. */
2860                         break;
2861                     }
2862                     break;
2863                   case TN_DO:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<DO ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       default:
2868                         /* Whatever this is, we refuse to do it. */
2869                         if (appData.debugMode)
2870                           fprintf(debugFP, "%d ", option);
2871                         TelnetRequest(TN_WONT, option);
2872                         break;
2873                     }
2874                     break;
2875                   case TN_DONT:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<DONT ");
2878                     switch (option = (unsigned char) buf[++i]) {
2879                       default:
2880                         if (appData.debugMode)
2881                           fprintf(debugFP, "%d ", option);
2882                         /* Whatever this is, we are already not doing
2883                            it, because we never agree to do anything
2884                            non-default, so according to the protocol
2885                            rules, we don't reply. */
2886                         break;
2887                     }
2888                     break;
2889                   case TN_IAC:
2890                     if (appData.debugMode)
2891                       fprintf(debugFP, "\n<IAC ");
2892                     /* Doubled IAC; pass it through */
2893                     i--;
2894                     break;
2895                   default:
2896                     if (appData.debugMode)
2897                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2898                     /* Drop all other telnet commands on the floor */
2899                     break;
2900                 }
2901                 if (oldi > next_out)
2902                   SendToPlayer(&buf[next_out], oldi - next_out);
2903                 if (++i > next_out)
2904                   next_out = i;
2905                 continue;
2906             }
2907
2908             /* OK, this at least will *usually* work */
2909             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2910                 loggedOn = TRUE;
2911             }
2912
2913             if (loggedOn && !intfSet) {
2914                 if (ics_type == ICS_ICC) {
2915                   snprintf(str, MSG_SIZ,
2916                           "/set-quietly interface %s\n/set-quietly style 12\n",
2917                           programVersion);
2918                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2920                 } else if (ics_type == ICS_CHESSNET) {
2921                   snprintf(str, MSG_SIZ, "/style 12\n");
2922                 } else {
2923                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2924                   strcat(str, programVersion);
2925                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2928 #ifdef WIN32
2929                   strcat(str, "$iset nohighlight 1\n");
2930 #endif
2931                   strcat(str, "$iset lock 1\n$style 12\n");
2932                 }
2933                 SendToICS(str);
2934                 NotifyFrontendLogin();
2935                 intfSet = TRUE;
2936             }
2937
2938             if (started == STARTED_COMMENT) {
2939                 /* Accumulate characters in comment */
2940                 parse[parse_pos++] = buf[i];
2941                 if (buf[i] == '\n') {
2942                     parse[parse_pos] = NULLCHAR;
2943                     if(chattingPartner>=0) {
2944                         char mess[MSG_SIZ];
2945                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2946                         OutputChatMessage(chattingPartner, mess);
2947                         chattingPartner = -1;
2948                         next_out = i+1; // [HGM] suppress printing in ICS window
2949                     } else
2950                     if(!suppressKibitz) // [HGM] kibitz
2951                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2952                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2953                         int nrDigit = 0, nrAlph = 0, j;
2954                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2955                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2956                         parse[parse_pos] = NULLCHAR;
2957                         // try to be smart: if it does not look like search info, it should go to
2958                         // ICS interaction window after all, not to engine-output window.
2959                         for(j=0; j<parse_pos; j++) { // count letters and digits
2960                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2961                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2962                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2963                         }
2964                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2965                             int depth=0; float score;
2966                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2967                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2968                                 pvInfoList[forwardMostMove-1].depth = depth;
2969                                 pvInfoList[forwardMostMove-1].score = 100*score;
2970                             }
2971                             OutputKibitz(suppressKibitz, parse);
2972                         } else {
2973                             char tmp[MSG_SIZ];
2974                             if(gameMode == IcsObserving) // restore original ICS messages
2975                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2976                             else
2977                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2978                             SendToPlayer(tmp, strlen(tmp));
2979                         }
2980                         next_out = i+1; // [HGM] suppress printing in ICS window
2981                     }
2982                     started = STARTED_NONE;
2983                 } else {
2984                     /* Don't match patterns against characters in comment */
2985                     i++;
2986                     continue;
2987                 }
2988             }
2989             if (started == STARTED_CHATTER) {
2990                 if (buf[i] != '\n') {
2991                     /* Don't match patterns against characters in chatter */
2992                     i++;
2993                     continue;
2994                 }
2995                 started = STARTED_NONE;
2996                 if(suppressKibitz) next_out = i+1;
2997             }
2998
2999             /* Kludge to deal with rcmd protocol */
3000             if (firstTime && looking_at(buf, &i, "\001*")) {
3001                 DisplayFatalError(&buf[1], 0, 1);
3002                 continue;
3003             } else {
3004                 firstTime = FALSE;
3005             }
3006
3007             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3008                 ics_type = ICS_ICC;
3009                 ics_prefix = "/";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3015                 ics_type = ICS_FICS;
3016                 ics_prefix = "$";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3022                 ics_type = ICS_CHESSNET;
3023                 ics_prefix = "/";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028
3029             if (!loggedOn &&
3030                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3031                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3032                  looking_at(buf, &i, "will be \"*\""))) {
3033               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3034               continue;
3035             }
3036
3037             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3038               char buf[MSG_SIZ];
3039               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3040               DisplayIcsInteractionTitle(buf);
3041               have_set_title = TRUE;
3042             }
3043
3044             /* skip finger notes */
3045             if (started == STARTED_NONE &&
3046                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3047                  (buf[i] == '1' && buf[i+1] == '0')) &&
3048                 buf[i+2] == ':' && buf[i+3] == ' ') {
3049               started = STARTED_CHATTER;
3050               i += 3;
3051               continue;
3052             }
3053
3054             oldi = i;
3055             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3056             if(appData.seekGraph) {
3057                 if(soughtPending && MatchSoughtLine(buf+i)) {
3058                     i = strstr(buf+i, "rated") - buf;
3059                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3060                     next_out = leftover_start = i;
3061                     started = STARTED_CHATTER;
3062                     suppressKibitz = TRUE;
3063                     continue;
3064                 }
3065                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3066                         && looking_at(buf, &i, "* ads displayed")) {
3067                     soughtPending = FALSE;
3068                     seekGraphUp = TRUE;
3069                     DrawSeekGraph();
3070                     continue;
3071                 }
3072                 if(appData.autoRefresh) {
3073                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3074                         int s = (ics_type == ICS_ICC); // ICC format differs
3075                         if(seekGraphUp)
3076                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3077                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3078                         looking_at(buf, &i, "*% "); // eat prompt
3079                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3080                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3081                         next_out = i; // suppress
3082                         continue;
3083                     }
3084                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3085                         char *p = star_match[0];
3086                         while(*p) {
3087                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3088                             while(*p && *p++ != ' '); // next
3089                         }
3090                         looking_at(buf, &i, "*% "); // eat prompt
3091                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3092                         next_out = i;
3093                         continue;
3094                     }
3095                 }
3096             }
3097
3098             /* skip formula vars */
3099             if (started == STARTED_NONE &&
3100                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3101               started = STARTED_CHATTER;
3102               i += 3;
3103               continue;
3104             }
3105
3106             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3107             if (appData.autoKibitz && started == STARTED_NONE &&
3108                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3109                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3110                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3111                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3112                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3113                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3114                         suppressKibitz = TRUE;
3115                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3116                         next_out = i;
3117                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3118                                 && (gameMode == IcsPlayingWhite)) ||
3119                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3120                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3121                             started = STARTED_CHATTER; // own kibitz we simply discard
3122                         else {
3123                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3124                             parse_pos = 0; parse[0] = NULLCHAR;
3125                             savingComment = TRUE;
3126                             suppressKibitz = gameMode != IcsObserving ? 2 :
3127                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3128                         }
3129                         continue;
3130                 } else
3131                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3132                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3133                          && atoi(star_match[0])) {
3134                     // suppress the acknowledgements of our own autoKibitz
3135                     char *p;
3136                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3137                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3138                     SendToPlayer(star_match[0], strlen(star_match[0]));
3139                     if(looking_at(buf, &i, "*% ")) // eat prompt
3140                         suppressKibitz = FALSE;
3141                     next_out = i;
3142                     continue;
3143                 }
3144             } // [HGM] kibitz: end of patch
3145
3146             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3147
3148             // [HGM] chat: intercept tells by users for which we have an open chat window
3149             channel = -1;
3150             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3151                                            looking_at(buf, &i, "* whispers:") ||
3152                                            looking_at(buf, &i, "* kibitzes:") ||
3153                                            looking_at(buf, &i, "* shouts:") ||
3154                                            looking_at(buf, &i, "* c-shouts:") ||
3155                                            looking_at(buf, &i, "--> * ") ||
3156                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3157                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3159                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3160                 int p;
3161                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3162                 chattingPartner = -1;
3163
3164                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3165                 for(p=0; p<MAX_CHAT; p++) {
3166                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3167                     talker[0] = '['; strcat(talker, "] ");
3168                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3169                     chattingPartner = p; break;
3170                     }
3171                 } else
3172                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(!strcmp("kibitzes", chatPartner[p])) {
3175                         talker[0] = '['; strcat(talker, "] ");
3176                         chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("whispers", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3187                   if(buf[i-8] == '-' && buf[i-3] == 't')
3188                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3189                     if(!strcmp("c-shouts", chatPartner[p])) {
3190                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3191                         chattingPartner = p; break;
3192                     }
3193                   }
3194                   if(chattingPartner < 0)
3195                   for(p=0; p<MAX_CHAT; p++) {
3196                     if(!strcmp("shouts", chatPartner[p])) {
3197                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3198                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3199                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3200                         chattingPartner = p; break;
3201                     }
3202                   }
3203                 }
3204                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3205                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3206                     talker[0] = 0; Colorize(ColorTell, FALSE);
3207                     chattingPartner = p; break;
3208                 }
3209                 if(chattingPartner<0) i = oldi; else {
3210                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3211                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3212                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213                     started = STARTED_COMMENT;
3214                     parse_pos = 0; parse[0] = NULLCHAR;
3215                     savingComment = 3 + chattingPartner; // counts as TRUE
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219             } // [HGM] chat: end of patch
3220
3221           backup = i;
3222             if (appData.zippyTalk || appData.zippyPlay) {
3223                 /* [DM] Backup address for color zippy lines */
3224 #if ZIPPY
3225                if (loggedOn == TRUE)
3226                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3227                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3228 #endif
3229             } // [DM] 'else { ' deleted
3230                 if (
3231                     /* Regular tells and says */
3232                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3233                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3234                     looking_at(buf, &i, "* says: ") ||
3235                     /* Don't color "message" or "messages" output */
3236                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3237                     looking_at(buf, &i, "*. * at *:*: ") ||
3238                     looking_at(buf, &i, "--* (*:*): ") ||
3239                     /* Message notifications (same color as tells) */
3240                     looking_at(buf, &i, "* has left a message ") ||
3241                     looking_at(buf, &i, "* just sent you a message:\n") ||
3242                     /* Whispers and kibitzes */
3243                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3244                     looking_at(buf, &i, "* kibitzes: ") ||
3245                     /* Channel tells */
3246                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3247
3248                   if (tkind == 1 && strchr(star_match[0], ':')) {
3249                       /* Avoid "tells you:" spoofs in channels */
3250                      tkind = 3;
3251                   }
3252                   if (star_match[0][0] == NULLCHAR ||
3253                       strchr(star_match[0], ' ') ||
3254                       (tkind == 3 && strchr(star_match[1], ' '))) {
3255                     /* Reject bogus matches */
3256                     i = oldi;
3257                   } else {
3258                     if (appData.colorize) {
3259                       if (oldi > next_out) {
3260                         SendToPlayer(&buf[next_out], oldi - next_out);
3261                         next_out = oldi;
3262                       }
3263                       switch (tkind) {
3264                       case 1:
3265                         Colorize(ColorTell, FALSE);
3266                         curColor = ColorTell;
3267                         break;
3268                       case 2:
3269                         Colorize(ColorKibitz, FALSE);
3270                         curColor = ColorKibitz;
3271                         break;
3272                       case 3:
3273                         p = strrchr(star_match[1], '(');
3274                         if (p == NULL) {
3275                           p = star_match[1];
3276                         } else {
3277                           p++;
3278                         }
3279                         if (atoi(p) == 1) {
3280                           Colorize(ColorChannel1, FALSE);
3281                           curColor = ColorChannel1;
3282                         } else {
3283                           Colorize(ColorChannel, FALSE);
3284                           curColor = ColorChannel;
3285                         }
3286                         break;
3287                       case 5:
3288                         curColor = ColorNormal;
3289                         break;
3290                       }
3291                     }
3292                     if (started == STARTED_NONE && appData.autoComment &&
3293                         (gameMode == IcsObserving ||
3294                          gameMode == IcsPlayingWhite ||
3295                          gameMode == IcsPlayingBlack)) {
3296                       parse_pos = i - oldi;
3297                       memcpy(parse, &buf[oldi], parse_pos);
3298                       parse[parse_pos] = NULLCHAR;
3299                       started = STARTED_COMMENT;
3300                       savingComment = TRUE;
3301                     } else {
3302                       started = STARTED_CHATTER;
3303                       savingComment = FALSE;
3304                     }
3305                     loggedOn = TRUE;
3306                     continue;
3307                   }
3308                 }
3309
3310                 if (looking_at(buf, &i, "* s-shouts: ") ||
3311                     looking_at(buf, &i, "* c-shouts: ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSShout, FALSE);
3318                         curColor = ColorSShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at(buf, &i, "--->")) {
3326                     loggedOn = TRUE;
3327                     continue;
3328                 }
3329
3330                 if (looking_at(buf, &i, "* shouts: ") ||
3331                     looking_at(buf, &i, "--> ")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorShout, FALSE);
3338                         curColor = ColorShout;
3339                     }
3340                     loggedOn = TRUE;
3341                     started = STARTED_CHATTER;
3342                     continue;
3343                 }
3344
3345                 if (looking_at( buf, &i, "Challenge:")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorChallenge, FALSE);
3352                         curColor = ColorChallenge;
3353                     }
3354                     loggedOn = TRUE;
3355                     continue;
3356                 }
3357
3358                 if (looking_at(buf, &i, "* offers you") ||
3359                     looking_at(buf, &i, "* offers to be") ||
3360                     looking_at(buf, &i, "* would like to") ||
3361                     looking_at(buf, &i, "* requests to") ||
3362                     looking_at(buf, &i, "Your opponent offers") ||
3363                     looking_at(buf, &i, "Your opponent requests")) {
3364
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorRequest, FALSE);
3371                         curColor = ColorRequest;
3372                     }
3373                     continue;
3374                 }
3375
3376                 if (looking_at(buf, &i, "* (*) seeking")) {
3377                     if (appData.colorize) {
3378                         if (oldi > next_out) {
3379                             SendToPlayer(&buf[next_out], oldi - next_out);
3380                             next_out = oldi;
3381                         }
3382                         Colorize(ColorSeek, FALSE);
3383                         curColor = ColorSeek;
3384                     }
3385                     continue;
3386             }
3387
3388           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3389
3390             if (looking_at(buf, &i, "\\   ")) {
3391                 if (prevColor != ColorNormal) {
3392                     if (oldi > next_out) {
3393                         SendToPlayer(&buf[next_out], oldi - next_out);
3394                         next_out = oldi;
3395                     }
3396                     Colorize(prevColor, TRUE);
3397                     curColor = prevColor;
3398                 }
3399                 if (savingComment) {
3400                     parse_pos = i - oldi;
3401                     memcpy(parse, &buf[oldi], parse_pos);
3402                     parse[parse_pos] = NULLCHAR;
3403                     started = STARTED_COMMENT;
3404                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3405                         chattingPartner = savingComment - 3; // kludge to remember the box
3406                 } else {
3407                     started = STARTED_CHATTER;
3408                 }
3409                 continue;
3410             }
3411
3412             if (looking_at(buf, &i, "Black Strength :") ||
3413                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3414                 looking_at(buf, &i, "<10>") ||
3415                 looking_at(buf, &i, "#@#")) {
3416                 /* Wrong board style */
3417                 loggedOn = TRUE;
3418                 SendToICS(ics_prefix);
3419                 SendToICS("set style 12\n");
3420                 SendToICS(ics_prefix);
3421                 SendToICS("refresh\n");
3422                 continue;
3423             }
3424
3425             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3426                 ICSInitScript();
3427                 have_sent_ICS_logon = 1;
3428                 continue;
3429             }
3430
3431             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3432                 (looking_at(buf, &i, "\n<12> ") ||
3433                  looking_at(buf, &i, "<12> "))) {
3434                 loggedOn = TRUE;
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_BOARD;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3445                 looking_at(buf, &i, "<b1> ")) {
3446                 if (oldi > next_out) {
3447                     SendToPlayer(&buf[next_out], oldi - next_out);
3448                 }
3449                 next_out = i;
3450                 started = STARTED_HOLDINGS;
3451                 parse_pos = 0;
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3456                 loggedOn = TRUE;
3457                 /* Header for a move list -- first line */
3458
3459                 switch (ics_getting_history) {
3460                   case H_FALSE:
3461                     switch (gameMode) {
3462                       case IcsIdle:
3463                       case BeginningOfGame:
3464                         /* User typed "moves" or "oldmoves" while we
3465                            were idle.  Pretend we asked for these
3466                            moves and soak them up so user can step
3467                            through them and/or save them.
3468                            */
3469                         Reset(FALSE, TRUE);
3470                         gameMode = IcsObserving;
3471                         ModeHighlight();
3472                         ics_gamenum = -1;
3473                         ics_getting_history = H_GOT_UNREQ_HEADER;
3474                         break;
3475                       case EditGame: /*?*/
3476                       case EditPosition: /*?*/
3477                         /* Should above feature work in these modes too? */
3478                         /* For now it doesn't */
3479                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3480                         break;
3481                       default:
3482                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3483                         break;
3484                     }
3485                     break;
3486                   case H_REQUESTED:
3487                     /* Is this the right one? */
3488                     if (gameInfo.white && gameInfo.black &&
3489                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3490                         strcmp(gameInfo.black, star_match[2]) == 0) {
3491                         /* All is well */
3492                         ics_getting_history = H_GOT_REQ_HEADER;
3493                     }
3494                     break;
3495                   case H_GOT_REQ_HEADER:
3496                   case H_GOT_UNREQ_HEADER:
3497                   case H_GOT_UNWANTED_HEADER:
3498                   case H_GETTING_MOVES:
3499                     /* Should not happen */
3500                     DisplayError(_("Error gathering move list: two headers"), 0);
3501                     ics_getting_history = H_FALSE;
3502                     break;
3503                 }
3504
3505                 /* Save player ratings into gameInfo if needed */
3506                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3507                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3508                     (gameInfo.whiteRating == -1 ||
3509                      gameInfo.blackRating == -1)) {
3510
3511                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3512                     gameInfo.blackRating = string_to_rating(star_match[3]);
3513                     if (appData.debugMode)
3514                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3515                               gameInfo.whiteRating, gameInfo.blackRating);
3516                 }
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i,
3521               "* * match, initial time: * minute*, increment: * second")) {
3522                 /* Header for a move list -- second line */
3523                 /* Initial board will follow if this is a wild game */
3524                 if (gameInfo.event != NULL) free(gameInfo.event);
3525                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3526                 gameInfo.event = StrSave(str);
3527                 /* [HGM] we switched variant. Translate boards if needed. */
3528                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3529                 continue;
3530             }
3531
3532             if (looking_at(buf, &i, "Move  ")) {
3533                 /* Beginning of a move list */
3534                 switch (ics_getting_history) {
3535                   case H_FALSE:
3536                     /* Normally should not happen */
3537                     /* Maybe user hit reset while we were parsing */
3538                     break;
3539                   case H_REQUESTED:
3540                     /* Happens if we are ignoring a move list that is not
3541                      * the one we just requested.  Common if the user
3542                      * tries to observe two games without turning off
3543                      * getMoveList */
3544                     break;
3545                   case H_GETTING_MOVES:
3546                     /* Should not happen */
3547                     DisplayError(_("Error gathering move list: nested"), 0);
3548                     ics_getting_history = H_FALSE;
3549                     break;
3550                   case H_GOT_REQ_HEADER:
3551                     ics_getting_history = H_GETTING_MOVES;
3552                     started = STARTED_MOVES;
3553                     parse_pos = 0;
3554                     if (oldi > next_out) {
3555                         SendToPlayer(&buf[next_out], oldi - next_out);
3556                     }
3557                     break;
3558                   case H_GOT_UNREQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES_NOHIDE;
3561                     parse_pos = 0;
3562                     break;
3563                   case H_GOT_UNWANTED_HEADER:
3564                     ics_getting_history = H_FALSE;
3565                     break;
3566                 }
3567                 continue;
3568             }
3569
3570             if (looking_at(buf, &i, "% ") ||
3571                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3572                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3573                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3574                     soughtPending = FALSE;
3575                     seekGraphUp = TRUE;
3576                     DrawSeekGraph();
3577                 }
3578                 if(suppressKibitz) next_out = i;
3579                 savingComment = FALSE;
3580                 suppressKibitz = 0;
3581                 switch (started) {
3582                   case STARTED_MOVES:
3583                   case STARTED_MOVES_NOHIDE:
3584                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3585                     parse[parse_pos + i - oldi] = NULLCHAR;
3586                     ParseGameHistory(parse);
3587 #if ZIPPY
3588                     if (appData.zippyPlay && first.initDone) {
3589                         FeedMovesToProgram(&first, forwardMostMove);
3590                         if (gameMode == IcsPlayingWhite) {
3591                             if (WhiteOnMove(forwardMostMove)) {
3592                                 if (first.sendTime) {
3593                                   if (first.useColors) {
3594                                     SendToProgram("black\n", &first);
3595                                   }
3596                                   SendTimeRemaining(&first, TRUE);
3597                                 }
3598                                 if (first.useColors) {
3599                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3600                                 }
3601                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3602                                 first.maybeThinking = TRUE;
3603                             } else {
3604                                 if (first.usePlayother) {
3605                                   if (first.sendTime) {
3606                                     SendTimeRemaining(&first, TRUE);
3607                                   }
3608                                   SendToProgram("playother\n", &first);
3609                                   firstMove = FALSE;
3610                                 } else {
3611                                   firstMove = TRUE;
3612                                 }
3613                             }
3614                         } else if (gameMode == IcsPlayingBlack) {
3615                             if (!WhiteOnMove(forwardMostMove)) {
3616                                 if (first.sendTime) {
3617                                   if (first.useColors) {
3618                                     SendToProgram("white\n", &first);
3619                                   }
3620                                   SendTimeRemaining(&first, FALSE);
3621                                 }
3622                                 if (first.useColors) {
3623                                   SendToProgram("black\n", &first);
3624                                 }
3625                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3626                                 first.maybeThinking = TRUE;
3627                             } else {
3628                                 if (first.usePlayother) {
3629                                   if (first.sendTime) {
3630                                     SendTimeRemaining(&first, FALSE);
3631                                   }
3632                                   SendToProgram("playother\n", &first);
3633                                   firstMove = FALSE;
3634                                 } else {
3635                                   firstMove = TRUE;
3636                                 }
3637                             }
3638                         }
3639                     }
3640 #endif
3641                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3642                         /* Moves came from oldmoves or moves command
3643                            while we weren't doing anything else.
3644                            */
3645                         currentMove = forwardMostMove;
3646                         ClearHighlights();/*!!could figure this out*/
3647                         flipView = appData.flipView;
3648                         DrawPosition(TRUE, boards[currentMove]);
3649                         DisplayBothClocks();
3650                         snprintf(str, MSG_SIZ, "%s %s %s",
3651                                 gameInfo.white, _("vs."),  gameInfo.black);
3652                         DisplayTitle(str);
3653                         gameMode = IcsIdle;
3654                     } else {
3655                         /* Moves were history of an active game */
3656                         if (gameInfo.resultDetails != NULL) {
3657                             free(gameInfo.resultDetails);
3658                             gameInfo.resultDetails = NULL;
3659                         }
3660                     }
3661                     HistorySet(parseList, backwardMostMove,
3662                                forwardMostMove, currentMove-1);
3663                     DisplayMove(currentMove - 1);
3664                     if (started == STARTED_MOVES) next_out = i;
3665                     started = STARTED_NONE;
3666                     ics_getting_history = H_FALSE;
3667                     break;
3668
3669                   case STARTED_OBSERVE:
3670                     started = STARTED_NONE;
3671                     SendToICS(ics_prefix);
3672                     SendToICS("refresh\n");
3673                     break;
3674
3675                   default:
3676                     break;
3677                 }
3678                 if(bookHit) { // [HGM] book: simulate book reply
3679                     static char bookMove[MSG_SIZ]; // a bit generous?
3680
3681                     programStats.nodes = programStats.depth = programStats.time =
3682                     programStats.score = programStats.got_only_move = 0;
3683                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3684
3685                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3686                     strcat(bookMove, bookHit);
3687                     HandleMachineMove(bookMove, &first);
3688                 }
3689                 continue;
3690             }
3691
3692             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3693                  started == STARTED_HOLDINGS ||
3694                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3695                 /* Accumulate characters in move list or board */
3696                 parse[parse_pos++] = buf[i];
3697             }
3698
3699             /* Start of game messages.  Mostly we detect start of game
3700                when the first board image arrives.  On some versions
3701                of the ICS, though, we need to do a "refresh" after starting
3702                to observe in order to get the current board right away. */
3703             if (looking_at(buf, &i, "Adding game * to observation list")) {
3704                 started = STARTED_OBSERVE;
3705                 continue;
3706             }
3707
3708             /* Handle auto-observe */
3709             if (appData.autoObserve &&
3710                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3711                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3712                 char *player;
3713                 /* Choose the player that was highlighted, if any. */
3714                 if (star_match[0][0] == '\033' ||
3715                     star_match[1][0] != '\033') {
3716                     player = star_match[0];
3717                 } else {
3718                     player = star_match[2];
3719                 }
3720                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3721                         ics_prefix, StripHighlightAndTitle(player));
3722                 SendToICS(str);
3723
3724                 /* Save ratings from notify string */
3725                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3726                 player1Rating = string_to_rating(star_match[1]);
3727                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3728                 player2Rating = string_to_rating(star_match[3]);
3729
3730                 if (appData.debugMode)
3731                   fprintf(debugFP,
3732                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3733                           player1Name, player1Rating,
3734                           player2Name, player2Rating);
3735
3736                 continue;
3737             }
3738
3739             /* Deal with automatic examine mode after a game,
3740                and with IcsObserving -> IcsExamining transition */
3741             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3742                 looking_at(buf, &i, "has made you an examiner of game *")) {
3743
3744                 int gamenum = atoi(star_match[0]);
3745                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3746                     gamenum == ics_gamenum) {
3747                     /* We were already playing or observing this game;
3748                        no need to refetch history */
3749                     gameMode = IcsExamining;
3750                     if (pausing) {
3751                         pauseExamForwardMostMove = forwardMostMove;
3752                     } else if (currentMove < forwardMostMove) {
3753                         ForwardInner(forwardMostMove);
3754                     }
3755                 } else {
3756                     /* I don't think this case really can happen */
3757                     SendToICS(ics_prefix);
3758                     SendToICS("refresh\n");
3759                 }
3760                 continue;
3761             }
3762
3763             /* Error messages */
3764 //          if (ics_user_moved) {
3765             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3766                 if (looking_at(buf, &i, "Illegal move") ||
3767                     looking_at(buf, &i, "Not a legal move") ||
3768                     looking_at(buf, &i, "Your king is in check") ||
3769                     looking_at(buf, &i, "It isn't your turn") ||
3770                     looking_at(buf, &i, "It is not your move")) {
3771                     /* Illegal move */
3772                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3773                         currentMove = forwardMostMove-1;
3774                         DisplayMove(currentMove - 1); /* before DMError */
3775                         DrawPosition(FALSE, boards[currentMove]);
3776                         SwitchClocks(forwardMostMove-1); // [HGM] race
3777                         DisplayBothClocks();
3778                     }
3779                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3780                     ics_user_moved = 0;
3781                     continue;
3782                 }
3783             }
3784
3785             if (looking_at(buf, &i, "still have time") ||
3786                 looking_at(buf, &i, "not out of time") ||
3787                 looking_at(buf, &i, "either player is out of time") ||
3788                 looking_at(buf, &i, "has timeseal; checking")) {
3789                 /* We must have called his flag a little too soon */
3790                 whiteFlag = blackFlag = FALSE;
3791                 continue;
3792             }
3793
3794             if (looking_at(buf, &i, "added * seconds to") ||
3795                 looking_at(buf, &i, "seconds were added to")) {
3796                 /* Update the clocks */
3797                 SendToICS(ics_prefix);
3798                 SendToICS("refresh\n");
3799                 continue;
3800             }
3801
3802             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3803                 ics_clock_paused = TRUE;
3804                 StopClocks();
3805                 continue;
3806             }
3807
3808             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3809                 ics_clock_paused = FALSE;
3810                 StartClocks();
3811                 continue;
3812             }
3813
3814             /* Grab player ratings from the Creating: message.
3815                Note we have to check for the special case when
3816                the ICS inserts things like [white] or [black]. */
3817             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3818                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3819                 /* star_matches:
3820                    0    player 1 name (not necessarily white)
3821                    1    player 1 rating
3822                    2    empty, white, or black (IGNORED)
3823                    3    player 2 name (not necessarily black)
3824                    4    player 2 rating
3825
3826                    The names/ratings are sorted out when the game
3827                    actually starts (below).
3828                 */
3829                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3830                 player1Rating = string_to_rating(star_match[1]);
3831                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3832                 player2Rating = string_to_rating(star_match[4]);
3833
3834                 if (appData.debugMode)
3835                   fprintf(debugFP,
3836                           "Ratings from 'Creating:' %s %d, %s %d\n",
3837                           player1Name, player1Rating,
3838                           player2Name, player2Rating);
3839
3840                 continue;
3841             }
3842
3843             /* Improved generic start/end-of-game messages */
3844             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3845                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3846                 /* If tkind == 0: */
3847                 /* star_match[0] is the game number */
3848                 /*           [1] is the white player's name */
3849                 /*           [2] is the black player's name */
3850                 /* For end-of-game: */
3851                 /*           [3] is the reason for the game end */
3852                 /*           [4] is a PGN end game-token, preceded by " " */
3853                 /* For start-of-game: */
3854                 /*           [3] begins with "Creating" or "Continuing" */
3855                 /*           [4] is " *" or empty (don't care). */
3856                 int gamenum = atoi(star_match[0]);
3857                 char *whitename, *blackname, *why, *endtoken;
3858                 ChessMove endtype = EndOfFile;
3859
3860                 if (tkind == 0) {
3861                   whitename = star_match[1];
3862                   blackname = star_match[2];
3863                   why = star_match[3];
3864                   endtoken = star_match[4];
3865                 } else {
3866                   whitename = star_match[1];
3867                   blackname = star_match[3];
3868                   why = star_match[5];
3869                   endtoken = star_match[6];
3870                 }
3871
3872                 /* Game start messages */
3873                 if (strncmp(why, "Creating ", 9) == 0 ||
3874                     strncmp(why, "Continuing ", 11) == 0) {
3875                     gs_gamenum = gamenum;
3876                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3877                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3878                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3879 #if ZIPPY
3880                     if (appData.zippyPlay) {
3881                         ZippyGameStart(whitename, blackname);
3882                     }
3883 #endif /*ZIPPY*/
3884                     partnerBoardValid = FALSE; // [HGM] bughouse
3885                     continue;
3886                 }
3887
3888                 /* Game end messages */
3889                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3890                     ics_gamenum != gamenum) {
3891                     continue;
3892                 }
3893                 while (endtoken[0] == ' ') endtoken++;
3894                 switch (endtoken[0]) {
3895                   case '*':
3896                   default:
3897                     endtype = GameUnfinished;
3898                     break;
3899                   case '0':
3900                     endtype = BlackWins;
3901                     break;
3902                   case '1':
3903                     if (endtoken[1] == '/')
3904                       endtype = GameIsDrawn;
3905                     else
3906                       endtype = WhiteWins;
3907                     break;
3908                 }
3909                 GameEnds(endtype, why, GE_ICS);
3910 #if ZIPPY
3911                 if (appData.zippyPlay && first.initDone) {
3912                     ZippyGameEnd(endtype, why);
3913                     if (first.pr == NoProc) {
3914                       /* Start the next process early so that we'll
3915                          be ready for the next challenge */
3916                       StartChessProgram(&first);
3917                     }
3918                     /* Send "new" early, in case this command takes
3919                        a long time to finish, so that we'll be ready
3920                        for the next challenge. */
3921                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3922                     Reset(TRUE, TRUE);
3923                 }
3924 #endif /*ZIPPY*/
3925                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "Removing game * from observation") ||
3930                 looking_at(buf, &i, "no longer observing game *") ||
3931                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3932                 if (gameMode == IcsObserving &&
3933                     atoi(star_match[0]) == ics_gamenum)
3934                   {
3935                       /* icsEngineAnalyze */
3936                       if (appData.icsEngineAnalyze) {
3937                             ExitAnalyzeMode();
3938                             ModeHighlight();
3939                       }
3940                       StopClocks();
3941                       gameMode = IcsIdle;
3942                       ics_gamenum = -1;
3943                       ics_user_moved = FALSE;
3944                   }
3945                 continue;
3946             }
3947
3948             if (looking_at(buf, &i, "no longer examining game *")) {
3949                 if (gameMode == IcsExamining &&
3950                     atoi(star_match[0]) == ics_gamenum)
3951                   {
3952                       gameMode = IcsIdle;
3953                       ics_gamenum = -1;
3954                       ics_user_moved = FALSE;
3955                   }
3956                 continue;
3957             }
3958
3959             /* Advance leftover_start past any newlines we find,
3960                so only partial lines can get reparsed */
3961             if (looking_at(buf, &i, "\n")) {
3962                 prevColor = curColor;
3963                 if (curColor != ColorNormal) {
3964                     if (oldi > next_out) {
3965                         SendToPlayer(&buf[next_out], oldi - next_out);
3966                         next_out = oldi;
3967                     }
3968                     Colorize(ColorNormal, FALSE);
3969                     curColor = ColorNormal;
3970                 }
3971                 if (started == STARTED_BOARD) {
3972                     started = STARTED_NONE;
3973                     parse[parse_pos] = NULLCHAR;
3974                     ParseBoard12(parse);
3975                     ics_user_moved = 0;
3976
3977                     /* Send premove here */
3978                     if (appData.premove) {
3979                       char str[MSG_SIZ];
3980                       if (currentMove == 0 &&
3981                           gameMode == IcsPlayingWhite &&
3982                           appData.premoveWhite) {
3983                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3984                         if (appData.debugMode)
3985                           fprintf(debugFP, "Sending premove:\n");
3986                         SendToICS(str);
3987                       } else if (currentMove == 1 &&
3988                                  gameMode == IcsPlayingBlack &&
3989                                  appData.premoveBlack) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (gotPremove) {
3995                         gotPremove = 0;
3996                         ClearPremoveHighlights();
3997                         if (appData.debugMode)
3998                           fprintf(debugFP, "Sending premove:\n");
3999                           UserMoveEvent(premoveFromX, premoveFromY,
4000                                         premoveToX, premoveToY,
4001                                         premovePromoChar);
4002                       }
4003                     }
4004
4005                     /* Usually suppress following prompt */
4006                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4007                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4008                         if (looking_at(buf, &i, "*% ")) {
4009                             savingComment = FALSE;
4010                             suppressKibitz = 0;
4011                         }
4012                     }
4013                     next_out = i;
4014                 } else if (started == STARTED_HOLDINGS) {
4015                     int gamenum;
4016                     char new_piece[MSG_SIZ];
4017                     started = STARTED_NONE;
4018                     parse[parse_pos] = NULLCHAR;
4019                     if (appData.debugMode)
4020                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4021                                                         parse, currentMove);
4022                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4023                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4024                         if (gameInfo.variant == VariantNormal) {
4025                           /* [HGM] We seem to switch variant during a game!
4026                            * Presumably no holdings were displayed, so we have
4027                            * to move the position two files to the right to
4028                            * create room for them!
4029                            */
4030                           VariantClass newVariant;
4031                           switch(gameInfo.boardWidth) { // base guess on board width
4032                                 case 9:  newVariant = VariantShogi; break;
4033                                 case 10: newVariant = VariantGreat; break;
4034                                 default: newVariant = VariantCrazyhouse; break;
4035                           }
4036                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4037                           /* Get a move list just to see the header, which
4038                              will tell us whether this is really bug or zh */
4039                           if (ics_getting_history == H_FALSE) {
4040                             ics_getting_history = H_REQUESTED;
4041                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4042                             SendToICS(str);
4043                           }
4044                         }
4045                         new_piece[0] = NULLCHAR;
4046                         sscanf(parse, "game %d white [%s black [%s <- %s",
4047                                &gamenum, white_holding, black_holding,
4048                                new_piece);
4049                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4050                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4051                         /* [HGM] copy holdings to board holdings area */
4052                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4053                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4054                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4055 #if ZIPPY
4056                         if (appData.zippyPlay && first.initDone) {
4057                             ZippyHoldings(white_holding, black_holding,
4058                                           new_piece);
4059                         }
4060 #endif /*ZIPPY*/
4061                         if (tinyLayout || smallLayout) {
4062                             char wh[16], bh[16];
4063                             PackHolding(wh, white_holding);
4064                             PackHolding(bh, black_holding);
4065                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4066                                     gameInfo.white, gameInfo.black);
4067                         } else {
4068                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4069                                     gameInfo.white, white_holding, _("vs."),
4070                                     gameInfo.black, black_holding);
4071                         }
4072                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4073                         DrawPosition(FALSE, boards[currentMove]);
4074                         DisplayTitle(str);
4075                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4076                         sscanf(parse, "game %d white [%s black [%s <- %s",
4077                                &gamenum, white_holding, black_holding,
4078                                new_piece);
4079                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4080                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4081                         /* [HGM] copy holdings to partner-board holdings area */
4082                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4083                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4084                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4085                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4086                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4087                       }
4088                     }
4089                     /* Suppress following prompt */
4090                     if (looking_at(buf, &i, "*% ")) {
4091                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4092                         savingComment = FALSE;
4093                         suppressKibitz = 0;
4094                     }
4095                     next_out = i;
4096                 }
4097                 continue;
4098             }
4099
4100             i++;                /* skip unparsed character and loop back */
4101         }
4102
4103         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4104 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4105 //          SendToPlayer(&buf[next_out], i - next_out);
4106             started != STARTED_HOLDINGS && leftover_start > next_out) {
4107             SendToPlayer(&buf[next_out], leftover_start - next_out);
4108             next_out = i;
4109         }
4110
4111         leftover_len = buf_len - leftover_start;
4112         /* if buffer ends with something we couldn't parse,
4113            reparse it after appending the next read */
4114
4115     } else if (count == 0) {
4116         RemoveInputSource(isr);
4117         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4118     } else {
4119         DisplayFatalError(_("Error reading from ICS"), error, 1);
4120     }
4121 }
4122
4123
4124 /* Board style 12 looks like this:
4125
4126    <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
4127
4128  * The "<12> " is stripped before it gets to this routine.  The two
4129  * trailing 0's (flip state and clock ticking) are later addition, and
4130  * some chess servers may not have them, or may have only the first.
4131  * Additional trailing fields may be added in the future.
4132  */
4133
4134 #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"
4135
4136 #define RELATION_OBSERVING_PLAYED    0
4137 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4138 #define RELATION_PLAYING_MYMOVE      1
4139 #define RELATION_PLAYING_NOTMYMOVE  -1
4140 #define RELATION_EXAMINING           2
4141 #define RELATION_ISOLATED_BOARD     -3
4142 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4143
4144 void
4145 ParseBoard12 (char *string)
4146 {
4147     GameMode newGameMode;
4148     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4149     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4150     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4151     char to_play, board_chars[200];
4152     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4153     char black[32], white[32];
4154     Board board;
4155     int prevMove = currentMove;
4156     int ticking = 2;
4157     ChessMove moveType;
4158     int fromX, fromY, toX, toY;
4159     char promoChar;
4160     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4161     char *bookHit = NULL; // [HGM] book
4162     Boolean weird = FALSE, reqFlag = FALSE;
4163
4164     fromX = fromY = toX = toY = -1;
4165
4166     newGame = FALSE;
4167
4168     if (appData.debugMode)
4169       fprintf(debugFP, _("Parsing board: %s\n"), string);
4170
4171     move_str[0] = NULLCHAR;
4172     elapsed_time[0] = NULLCHAR;
4173     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4174         int  i = 0, j;
4175         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4176             if(string[i] == ' ') { ranks++; files = 0; }
4177             else files++;
4178             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4179             i++;
4180         }
4181         for(j = 0; j <i; j++) board_chars[j] = string[j];
4182         board_chars[i] = '\0';
4183         string += i + 1;
4184     }
4185     n = sscanf(string, PATTERN, &to_play, &double_push,
4186                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4187                &gamenum, white, black, &relation, &basetime, &increment,
4188                &white_stren, &black_stren, &white_time, &black_time,
4189                &moveNum, str, elapsed_time, move_str, &ics_flip,
4190                &ticking);
4191
4192     if (n < 21) {
4193         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4194         DisplayError(str, 0);
4195         return;
4196     }
4197
4198     /* Convert the move number to internal form */
4199     moveNum = (moveNum - 1) * 2;
4200     if (to_play == 'B') moveNum++;
4201     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4202       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4203                         0, 1);
4204       return;
4205     }
4206
4207     switch (relation) {
4208       case RELATION_OBSERVING_PLAYED:
4209       case RELATION_OBSERVING_STATIC:
4210         if (gamenum == -1) {
4211             /* Old ICC buglet */
4212             relation = RELATION_OBSERVING_STATIC;
4213         }
4214         newGameMode = IcsObserving;
4215         break;
4216       case RELATION_PLAYING_MYMOVE:
4217       case RELATION_PLAYING_NOTMYMOVE:
4218         newGameMode =
4219           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4220             IcsPlayingWhite : IcsPlayingBlack;
4221         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4222         break;
4223       case RELATION_EXAMINING:
4224         newGameMode = IcsExamining;
4225         break;
4226       case RELATION_ISOLATED_BOARD:
4227       default:
4228         /* Just display this board.  If user was doing something else,
4229            we will forget about it until the next board comes. */
4230         newGameMode = IcsIdle;
4231         break;
4232       case RELATION_STARTING_POSITION:
4233         newGameMode = gameMode;
4234         break;
4235     }
4236
4237     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4238          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4239       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4240       char *toSqr;
4241       for (k = 0; k < ranks; k++) {
4242         for (j = 0; j < files; j++)
4243           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4244         if(gameInfo.holdingsWidth > 1) {
4245              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4246              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4247         }
4248       }
4249       CopyBoard(partnerBoard, board);
4250       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4251         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4252         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4253       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4254       if(toSqr = strchr(str, '-')) {
4255         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4256         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4257       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4258       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4259       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4260       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4261       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4262       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4263                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4264       DisplayMessage(partnerStatus, "");
4265         partnerBoardValid = TRUE;
4266       return;
4267     }
4268
4269     /* Modify behavior for initial board display on move listing
4270        of wild games.
4271        */
4272     switch (ics_getting_history) {
4273       case H_FALSE:
4274       case H_REQUESTED:
4275         break;
4276       case H_GOT_REQ_HEADER:
4277       case H_GOT_UNREQ_HEADER:
4278         /* This is the initial position of the current game */
4279         gamenum = ics_gamenum;
4280         moveNum = 0;            /* old ICS bug workaround */
4281         if (to_play == 'B') {
4282           startedFromSetupPosition = TRUE;
4283           blackPlaysFirst = TRUE;
4284           moveNum = 1;
4285           if (forwardMostMove == 0) forwardMostMove = 1;
4286           if (backwardMostMove == 0) backwardMostMove = 1;
4287           if (currentMove == 0) currentMove = 1;
4288         }
4289         newGameMode = gameMode;
4290         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4291         break;
4292       case H_GOT_UNWANTED_HEADER:
4293         /* This is an initial board that we don't want */
4294         return;
4295       case H_GETTING_MOVES:
4296         /* Should not happen */
4297         DisplayError(_("Error gathering move list: extra board"), 0);
4298         ics_getting_history = H_FALSE;
4299         return;
4300     }
4301
4302    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4303                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4304      /* [HGM] We seem to have switched variant unexpectedly
4305       * Try to guess new variant from board size
4306       */
4307           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4308           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4309           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4310           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4311           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4312           if(!weird) newVariant = VariantNormal;
4313           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4314           /* Get a move list just to see the header, which
4315              will tell us whether this is really bug or zh */
4316           if (ics_getting_history == H_FALSE) {
4317             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4318             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4319             SendToICS(str);
4320           }
4321     }
4322
4323     /* Take action if this is the first board of a new game, or of a
4324        different game than is currently being displayed.  */
4325     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4326         relation == RELATION_ISOLATED_BOARD) {
4327
4328         /* Forget the old game and get the history (if any) of the new one */
4329         if (gameMode != BeginningOfGame) {
4330           Reset(TRUE, TRUE);
4331         }
4332         newGame = TRUE;
4333         if (appData.autoRaiseBoard) BoardToTop();
4334         prevMove = -3;
4335         if (gamenum == -1) {
4336             newGameMode = IcsIdle;
4337         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4338                    appData.getMoveList && !reqFlag) {
4339             /* Need to get game history */
4340             ics_getting_history = H_REQUESTED;
4341             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4342             SendToICS(str);
4343         }
4344
4345         /* Initially flip the board to have black on the bottom if playing
4346            black or if the ICS flip flag is set, but let the user change
4347            it with the Flip View button. */
4348         flipView = appData.autoFlipView ?
4349           (newGameMode == IcsPlayingBlack) || ics_flip :
4350           appData.flipView;
4351
4352         /* Done with values from previous mode; copy in new ones */
4353         gameMode = newGameMode;
4354         ModeHighlight();
4355         ics_gamenum = gamenum;
4356         if (gamenum == gs_gamenum) {
4357             int klen = strlen(gs_kind);
4358             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4359             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4360             gameInfo.event = StrSave(str);
4361         } else {
4362             gameInfo.event = StrSave("ICS game");
4363         }
4364         gameInfo.site = StrSave(appData.icsHost);
4365         gameInfo.date = PGNDate();
4366         gameInfo.round = StrSave("-");
4367         gameInfo.white = StrSave(white);
4368         gameInfo.black = StrSave(black);
4369         timeControl = basetime * 60 * 1000;
4370         timeControl_2 = 0;
4371         timeIncrement = increment * 1000;
4372         movesPerSession = 0;
4373         gameInfo.timeControl = TimeControlTagValue();
4374         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4377     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4378     setbuf(debugFP, NULL);
4379   }
4380
4381         gameInfo.outOfBook = NULL;
4382
4383         /* Do we have the ratings? */
4384         if (strcmp(player1Name, white) == 0 &&
4385             strcmp(player2Name, black) == 0) {
4386             if (appData.debugMode)
4387               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4388                       player1Rating, player2Rating);
4389             gameInfo.whiteRating = player1Rating;
4390             gameInfo.blackRating = player2Rating;
4391         } else if (strcmp(player2Name, white) == 0 &&
4392                    strcmp(player1Name, black) == 0) {
4393             if (appData.debugMode)
4394               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4395                       player2Rating, player1Rating);
4396             gameInfo.whiteRating = player2Rating;
4397             gameInfo.blackRating = player1Rating;
4398         }
4399         player1Name[0] = player2Name[0] = NULLCHAR;
4400
4401         /* Silence shouts if requested */
4402         if (appData.quietPlay &&
4403             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4404             SendToICS(ics_prefix);
4405             SendToICS("set shout 0\n");
4406         }
4407     }
4408
4409     /* Deal with midgame name changes */
4410     if (!newGame) {
4411         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4412             if (gameInfo.white) free(gameInfo.white);
4413             gameInfo.white = StrSave(white);
4414         }
4415         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4416             if (gameInfo.black) free(gameInfo.black);
4417             gameInfo.black = StrSave(black);
4418         }
4419     }
4420
4421     /* Throw away game result if anything actually changes in examine mode */
4422     if (gameMode == IcsExamining && !newGame) {
4423         gameInfo.result = GameUnfinished;
4424         if (gameInfo.resultDetails != NULL) {
4425             free(gameInfo.resultDetails);
4426             gameInfo.resultDetails = NULL;
4427         }
4428     }
4429
4430     /* In pausing && IcsExamining mode, we ignore boards coming
4431        in if they are in a different variation than we are. */
4432     if (pauseExamInvalid) return;
4433     if (pausing && gameMode == IcsExamining) {
4434         if (moveNum <= pauseExamForwardMostMove) {
4435             pauseExamInvalid = TRUE;
4436             forwardMostMove = pauseExamForwardMostMove;
4437             return;
4438         }
4439     }
4440
4441   if (appData.debugMode) {
4442     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4443   }
4444     /* Parse the board */
4445     for (k = 0; k < ranks; k++) {
4446       for (j = 0; j < files; j++)
4447         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4448       if(gameInfo.holdingsWidth > 1) {
4449            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4450            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4451       }
4452     }
4453     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4454       board[5][BOARD_RGHT+1] = WhiteAngel;
4455       board[6][BOARD_RGHT+1] = WhiteMarshall;
4456       board[1][0] = BlackMarshall;
4457       board[2][0] = BlackAngel;
4458       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4459     }
4460     CopyBoard(boards[moveNum], board);
4461     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4462     if (moveNum == 0) {
4463         startedFromSetupPosition =
4464           !CompareBoards(board, initialPosition);
4465         if(startedFromSetupPosition)
4466             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4467     }
4468
4469     /* [HGM] Set castling rights. Take the outermost Rooks,
4470        to make it also work for FRC opening positions. Note that board12
4471        is really defective for later FRC positions, as it has no way to
4472        indicate which Rook can castle if they are on the same side of King.
4473        For the initial position we grant rights to the outermost Rooks,
4474        and remember thos rights, and we then copy them on positions
4475        later in an FRC game. This means WB might not recognize castlings with
4476        Rooks that have moved back to their original position as illegal,
4477        but in ICS mode that is not its job anyway.
4478     */
4479     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4480     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4481
4482         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4483             if(board[0][i] == WhiteRook) j = i;
4484         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4485         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4486             if(board[0][i] == WhiteRook) j = i;
4487         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4488         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4489             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4490         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4491         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4492             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4493         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4494
4495         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4496         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4497         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4498             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4499         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4500             if(board[BOARD_HEIGHT-1][k] == bKing)
4501                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4502         if(gameInfo.variant == VariantTwoKings) {
4503             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4504             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4505             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4506         }
4507     } else { int r;
4508         r = boards[moveNum][CASTLING][0] = initialRights[0];
4509         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4510         r = boards[moveNum][CASTLING][1] = initialRights[1];
4511         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4512         r = boards[moveNum][CASTLING][3] = initialRights[3];
4513         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4514         r = boards[moveNum][CASTLING][4] = initialRights[4];
4515         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4516         /* wildcastle kludge: always assume King has rights */
4517         r = boards[moveNum][CASTLING][2] = initialRights[2];
4518         r = boards[moveNum][CASTLING][5] = initialRights[5];
4519     }
4520     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4521     boards[moveNum][EP_STATUS] = EP_NONE;
4522     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4523     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4524     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4525
4526
4527     if (ics_getting_history == H_GOT_REQ_HEADER ||
4528         ics_getting_history == H_GOT_UNREQ_HEADER) {
4529         /* This was an initial position from a move list, not
4530            the current position */
4531         return;
4532     }
4533
4534     /* Update currentMove and known move number limits */
4535     newMove = newGame || moveNum > forwardMostMove;
4536
4537     if (newGame) {
4538         forwardMostMove = backwardMostMove = currentMove = moveNum;
4539         if (gameMode == IcsExamining && moveNum == 0) {
4540           /* Workaround for ICS limitation: we are not told the wild
4541              type when starting to examine a game.  But if we ask for
4542              the move list, the move list header will tell us */
4543             ics_getting_history = H_REQUESTED;
4544             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4545             SendToICS(str);
4546         }
4547     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4548                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4549 #if ZIPPY
4550         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4551         /* [HGM] applied this also to an engine that is silently watching        */
4552         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4553             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4554             gameInfo.variant == currentlyInitializedVariant) {
4555           takeback = forwardMostMove - moveNum;
4556           for (i = 0; i < takeback; i++) {
4557             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4558             SendToProgram("undo\n", &first);
4559           }
4560         }
4561 #endif
4562
4563         forwardMostMove = moveNum;
4564         if (!pausing || currentMove > forwardMostMove)
4565           currentMove = forwardMostMove;
4566     } else {
4567         /* New part of history that is not contiguous with old part */
4568         if (pausing && gameMode == IcsExamining) {
4569             pauseExamInvalid = TRUE;
4570             forwardMostMove = pauseExamForwardMostMove;
4571             return;
4572         }
4573         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4574 #if ZIPPY
4575             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4576                 // [HGM] when we will receive the move list we now request, it will be
4577                 // fed to the engine from the first move on. So if the engine is not
4578                 // in the initial position now, bring it there.
4579                 InitChessProgram(&first, 0);
4580             }
4581 #endif
4582             ics_getting_history = H_REQUESTED;
4583             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4584             SendToICS(str);
4585         }
4586         forwardMostMove = backwardMostMove = currentMove = moveNum;
4587     }
4588
4589     /* Update the clocks */
4590     if (strchr(elapsed_time, '.')) {
4591       /* Time is in ms */
4592       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4593       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4594     } else {
4595       /* Time is in seconds */
4596       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4597       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4598     }
4599
4600
4601 #if ZIPPY
4602     if (appData.zippyPlay && newGame &&
4603         gameMode != IcsObserving && gameMode != IcsIdle &&
4604         gameMode != IcsExamining)
4605       ZippyFirstBoard(moveNum, basetime, increment);
4606 #endif
4607
4608     /* Put the move on the move list, first converting
4609        to canonical algebraic form. */
4610     if (moveNum > 0) {
4611   if (appData.debugMode) {
4612     if (appData.debugMode) { int f = forwardMostMove;
4613         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4614                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4615                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4616     }
4617     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4618     fprintf(debugFP, "moveNum = %d\n", moveNum);
4619     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4620     setbuf(debugFP, NULL);
4621   }
4622         if (moveNum <= backwardMostMove) {
4623             /* We don't know what the board looked like before
4624                this move.  Punt. */
4625           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4626             strcat(parseList[moveNum - 1], " ");
4627             strcat(parseList[moveNum - 1], elapsed_time);
4628             moveList[moveNum - 1][0] = NULLCHAR;
4629         } else if (strcmp(move_str, "none") == 0) {
4630             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4631             /* Again, we don't know what the board looked like;
4632                this is really the start of the game. */
4633             parseList[moveNum - 1][0] = NULLCHAR;
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             backwardMostMove = moveNum;
4636             startedFromSetupPosition = TRUE;
4637             fromX = fromY = toX = toY = -1;
4638         } else {
4639           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4640           //                 So we parse the long-algebraic move string in stead of the SAN move
4641           int valid; char buf[MSG_SIZ], *prom;
4642
4643           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4644                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4645           // str looks something like "Q/a1-a2"; kill the slash
4646           if(str[1] == '/')
4647             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4648           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4649           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4650                 strcat(buf, prom); // long move lacks promo specification!
4651           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4652                 if(appData.debugMode)
4653                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4654                 safeStrCpy(move_str, buf, MSG_SIZ);
4655           }
4656           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4657                                 &fromX, &fromY, &toX, &toY, &promoChar)
4658                || ParseOneMove(buf, moveNum - 1, &moveType,
4659                                 &fromX, &fromY, &toX, &toY, &promoChar);
4660           // end of long SAN patch
4661           if (valid) {
4662             (void) CoordsToAlgebraic(boards[moveNum - 1],
4663                                      PosFlags(moveNum - 1),
4664                                      fromY, fromX, toY, toX, promoChar,
4665                                      parseList[moveNum-1]);
4666             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4667               case MT_NONE:
4668               case MT_STALEMATE:
4669               default:
4670                 break;
4671               case MT_CHECK:
4672                 if(gameInfo.variant != VariantShogi)
4673                     strcat(parseList[moveNum - 1], "+");
4674                 break;
4675               case MT_CHECKMATE:
4676               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4677                 strcat(parseList[moveNum - 1], "#");
4678                 break;
4679             }
4680             strcat(parseList[moveNum - 1], " ");
4681             strcat(parseList[moveNum - 1], elapsed_time);
4682             /* currentMoveString is set as a side-effect of ParseOneMove */
4683             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4684             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4685             strcat(moveList[moveNum - 1], "\n");
4686
4687             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4688                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4689               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4690                 ChessSquare old, new = boards[moveNum][k][j];
4691                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4692                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4693                   if(old == new) continue;
4694                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4695                   else if(new == WhiteWazir || new == BlackWazir) {
4696                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4697                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4698                       else boards[moveNum][k][j] = old; // preserve type of Gold
4699                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4700                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4701               }
4702           } else {
4703             /* Move from ICS was illegal!?  Punt. */
4704             if (appData.debugMode) {
4705               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4706               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4707             }
4708             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4709             strcat(parseList[moveNum - 1], " ");
4710             strcat(parseList[moveNum - 1], elapsed_time);
4711             moveList[moveNum - 1][0] = NULLCHAR;
4712             fromX = fromY = toX = toY = -1;
4713           }
4714         }
4715   if (appData.debugMode) {
4716     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4717     setbuf(debugFP, NULL);
4718   }
4719
4720 #if ZIPPY
4721         /* Send move to chess program (BEFORE animating it). */
4722         if (appData.zippyPlay && !newGame && newMove &&
4723            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4724
4725             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4726                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4727                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4729                             move_str);
4730                     DisplayError(str, 0);
4731                 } else {
4732                     if (first.sendTime) {
4733                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4734                     }
4735                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4736                     if (firstMove && !bookHit) {
4737                         firstMove = FALSE;
4738                         if (first.useColors) {
4739                           SendToProgram(gameMode == IcsPlayingWhite ?
4740                                         "white\ngo\n" :
4741                                         "black\ngo\n", &first);
4742                         } else {
4743                           SendToProgram("go\n", &first);
4744                         }
4745                         first.maybeThinking = TRUE;
4746                     }
4747                 }
4748             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4749               if (moveList[moveNum - 1][0] == NULLCHAR) {
4750                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4751                 DisplayError(str, 0);
4752               } else {
4753                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4754                 SendMoveToProgram(moveNum - 1, &first);
4755               }
4756             }
4757         }
4758 #endif
4759     }
4760
4761     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4762         /* If move comes from a remote source, animate it.  If it
4763            isn't remote, it will have already been animated. */
4764         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4765             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4766         }
4767         if (!pausing && appData.highlightLastMove) {
4768             SetHighlights(fromX, fromY, toX, toY);
4769         }
4770     }
4771
4772     /* Start the clocks */
4773     whiteFlag = blackFlag = FALSE;
4774     appData.clockMode = !(basetime == 0 && increment == 0);
4775     if (ticking == 0) {
4776       ics_clock_paused = TRUE;
4777       StopClocks();
4778     } else if (ticking == 1) {
4779       ics_clock_paused = FALSE;
4780     }
4781     if (gameMode == IcsIdle ||
4782         relation == RELATION_OBSERVING_STATIC ||
4783         relation == RELATION_EXAMINING ||
4784         ics_clock_paused)
4785       DisplayBothClocks();
4786     else
4787       StartClocks();
4788
4789     /* Display opponents and material strengths */
4790     if (gameInfo.variant != VariantBughouse &&
4791         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4792         if (tinyLayout || smallLayout) {
4793             if(gameInfo.variant == VariantNormal)
4794               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4795                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4796                     basetime, increment);
4797             else
4798               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4799                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4800                     basetime, increment, (int) gameInfo.variant);
4801         } else {
4802             if(gameInfo.variant == VariantNormal)
4803               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4804                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4805                     basetime, increment);
4806             else
4807               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4808                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4809                     basetime, increment, VariantName(gameInfo.variant));
4810         }
4811         DisplayTitle(str);
4812   if (appData.debugMode) {
4813     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4814   }
4815     }
4816
4817
4818     /* Display the board */
4819     if (!pausing && !appData.noGUI) {
4820
4821       if (appData.premove)
4822           if (!gotPremove ||
4823              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4824              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4825               ClearPremoveHighlights();
4826
4827       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4828         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4829       DrawPosition(j, boards[currentMove]);
4830
4831       DisplayMove(moveNum - 1);
4832       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4833             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4834               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4835         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4836       }
4837     }
4838
4839     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4840 #if ZIPPY
4841     if(bookHit) { // [HGM] book: simulate book reply
4842         static char bookMove[MSG_SIZ]; // a bit generous?
4843
4844         programStats.nodes = programStats.depth = programStats.time =
4845         programStats.score = programStats.got_only_move = 0;
4846         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4847
4848         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4849         strcat(bookMove, bookHit);
4850         HandleMachineMove(bookMove, &first);
4851     }
4852 #endif
4853 }
4854
4855 void
4856 GetMoveListEvent ()
4857 {
4858     char buf[MSG_SIZ];
4859     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4860         ics_getting_history = H_REQUESTED;
4861         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4862         SendToICS(buf);
4863     }
4864 }
4865
4866 void
4867 AnalysisPeriodicEvent (int force)
4868 {
4869     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4870          && !force) || !appData.periodicUpdates)
4871       return;
4872
4873     /* Send . command to Crafty to collect stats */
4874     SendToProgram(".\n", &first);
4875
4876     /* Don't send another until we get a response (this makes
4877        us stop sending to old Crafty's which don't understand
4878        the "." command (sending illegal cmds resets node count & time,
4879        which looks bad)) */
4880     programStats.ok_to_send = 0;
4881 }
4882
4883 void
4884 ics_update_width (int new_width)
4885 {
4886         ics_printf("set width %d\n", new_width);
4887 }
4888
4889 void
4890 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4891 {
4892     char buf[MSG_SIZ];
4893
4894     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4895         // null move in variant where engine does not understand it (for analysis purposes)
4896         SendBoard(cps, moveNum + 1); // send position after move in stead.
4897         return;
4898     }
4899     if (cps->useUsermove) {
4900       SendToProgram("usermove ", cps);
4901     }
4902     if (cps->useSAN) {
4903       char *space;
4904       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4905         int len = space - parseList[moveNum];
4906         memcpy(buf, parseList[moveNum], len);
4907         buf[len++] = '\n';
4908         buf[len] = NULLCHAR;
4909       } else {
4910         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4911       }
4912       SendToProgram(buf, cps);
4913     } else {
4914       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4915         AlphaRank(moveList[moveNum], 4);
4916         SendToProgram(moveList[moveNum], cps);
4917         AlphaRank(moveList[moveNum], 4); // and back
4918       } else
4919       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4920        * the engine. It would be nice to have a better way to identify castle
4921        * moves here. */
4922       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4923                                                                          && cps->useOOCastle) {
4924         int fromX = moveList[moveNum][0] - AAA;
4925         int fromY = moveList[moveNum][1] - ONE;
4926         int toX = moveList[moveNum][2] - AAA;
4927         int toY = moveList[moveNum][3] - ONE;
4928         if((boards[moveNum][fromY][fromX] == WhiteKing
4929             && boards[moveNum][toY][toX] == WhiteRook)
4930            || (boards[moveNum][fromY][fromX] == BlackKing
4931                && boards[moveNum][toY][toX] == BlackRook)) {
4932           if(toX > fromX) SendToProgram("O-O\n", cps);
4933           else SendToProgram("O-O-O\n", cps);
4934         }
4935         else SendToProgram(moveList[moveNum], cps);
4936       } else
4937       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4938         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4939           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4940           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4941                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4942         } else
4943           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4944                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4945         SendToProgram(buf, cps);
4946       }
4947       else SendToProgram(moveList[moveNum], cps);
4948       /* End of additions by Tord */
4949     }
4950
4951     /* [HGM] setting up the opening has brought engine in force mode! */
4952     /*       Send 'go' if we are in a mode where machine should play. */
4953     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4954         (gameMode == TwoMachinesPlay   ||
4955 #if ZIPPY
4956          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4957 #endif
4958          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4959         SendToProgram("go\n", cps);
4960   if (appData.debugMode) {
4961     fprintf(debugFP, "(extra)\n");
4962   }
4963     }
4964     setboardSpoiledMachineBlack = 0;
4965 }
4966
4967 void
4968 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4969 {
4970     char user_move[MSG_SIZ];
4971     char suffix[4];
4972
4973     if(gameInfo.variant == VariantSChess && promoChar) {
4974         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4975         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4976     } else suffix[0] = NULLCHAR;
4977
4978     switch (moveType) {
4979       default:
4980         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4981                 (int)moveType, fromX, fromY, toX, toY);
4982         DisplayError(user_move + strlen("say "), 0);
4983         break;
4984       case WhiteKingSideCastle:
4985       case BlackKingSideCastle:
4986       case WhiteQueenSideCastleWild:
4987       case BlackQueenSideCastleWild:
4988       /* PUSH Fabien */
4989       case WhiteHSideCastleFR:
4990       case BlackHSideCastleFR:
4991       /* POP Fabien */
4992         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4993         break;
4994       case WhiteQueenSideCastle:
4995       case BlackQueenSideCastle:
4996       case WhiteKingSideCastleWild:
4997       case BlackKingSideCastleWild:
4998       /* PUSH Fabien */
4999       case WhiteASideCastleFR:
5000       case BlackASideCastleFR:
5001       /* POP Fabien */
5002         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5003         break;
5004       case WhiteNonPromotion:
5005       case BlackNonPromotion:
5006         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5007         break;
5008       case WhitePromotion:
5009       case BlackPromotion:
5010         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5011           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5013                 PieceToChar(WhiteFerz));
5014         else if(gameInfo.variant == VariantGreat)
5015           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5016                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5017                 PieceToChar(WhiteMan));
5018         else
5019           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5021                 promoChar);
5022         break;
5023       case WhiteDrop:
5024       case BlackDrop:
5025       drop:
5026         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5027                  ToUpper(PieceToChar((ChessSquare) fromX)),
5028                  AAA + toX, ONE + toY);
5029         break;
5030       case IllegalMove:  /* could be a variant we don't quite understand */
5031         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5032       case NormalMove:
5033       case WhiteCapturesEnPassant:
5034       case BlackCapturesEnPassant:
5035         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5036                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5037         break;
5038     }
5039     SendToICS(user_move);
5040     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5041         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5042 }
5043
5044 void
5045 UploadGameEvent ()
5046 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5047     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5048     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5049     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5050       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5051       return;
5052     }
5053     if(gameMode != IcsExamining) { // is this ever not the case?
5054         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5055
5056         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5057           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5058         } else { // on FICS we must first go to general examine mode
5059           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5060         }
5061         if(gameInfo.variant != VariantNormal) {
5062             // try figure out wild number, as xboard names are not always valid on ICS
5063             for(i=1; i<=36; i++) {
5064               snprintf(buf, MSG_SIZ, "wild/%d", i);
5065                 if(StringToVariant(buf) == gameInfo.variant) break;
5066             }
5067             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5068             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5069             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5070         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5071         SendToICS(ics_prefix);
5072         SendToICS(buf);
5073         if(startedFromSetupPosition || backwardMostMove != 0) {
5074           fen = PositionToFEN(backwardMostMove, NULL);
5075           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5076             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5077             SendToICS(buf);
5078           } else { // FICS: everything has to set by separate bsetup commands
5079             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5080             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5081             SendToICS(buf);
5082             if(!WhiteOnMove(backwardMostMove)) {
5083                 SendToICS("bsetup tomove black\n");
5084             }
5085             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5086             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5087             SendToICS(buf);
5088             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5089             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5090             SendToICS(buf);
5091             i = boards[backwardMostMove][EP_STATUS];
5092             if(i >= 0) { // set e.p.
5093               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5094                 SendToICS(buf);
5095             }
5096             bsetup++;
5097           }
5098         }
5099       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5100             SendToICS("bsetup done\n"); // switch to normal examining.
5101     }
5102     for(i = backwardMostMove; i<last; i++) {
5103         char buf[20];
5104         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5105         SendToICS(buf);
5106     }
5107     SendToICS(ics_prefix);
5108     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5109 }
5110
5111 void
5112 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5113 {
5114     if (rf == DROP_RANK) {
5115       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5116       sprintf(move, "%c@%c%c\n",
5117                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5118     } else {
5119         if (promoChar == 'x' || promoChar == NULLCHAR) {
5120           sprintf(move, "%c%c%c%c\n",
5121                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5122         } else {
5123             sprintf(move, "%c%c%c%c%c\n",
5124                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5125         }
5126     }
5127 }
5128
5129 void
5130 ProcessICSInitScript (FILE *f)
5131 {
5132     char buf[MSG_SIZ];
5133
5134     while (fgets(buf, MSG_SIZ, f)) {
5135         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5136     }
5137
5138     fclose(f);
5139 }
5140
5141
5142 static int lastX, lastY, selectFlag, dragging;
5143
5144 void
5145 Sweep (int step)
5146 {
5147     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5148     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5149     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5150     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5151     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5152     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5153     do {
5154         promoSweep -= step;
5155         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5156         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5157         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5158         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5159         if(!step) step = -1;
5160     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5161             appData.testLegality && (promoSweep == king ||
5162             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5163     ChangeDragPiece(promoSweep);
5164 }
5165
5166 int
5167 PromoScroll (int x, int y)
5168 {
5169   int step = 0;
5170
5171   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5172   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5173   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5174   if(!step) return FALSE;
5175   lastX = x; lastY = y;
5176   if((promoSweep < BlackPawn) == flipView) step = -step;
5177   if(step > 0) selectFlag = 1;
5178   if(!selectFlag) Sweep(step);
5179   return FALSE;
5180 }
5181
5182 void
5183 NextPiece (int step)
5184 {
5185     ChessSquare piece = boards[currentMove][toY][toX];
5186     do {
5187         pieceSweep -= step;
5188         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5189         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5190         if(!step) step = -1;
5191     } while(PieceToChar(pieceSweep) == '.');
5192     boards[currentMove][toY][toX] = pieceSweep;
5193     DrawPosition(FALSE, boards[currentMove]);
5194     boards[currentMove][toY][toX] = piece;
5195 }
5196 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5197 void
5198 AlphaRank (char *move, int n)
5199 {
5200 //    char *p = move, c; int x, y;
5201
5202     if (appData.debugMode) {
5203         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5204     }
5205
5206     if(move[1]=='*' &&
5207        move[2]>='0' && move[2]<='9' &&
5208        move[3]>='a' && move[3]<='x'    ) {
5209         move[1] = '@';
5210         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5211         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5212     } else
5213     if(move[0]>='0' && move[0]<='9' &&
5214        move[1]>='a' && move[1]<='x' &&
5215        move[2]>='0' && move[2]<='9' &&
5216        move[3]>='a' && move[3]<='x'    ) {
5217         /* input move, Shogi -> normal */
5218         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5219         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5220         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5221         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5222     } else
5223     if(move[1]=='@' &&
5224        move[3]>='0' && move[3]<='9' &&
5225        move[2]>='a' && move[2]<='x'    ) {
5226         move[1] = '*';
5227         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5228         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5229     } else
5230     if(
5231        move[0]>='a' && move[0]<='x' &&
5232        move[3]>='0' && move[3]<='9' &&
5233        move[2]>='a' && move[2]<='x'    ) {
5234          /* output move, normal -> Shogi */
5235         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5236         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5237         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5239         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5240     }
5241     if (appData.debugMode) {
5242         fprintf(debugFP, "   out = '%s'\n", move);
5243     }
5244 }
5245
5246 char yy_textstr[8000];
5247
5248 /* Parser for moves from gnuchess, ICS, or user typein box */
5249 Boolean
5250 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5251 {
5252     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5253
5254     switch (*moveType) {
5255       case WhitePromotion:
5256       case BlackPromotion:
5257       case WhiteNonPromotion:
5258       case BlackNonPromotion:
5259       case NormalMove:
5260       case WhiteCapturesEnPassant:
5261       case BlackCapturesEnPassant:
5262       case WhiteKingSideCastle:
5263       case WhiteQueenSideCastle:
5264       case BlackKingSideCastle:
5265       case BlackQueenSideCastle:
5266       case WhiteKingSideCastleWild:
5267       case WhiteQueenSideCastleWild:
5268       case BlackKingSideCastleWild:
5269       case BlackQueenSideCastleWild:
5270       /* Code added by Tord: */
5271       case WhiteHSideCastleFR:
5272       case WhiteASideCastleFR:
5273       case BlackHSideCastleFR:
5274       case BlackASideCastleFR:
5275       /* End of code added by Tord */
5276       case IllegalMove:         /* bug or odd chess variant */
5277         *fromX = currentMoveString[0] - AAA;
5278         *fromY = currentMoveString[1] - ONE;
5279         *toX = currentMoveString[2] - AAA;
5280         *toY = currentMoveString[3] - ONE;
5281         *promoChar = currentMoveString[4];
5282         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5283             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5284     if (appData.debugMode) {
5285         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5286     }
5287             *fromX = *fromY = *toX = *toY = 0;
5288             return FALSE;
5289         }
5290         if (appData.testLegality) {
5291           return (*moveType != IllegalMove);
5292         } else {
5293           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5294                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5295         }
5296
5297       case WhiteDrop:
5298       case BlackDrop:
5299         *fromX = *moveType == WhiteDrop ?
5300           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5301           (int) CharToPiece(ToLower(currentMoveString[0]));
5302         *fromY = DROP_RANK;
5303         *toX = currentMoveString[2] - AAA;
5304         *toY = currentMoveString[3] - ONE;
5305         *promoChar = NULLCHAR;
5306         return TRUE;
5307
5308       case AmbiguousMove:
5309       case ImpossibleMove:
5310       case EndOfFile:
5311       case ElapsedTime:
5312       case Comment:
5313       case PGNTag:
5314       case NAG:
5315       case WhiteWins:
5316       case BlackWins:
5317       case GameIsDrawn:
5318       default:
5319     if (appData.debugMode) {
5320         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5321     }
5322         /* bug? */
5323         *fromX = *fromY = *toX = *toY = 0;
5324         *promoChar = NULLCHAR;
5325         return FALSE;
5326     }
5327 }
5328
5329 Boolean pushed = FALSE;
5330 char *lastParseAttempt;
5331
5332 void
5333 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5334 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5335   int fromX, fromY, toX, toY; char promoChar;
5336   ChessMove moveType;
5337   Boolean valid;
5338   int nr = 0;
5339
5340   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5341     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5342     pushed = TRUE;
5343   }
5344   endPV = forwardMostMove;
5345   do {
5346     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5347     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5348     lastParseAttempt = pv;
5349     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5350     if(!valid && nr == 0 &&
5351        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5352         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5353         // Hande case where played move is different from leading PV move
5354         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5355         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5356         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5357         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5358           endPV += 2; // if position different, keep this
5359           moveList[endPV-1][0] = fromX + AAA;
5360           moveList[endPV-1][1] = fromY + ONE;
5361           moveList[endPV-1][2] = toX + AAA;
5362           moveList[endPV-1][3] = toY + ONE;
5363           parseList[endPV-1][0] = NULLCHAR;
5364           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5365         }
5366       }
5367     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5368     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5369     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5370     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5371         valid++; // allow comments in PV
5372         continue;
5373     }
5374     nr++;
5375     if(endPV+1 > framePtr) break; // no space, truncate
5376     if(!valid) break;
5377     endPV++;
5378     CopyBoard(boards[endPV], boards[endPV-1]);
5379     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5380     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5381     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5382     CoordsToAlgebraic(boards[endPV - 1],
5383                              PosFlags(endPV - 1),
5384                              fromY, fromX, toY, toX, promoChar,
5385                              parseList[endPV - 1]);
5386   } while(valid);
5387   if(atEnd == 2) return; // used hidden, for PV conversion
5388   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5389   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5390   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5391                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5392   DrawPosition(TRUE, boards[currentMove]);
5393 }
5394
5395 int
5396 MultiPV (ChessProgramState *cps)
5397 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5398         int i;
5399         for(i=0; i<cps->nrOptions; i++)
5400             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5401                 return i;
5402         return -1;
5403 }
5404
5405 Boolean
5406 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5407 {
5408         int startPV, multi, lineStart, origIndex = index;
5409         char *p, buf2[MSG_SIZ];
5410
5411         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5412         lastX = x; lastY = y;
5413         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5414         lineStart = startPV = index;
5415         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5416         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5417         index = startPV;
5418         do{ while(buf[index] && buf[index] != '\n') index++;
5419         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5420         buf[index] = 0;
5421         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5422                 int n = first.option[multi].value;
5423                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5424                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5425                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5426                 first.option[multi].value = n;
5427                 *start = *end = 0;
5428                 return FALSE;
5429         }
5430         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5431         *start = startPV; *end = index-1;
5432         return TRUE;
5433 }
5434
5435 char *
5436 PvToSAN (char *pv)
5437 {
5438         static char buf[10*MSG_SIZ];
5439         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5440         *buf = NULLCHAR;
5441         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5442         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5443         for(i = forwardMostMove; i<endPV; i++){
5444             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5445             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5446             k += strlen(buf+k);
5447         }
5448         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5449         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5450         endPV = savedEnd;
5451         return buf;
5452 }
5453
5454 Boolean
5455 LoadPV (int x, int y)
5456 { // called on right mouse click to load PV
5457   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5458   lastX = x; lastY = y;
5459   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5460   return TRUE;
5461 }
5462
5463 void
5464 UnLoadPV ()
5465 {
5466   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5467   if(endPV < 0) return;
5468   if(appData.autoCopyPV) CopyFENToClipboard();
5469   endPV = -1;
5470   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5471         Boolean saveAnimate = appData.animate;
5472         if(pushed) {
5473             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5474                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5475             } else storedGames--; // abandon shelved tail of original game
5476         }
5477         pushed = FALSE;
5478         forwardMostMove = currentMove;
5479         currentMove = oldFMM;
5480         appData.animate = FALSE;
5481         ToNrEvent(forwardMostMove);
5482         appData.animate = saveAnimate;
5483   }
5484   currentMove = forwardMostMove;
5485   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5486   ClearPremoveHighlights();
5487   DrawPosition(TRUE, boards[currentMove]);
5488 }
5489
5490 void
5491 MovePV (int x, int y, int h)
5492 { // step through PV based on mouse coordinates (called on mouse move)
5493   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5494
5495   // we must somehow check if right button is still down (might be released off board!)
5496   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5497   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5498   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5499   if(!step) return;
5500   lastX = x; lastY = y;
5501
5502   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5503   if(endPV < 0) return;
5504   if(y < margin) step = 1; else
5505   if(y > h - margin) step = -1;
5506   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5507   currentMove += step;
5508   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5509   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5510                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5511   DrawPosition(FALSE, boards[currentMove]);
5512 }
5513
5514
5515 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5516 // All positions will have equal probability, but the current method will not provide a unique
5517 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5518 #define DARK 1
5519 #define LITE 2
5520 #define ANY 3
5521
5522 int squaresLeft[4];
5523 int piecesLeft[(int)BlackPawn];
5524 int seed, nrOfShuffles;
5525
5526 void
5527 GetPositionNumber ()
5528 {       // sets global variable seed
5529         int i;
5530
5531         seed = appData.defaultFrcPosition;
5532         if(seed < 0) { // randomize based on time for negative FRC position numbers
5533                 for(i=0; i<50; i++) seed += random();
5534                 seed = random() ^ random() >> 8 ^ random() << 8;
5535                 if(seed<0) seed = -seed;
5536         }
5537 }
5538
5539 int
5540 put (Board board, int pieceType, int rank, int n, int shade)
5541 // put the piece on the (n-1)-th empty squares of the given shade
5542 {
5543         int i;
5544
5545         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5546                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5547                         board[rank][i] = (ChessSquare) pieceType;
5548                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5549                         squaresLeft[ANY]--;
5550                         piecesLeft[pieceType]--;
5551                         return i;
5552                 }
5553         }
5554         return -1;
5555 }
5556
5557
5558 void
5559 AddOnePiece (Board board, int pieceType, int rank, int shade)
5560 // calculate where the next piece goes, (any empty square), and put it there
5561 {
5562         int i;
5563
5564         i = seed % squaresLeft[shade];
5565         nrOfShuffles *= squaresLeft[shade];
5566         seed /= squaresLeft[shade];
5567         put(board, pieceType, rank, i, shade);
5568 }
5569
5570 void
5571 AddTwoPieces (Board board, int pieceType, int rank)
5572 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5573 {
5574         int i, n=squaresLeft[ANY], j=n-1, k;
5575
5576         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5577         i = seed % k;  // pick one
5578         nrOfShuffles *= k;
5579         seed /= k;
5580         while(i >= j) i -= j--;
5581         j = n - 1 - j; i += j;
5582         put(board, pieceType, rank, j, ANY);
5583         put(board, pieceType, rank, i, ANY);
5584 }
5585
5586 void
5587 SetUpShuffle (Board board, int number)
5588 {
5589         int i, p, first=1;
5590
5591         GetPositionNumber(); nrOfShuffles = 1;
5592
5593         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5594         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5595         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5596
5597         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5598
5599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5600             p = (int) board[0][i];
5601             if(p < (int) BlackPawn) piecesLeft[p] ++;
5602             board[0][i] = EmptySquare;
5603         }
5604
5605         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5606             // shuffles restricted to allow normal castling put KRR first
5607             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5608                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5609             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5610                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5611             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5612                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5613             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5614                 put(board, WhiteRook, 0, 0, ANY);
5615             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5616         }
5617
5618         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5619             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5620             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5621                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5622                 while(piecesLeft[p] >= 2) {
5623                     AddOnePiece(board, p, 0, LITE);
5624                     AddOnePiece(board, p, 0, DARK);
5625                 }
5626                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5627             }
5628
5629         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5630             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5631             // but we leave King and Rooks for last, to possibly obey FRC restriction
5632             if(p == (int)WhiteRook) continue;
5633             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5634             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5635         }
5636
5637         // now everything is placed, except perhaps King (Unicorn) and Rooks
5638
5639         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5640             // Last King gets castling rights
5641             while(piecesLeft[(int)WhiteUnicorn]) {
5642                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5643                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5644             }
5645
5646             while(piecesLeft[(int)WhiteKing]) {
5647                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5648                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5649             }
5650
5651
5652         } else {
5653             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5654             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5655         }
5656
5657         // Only Rooks can be left; simply place them all
5658         while(piecesLeft[(int)WhiteRook]) {
5659                 i = put(board, WhiteRook, 0, 0, ANY);
5660                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5661                         if(first) {
5662                                 first=0;
5663                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5664                         }
5665                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5666                 }
5667         }
5668         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5669             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5670         }
5671
5672         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5673 }
5674
5675 int
5676 SetCharTable (char *table, const char * map)
5677 /* [HGM] moved here from winboard.c because of its general usefulness */
5678 /*       Basically a safe strcpy that uses the last character as King */
5679 {
5680     int result = FALSE; int NrPieces;
5681
5682     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5683                     && NrPieces >= 12 && !(NrPieces&1)) {
5684         int i; /* [HGM] Accept even length from 12 to 34 */
5685
5686         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5687         for( i=0; i<NrPieces/2-1; i++ ) {
5688             table[i] = map[i];
5689             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5690         }
5691         table[(int) WhiteKing]  = map[NrPieces/2-1];
5692         table[(int) BlackKing]  = map[NrPieces-1];
5693
5694         result = TRUE;
5695     }
5696
5697     return result;
5698 }
5699
5700 void
5701 Prelude (Board board)
5702 {       // [HGM] superchess: random selection of exo-pieces
5703         int i, j, k; ChessSquare p;
5704         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5705
5706         GetPositionNumber(); // use FRC position number
5707
5708         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5709             SetCharTable(pieceToChar, appData.pieceToCharTable);
5710             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5711                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5712         }
5713
5714         j = seed%4;                 seed /= 4;
5715         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5716         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5717         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5718         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5723         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5731         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5732         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5733         put(board, exoPieces[0],    0, 0, ANY);
5734         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5735 }
5736
5737 void
5738 InitPosition (int redraw)
5739 {
5740     ChessSquare (* pieces)[BOARD_FILES];
5741     int i, j, pawnRow, overrule,
5742     oldx = gameInfo.boardWidth,
5743     oldy = gameInfo.boardHeight,
5744     oldh = gameInfo.holdingsWidth;
5745     static int oldv;
5746
5747     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5748
5749     /* [AS] Initialize pv info list [HGM] and game status */
5750     {
5751         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5752             pvInfoList[i].depth = 0;
5753             boards[i][EP_STATUS] = EP_NONE;
5754             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5755         }
5756
5757         initialRulePlies = 0; /* 50-move counter start */
5758
5759         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5760         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5761     }
5762
5763
5764     /* [HGM] logic here is completely changed. In stead of full positions */
5765     /* the initialized data only consist of the two backranks. The switch */
5766     /* selects which one we will use, which is than copied to the Board   */
5767     /* initialPosition, which for the rest is initialized by Pawns and    */
5768     /* empty squares. This initial position is then copied to boards[0],  */
5769     /* possibly after shuffling, so that it remains available.            */
5770
5771     gameInfo.holdingsWidth = 0; /* default board sizes */
5772     gameInfo.boardWidth    = 8;
5773     gameInfo.boardHeight   = 8;
5774     gameInfo.holdingsSize  = 0;
5775     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5776     for(i=0; i<BOARD_FILES-2; i++)
5777       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5778     initialPosition[EP_STATUS] = EP_NONE;
5779     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5780     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5781          SetCharTable(pieceNickName, appData.pieceNickNames);
5782     else SetCharTable(pieceNickName, "............");
5783     pieces = FIDEArray;
5784
5785     switch (gameInfo.variant) {
5786     case VariantFischeRandom:
5787       shuffleOpenings = TRUE;
5788     default:
5789       break;
5790     case VariantShatranj:
5791       pieces = ShatranjArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5794       break;
5795     case VariantMakruk:
5796       pieces = makrukArray;
5797       nrCastlingRights = 0;
5798       startedFromSetupPosition = TRUE;
5799       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5800       break;
5801     case VariantTwoKings:
5802       pieces = twoKingsArray;
5803       break;
5804     case VariantGrand:
5805       pieces = GrandArray;
5806       nrCastlingRights = 0;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       gameInfo.boardWidth = 10;
5809       gameInfo.boardHeight = 10;
5810       gameInfo.holdingsSize = 7;
5811       break;
5812     case VariantCapaRandom:
5813       shuffleOpenings = TRUE;
5814     case VariantCapablanca:
5815       pieces = CapablancaArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5818       break;
5819     case VariantGothic:
5820       pieces = GothicArray;
5821       gameInfo.boardWidth = 10;
5822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5823       break;
5824     case VariantSChess:
5825       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5826       gameInfo.holdingsSize = 7;
5827       break;
5828     case VariantJanus:
5829       pieces = JanusArray;
5830       gameInfo.boardWidth = 10;
5831       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5832       nrCastlingRights = 6;
5833         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5834         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5835         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5836         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5837         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5838         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5839       break;
5840     case VariantFalcon:
5841       pieces = FalconArray;
5842       gameInfo.boardWidth = 10;
5843       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5844       break;
5845     case VariantXiangqi:
5846       pieces = XiangqiArray;
5847       gameInfo.boardWidth  = 9;
5848       gameInfo.boardHeight = 10;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5851       break;
5852     case VariantShogi:
5853       pieces = ShogiArray;
5854       gameInfo.boardWidth  = 9;
5855       gameInfo.boardHeight = 9;
5856       gameInfo.holdingsSize = 7;
5857       nrCastlingRights = 0;
5858       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5859       break;
5860     case VariantCourier:
5861       pieces = CourierArray;
5862       gameInfo.boardWidth  = 12;
5863       nrCastlingRights = 0;
5864       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5865       break;
5866     case VariantKnightmate:
5867       pieces = KnightmateArray;
5868       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5869       break;
5870     case VariantSpartan:
5871       pieces = SpartanArray;
5872       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5873       break;
5874     case VariantFairy:
5875       pieces = fairyArray;
5876       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5877       break;
5878     case VariantGreat:
5879       pieces = GreatArray;
5880       gameInfo.boardWidth = 10;
5881       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5882       gameInfo.holdingsSize = 8;
5883       break;
5884     case VariantSuper:
5885       pieces = FIDEArray;
5886       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5887       gameInfo.holdingsSize = 8;
5888       startedFromSetupPosition = TRUE;
5889       break;
5890     case VariantCrazyhouse:
5891     case VariantBughouse:
5892       pieces = FIDEArray;
5893       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5894       gameInfo.holdingsSize = 5;
5895       break;
5896     case VariantWildCastle:
5897       pieces = FIDEArray;
5898       /* !!?shuffle with kings guaranteed to be on d or e file */
5899       shuffleOpenings = 1;
5900       break;
5901     case VariantNoCastle:
5902       pieces = FIDEArray;
5903       nrCastlingRights = 0;
5904       /* !!?unconstrained back-rank shuffle */
5905       shuffleOpenings = 1;
5906       break;
5907     }
5908
5909     overrule = 0;
5910     if(appData.NrFiles >= 0) {
5911         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5912         gameInfo.boardWidth = appData.NrFiles;
5913     }
5914     if(appData.NrRanks >= 0) {
5915         gameInfo.boardHeight = appData.NrRanks;
5916     }
5917     if(appData.holdingsSize >= 0) {
5918         i = appData.holdingsSize;
5919         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5920         gameInfo.holdingsSize = i;
5921     }
5922     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5923     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5924         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5925
5926     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5927     if(pawnRow < 1) pawnRow = 1;
5928     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5929
5930     /* User pieceToChar list overrules defaults */
5931     if(appData.pieceToCharTable != NULL)
5932         SetCharTable(pieceToChar, appData.pieceToCharTable);
5933
5934     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5935
5936         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5937             s = (ChessSquare) 0; /* account holding counts in guard band */
5938         for( i=0; i<BOARD_HEIGHT; i++ )
5939             initialPosition[i][j] = s;
5940
5941         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5942         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5943         initialPosition[pawnRow][j] = WhitePawn;
5944         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5945         if(gameInfo.variant == VariantXiangqi) {
5946             if(j&1) {
5947                 initialPosition[pawnRow][j] =
5948                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5949                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5950                    initialPosition[2][j] = WhiteCannon;
5951                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5952                 }
5953             }
5954         }
5955         if(gameInfo.variant == VariantGrand) {
5956             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5957                initialPosition[0][j] = WhiteRook;
5958                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5959             }
5960         }
5961         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5962     }
5963     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5964
5965             j=BOARD_LEFT+1;
5966             initialPosition[1][j] = WhiteBishop;
5967             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5968             j=BOARD_RGHT-2;
5969             initialPosition[1][j] = WhiteRook;
5970             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5971     }
5972
5973     if( nrCastlingRights == -1) {
5974         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5975         /*       This sets default castling rights from none to normal corners   */
5976         /* Variants with other castling rights must set them themselves above    */
5977         nrCastlingRights = 6;
5978
5979         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5980         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5981         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5982         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5983         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5984         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5985      }
5986
5987      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5988      if(gameInfo.variant == VariantGreat) { // promotion commoners
5989         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5990         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5991         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5992         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5993      }
5994      if( gameInfo.variant == VariantSChess ) {
5995       initialPosition[1][0] = BlackMarshall;
5996       initialPosition[2][0] = BlackAngel;
5997       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5998       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5999       initialPosition[1][1] = initialPosition[2][1] = 
6000       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6001      }
6002   if (appData.debugMode) {
6003     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6004   }
6005     if(shuffleOpenings) {
6006         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6007         startedFromSetupPosition = TRUE;
6008     }
6009     if(startedFromPositionFile) {
6010       /* [HGM] loadPos: use PositionFile for every new game */
6011       CopyBoard(initialPosition, filePosition);
6012       for(i=0; i<nrCastlingRights; i++)
6013           initialRights[i] = filePosition[CASTLING][i];
6014       startedFromSetupPosition = TRUE;
6015     }
6016
6017     CopyBoard(boards[0], initialPosition);
6018
6019     if(oldx != gameInfo.boardWidth ||
6020        oldy != gameInfo.boardHeight ||
6021        oldv != gameInfo.variant ||
6022        oldh != gameInfo.holdingsWidth
6023                                          )
6024             InitDrawingSizes(-2 ,0);
6025
6026     oldv = gameInfo.variant;
6027     if (redraw)
6028       DrawPosition(TRUE, boards[currentMove]);
6029 }
6030
6031 void
6032 SendBoard (ChessProgramState *cps, int moveNum)
6033 {
6034     char message[MSG_SIZ];
6035
6036     if (cps->useSetboard) {
6037       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6038       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6039       SendToProgram(message, cps);
6040       free(fen);
6041
6042     } else {
6043       ChessSquare *bp;
6044       int i, j, left=0, right=BOARD_WIDTH;
6045       /* Kludge to set black to move, avoiding the troublesome and now
6046        * deprecated "black" command.
6047        */
6048       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6049         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6050
6051       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6052
6053       SendToProgram("edit\n", cps);
6054       SendToProgram("#\n", cps);
6055       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6056         bp = &boards[moveNum][i][left];
6057         for (j = left; j < right; j++, bp++) {
6058           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6059           if ((int) *bp < (int) BlackPawn) {
6060             if(j == BOARD_RGHT+1)
6061                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6062             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6063             if(message[0] == '+' || message[0] == '~') {
6064               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6065                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6066                         AAA + j, ONE + i);
6067             }
6068             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6069                 message[1] = BOARD_RGHT   - 1 - j + '1';
6070                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6071             }
6072             SendToProgram(message, cps);
6073           }
6074         }
6075       }
6076
6077       SendToProgram("c\n", cps);
6078       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6079         bp = &boards[moveNum][i][left];
6080         for (j = left; j < right; j++, bp++) {
6081           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6082           if (((int) *bp != (int) EmptySquare)
6083               && ((int) *bp >= (int) BlackPawn)) {
6084             if(j == BOARD_LEFT-2)
6085                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6086             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6087                     AAA + j, ONE + i);
6088             if(message[0] == '+' || message[0] == '~') {
6089               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6090                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6091                         AAA + j, ONE + i);
6092             }
6093             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6094                 message[1] = BOARD_RGHT   - 1 - j + '1';
6095                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6096             }
6097             SendToProgram(message, cps);
6098           }
6099         }
6100       }
6101
6102       SendToProgram(".\n", cps);
6103     }
6104     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6105 }
6106
6107 ChessSquare
6108 DefaultPromoChoice (int white)
6109 {
6110     ChessSquare result;
6111     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6112         result = WhiteFerz; // no choice
6113     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6114         result= WhiteKing; // in Suicide Q is the last thing we want
6115     else if(gameInfo.variant == VariantSpartan)
6116         result = white ? WhiteQueen : WhiteAngel;
6117     else result = WhiteQueen;
6118     if(!white) result = WHITE_TO_BLACK result;
6119     return result;
6120 }
6121
6122 static int autoQueen; // [HGM] oneclick
6123
6124 int
6125 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6126 {
6127     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6128     /* [HGM] add Shogi promotions */
6129     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6130     ChessSquare piece;
6131     ChessMove moveType;
6132     Boolean premove;
6133
6134     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6135     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6136
6137     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6138       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6139         return FALSE;
6140
6141     piece = boards[currentMove][fromY][fromX];
6142     if(gameInfo.variant == VariantShogi) {
6143         promotionZoneSize = BOARD_HEIGHT/3;
6144         highestPromotingPiece = (int)WhiteFerz;
6145     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6146         promotionZoneSize = 3;
6147     }
6148
6149     // Treat Lance as Pawn when it is not representing Amazon
6150     if(gameInfo.variant != VariantSuper) {
6151         if(piece == WhiteLance) piece = WhitePawn; else
6152         if(piece == BlackLance) piece = BlackPawn;
6153     }
6154
6155     // next weed out all moves that do not touch the promotion zone at all
6156     if((int)piece >= BlackPawn) {
6157         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6158              return FALSE;
6159         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6160     } else {
6161         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6162            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6163     }
6164
6165     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6166
6167     // weed out mandatory Shogi promotions
6168     if(gameInfo.variant == VariantShogi) {
6169         if(piece >= BlackPawn) {
6170             if(toY == 0 && piece == BlackPawn ||
6171                toY == 0 && piece == BlackQueen ||
6172                toY <= 1 && piece == BlackKnight) {
6173                 *promoChoice = '+';
6174                 return FALSE;
6175             }
6176         } else {
6177             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6178                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6179                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6180                 *promoChoice = '+';
6181                 return FALSE;
6182             }
6183         }
6184     }
6185
6186     // weed out obviously illegal Pawn moves
6187     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6188         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6189         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6190         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6191         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6192         // note we are not allowed to test for valid (non-)capture, due to premove
6193     }
6194
6195     // we either have a choice what to promote to, or (in Shogi) whether to promote
6196     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6197         *promoChoice = PieceToChar(BlackFerz);  // no choice
6198         return FALSE;
6199     }
6200     // no sense asking what we must promote to if it is going to explode...
6201     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6202         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6203         return FALSE;
6204     }
6205     // give caller the default choice even if we will not make it
6206     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6207     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6208     if(        sweepSelect && gameInfo.variant != VariantGreat
6209                            && gameInfo.variant != VariantGrand
6210                            && gameInfo.variant != VariantSuper) return FALSE;
6211     if(autoQueen) return FALSE; // predetermined
6212
6213     // suppress promotion popup on illegal moves that are not premoves
6214     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6215               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6216     if(appData.testLegality && !premove) {
6217         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6218                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6219         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6220             return FALSE;
6221     }
6222
6223     return TRUE;
6224 }
6225
6226 int
6227 InPalace (int row, int column)
6228 {   /* [HGM] for Xiangqi */
6229     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6230          column < (BOARD_WIDTH + 4)/2 &&
6231          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6232     return FALSE;
6233 }
6234
6235 int
6236 PieceForSquare (int x, int y)
6237 {
6238   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6239      return -1;
6240   else
6241      return boards[currentMove][y][x];
6242 }
6243
6244 int
6245 OKToStartUserMove (int x, int y)
6246 {
6247     ChessSquare from_piece;
6248     int white_piece;
6249
6250     if (matchMode) return FALSE;
6251     if (gameMode == EditPosition) return TRUE;
6252
6253     if (x >= 0 && y >= 0)
6254       from_piece = boards[currentMove][y][x];
6255     else
6256       from_piece = EmptySquare;
6257
6258     if (from_piece == EmptySquare) return FALSE;
6259
6260     white_piece = (int)from_piece >= (int)WhitePawn &&
6261       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6262
6263     switch (gameMode) {
6264       case AnalyzeFile:
6265       case TwoMachinesPlay:
6266       case EndOfGame:
6267         return FALSE;
6268
6269       case IcsObserving:
6270       case IcsIdle:
6271         return FALSE;
6272
6273       case MachinePlaysWhite:
6274       case IcsPlayingBlack:
6275         if (appData.zippyPlay) return FALSE;
6276         if (white_piece) {
6277             DisplayMoveError(_("You are playing Black"));
6278             return FALSE;
6279         }
6280         break;
6281
6282       case MachinePlaysBlack:
6283       case IcsPlayingWhite:
6284         if (appData.zippyPlay) return FALSE;
6285         if (!white_piece) {
6286             DisplayMoveError(_("You are playing White"));
6287             return FALSE;
6288         }
6289         break;
6290
6291       case PlayFromGameFile:
6292             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6293       case EditGame:
6294         if (!white_piece && WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is White's turn"));
6296             return FALSE;
6297         }
6298         if (white_piece && !WhiteOnMove(currentMove)) {
6299             DisplayMoveError(_("It is Black's turn"));
6300             return FALSE;
6301         }
6302         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6303             /* Editing correspondence game history */
6304             /* Could disallow this or prompt for confirmation */
6305             cmailOldMove = -1;
6306         }
6307         break;
6308
6309       case BeginningOfGame:
6310         if (appData.icsActive) return FALSE;
6311         if (!appData.noChessProgram) {
6312             if (!white_piece) {
6313                 DisplayMoveError(_("You are playing White"));
6314                 return FALSE;
6315             }
6316         }
6317         break;
6318
6319       case Training:
6320         if (!white_piece && WhiteOnMove(currentMove)) {
6321             DisplayMoveError(_("It is White's turn"));
6322             return FALSE;
6323         }
6324         if (white_piece && !WhiteOnMove(currentMove)) {
6325             DisplayMoveError(_("It is Black's turn"));
6326             return FALSE;
6327         }
6328         break;
6329
6330       default:
6331       case IcsExamining:
6332         break;
6333     }
6334     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6335         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6336         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6337         && gameMode != AnalyzeFile && gameMode != Training) {
6338         DisplayMoveError(_("Displayed position is not current"));
6339         return FALSE;
6340     }
6341     return TRUE;
6342 }
6343
6344 Boolean
6345 OnlyMove (int *x, int *y, Boolean captures) 
6346 {
6347     DisambiguateClosure cl;
6348     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6349     switch(gameMode) {
6350       case MachinePlaysBlack:
6351       case IcsPlayingWhite:
6352       case BeginningOfGame:
6353         if(!WhiteOnMove(currentMove)) return FALSE;
6354         break;
6355       case MachinePlaysWhite:
6356       case IcsPlayingBlack:
6357         if(WhiteOnMove(currentMove)) return FALSE;
6358         break;
6359       case EditGame:
6360         break;
6361       default:
6362         return FALSE;
6363     }
6364     cl.pieceIn = EmptySquare;
6365     cl.rfIn = *y;
6366     cl.ffIn = *x;
6367     cl.rtIn = -1;
6368     cl.ftIn = -1;
6369     cl.promoCharIn = NULLCHAR;
6370     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6371     if( cl.kind == NormalMove ||
6372         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6373         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6374         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6375       fromX = cl.ff;
6376       fromY = cl.rf;
6377       *x = cl.ft;
6378       *y = cl.rt;
6379       return TRUE;
6380     }
6381     if(cl.kind != ImpossibleMove) return FALSE;
6382     cl.pieceIn = EmptySquare;
6383     cl.rfIn = -1;
6384     cl.ffIn = -1;
6385     cl.rtIn = *y;
6386     cl.ftIn = *x;
6387     cl.promoCharIn = NULLCHAR;
6388     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6389     if( cl.kind == NormalMove ||
6390         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6391         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6392         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6393       fromX = cl.ff;
6394       fromY = cl.rf;
6395       *x = cl.ft;
6396       *y = cl.rt;
6397       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6398       return TRUE;
6399     }
6400     return FALSE;
6401 }
6402
6403 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6404 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6405 int lastLoadGameUseList = FALSE;
6406 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6407 ChessMove lastLoadGameStart = EndOfFile;
6408 int doubleClick, mappedMove = -1;
6409
6410 void
6411 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6412 {
6413     ChessMove moveType;
6414     ChessSquare pdown, pup;
6415     static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6416     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6417
6418
6419     /* Check if the user is playing in turn.  This is complicated because we
6420        let the user "pick up" a piece before it is his turn.  So the piece he
6421        tried to pick up may have been captured by the time he puts it down!
6422        Therefore we use the color the user is supposed to be playing in this
6423        test, not the color of the piece that is currently on the starting
6424        square---except in EditGame mode, where the user is playing both
6425        sides; fortunately there the capture race can't happen.  (It can
6426        now happen in IcsExamining mode, but that's just too bad.  The user
6427        will get a somewhat confusing message in that case.)
6428        */
6429
6430     switch (gameMode) {
6431       case AnalyzeFile:
6432       case TwoMachinesPlay:
6433       case EndOfGame:
6434       case IcsObserving:
6435       case IcsIdle:
6436         /* We switched into a game mode where moves are not accepted,
6437            perhaps while the mouse button was down. */
6438         return;
6439
6440       case MachinePlaysWhite:
6441         /* User is moving for Black */
6442         if (WhiteOnMove(currentMove)) {
6443             DisplayMoveError(_("It is White's turn"));
6444             return;
6445         }
6446         break;
6447
6448       case MachinePlaysBlack:
6449         /* User is moving for White */
6450         if (!WhiteOnMove(currentMove)) {
6451             DisplayMoveError(_("It is Black's turn"));
6452             return;
6453         }
6454         break;
6455
6456       case PlayFromGameFile:
6457             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6458       case EditGame:
6459       case IcsExamining:
6460       case BeginningOfGame:
6461       case AnalyzeMode:
6462       case Training:
6463         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6464         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6465             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6466             /* User is moving for Black */
6467             if (WhiteOnMove(currentMove)) {
6468                 DisplayMoveError(_("It is White's turn"));
6469                 return;
6470             }
6471         } else {
6472             /* User is moving for White */
6473             if (!WhiteOnMove(currentMove)) {
6474                 DisplayMoveError(_("It is Black's turn"));
6475                 return;
6476             }
6477         }
6478         break;
6479
6480       case IcsPlayingBlack:
6481         /* User is moving for Black */
6482         if (WhiteOnMove(currentMove)) {
6483             if (!appData.premove) {
6484                 DisplayMoveError(_("It is White's turn"));
6485             } else if (toX >= 0 && toY >= 0) {
6486                 premoveToX = toX;
6487                 premoveToY = toY;
6488                 premoveFromX = fromX;
6489                 premoveFromY = fromY;
6490                 premovePromoChar = promoChar;
6491                 gotPremove = 1;
6492                 if (appData.debugMode)
6493                     fprintf(debugFP, "Got premove: fromX %d,"
6494                             "fromY %d, toX %d, toY %d\n",
6495                             fromX, fromY, toX, toY);
6496             }
6497             return;
6498         }
6499         break;
6500
6501       case IcsPlayingWhite:
6502         /* User is moving for White */
6503         if (!WhiteOnMove(currentMove)) {
6504             if (!appData.premove) {
6505                 DisplayMoveError(_("It is Black's turn"));
6506             } else if (toX >= 0 && toY >= 0) {
6507                 premoveToX = toX;
6508                 premoveToY = toY;
6509                 premoveFromX = fromX;
6510                 premoveFromY = fromY;
6511                 premovePromoChar = promoChar;
6512                 gotPremove = 1;
6513                 if (appData.debugMode)
6514                     fprintf(debugFP, "Got premove: fromX %d,"
6515                             "fromY %d, toX %d, toY %d\n",
6516                             fromX, fromY, toX, toY);
6517             }
6518             return;
6519         }
6520         break;
6521
6522       default:
6523         break;
6524
6525       case EditPosition:
6526         /* EditPosition, empty square, or different color piece;
6527            click-click move is possible */
6528         if (toX == -2 || toY == -2) {
6529             boards[0][fromY][fromX] = EmptySquare;
6530             DrawPosition(FALSE, boards[currentMove]);
6531             return;
6532         } else if (toX >= 0 && toY >= 0) {
6533             boards[0][toY][toX] = boards[0][fromY][fromX];
6534             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6535                 if(boards[0][fromY][0] != EmptySquare) {
6536                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6537                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6538                 }
6539             } else
6540             if(fromX == BOARD_RGHT+1) {
6541                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6542                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6543                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6544                 }
6545             } else
6546             boards[0][fromY][fromX] = EmptySquare;
6547             DrawPosition(FALSE, boards[currentMove]);
6548             return;
6549         }
6550         return;
6551     }
6552
6553     if(doubleClick && (toX == -2 || toY == -2)) { // [HGM] exclude: off-board move means exclude all
6554         int i; // note that drop moves still have holdings coords as from-square at this point
6555         ChessMove moveType; char pc;
6556         for(i=0; i<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; i++) excludeMap[i] = -(toY == -2);
6557         mappedMove = currentMove;
6558         if(toY != -2) { SendToProgram("include all\n", &first); return; }
6559         SendToProgram("exclude all\n", &first);
6560         i = ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &pc);
6561         ff=fromX, rf=fromY, ft=toX, rt=toY, promoChar = pc; // make copy that will survive drop encoding
6562         if(!i) return; // kludge: continue with move changed to engine's last-reported best, so it gets included again.
6563     }
6564
6565     if(toX < 0 || toY < 0) return;
6566     pdown = boards[currentMove][fromY][fromX];
6567     pup = boards[currentMove][toY][toX];
6568
6569     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6570     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6571          if( pup != EmptySquare ) return;
6572          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6573            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6574                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6575            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6576            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6577            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6578            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6579          fromY = DROP_RANK;
6580     }
6581
6582     /* [HGM] always test for legality, to get promotion info */
6583     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6584                                          fromY, fromX, toY, toX, promoChar);
6585
6586     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6587
6588     /* [HGM] but possibly ignore an IllegalMove result */
6589     if (appData.testLegality) {
6590         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6591             DisplayMoveError(_("Illegal move"));
6592             return;
6593         }
6594     }
6595
6596     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6597         int i=(BOARD_FILES*rf+ff)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*rt+ft), j;
6598         char buf[MSG_SIZ];
6599         if(mappedMove != currentMove)
6600             for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = 0;
6601         j = i%8; i >>= 3;
6602         snprintf(buf, MSG_SIZ, "%sclude ", excludeMap[i] & 1<<j ? "in" : "ex");
6603         if(excludeMap[i] & 1<<j) ClearPremoveHighlights();
6604         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt);
6605         if(!promoChar) excludeMap[i] ^= 1<<j; mappedMove = currentMove;
6606         CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6607         SendToProgram(buf, &first);
6608         return;
6609     }
6610
6611     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6612 }
6613
6614 /* Common tail of UserMoveEvent and DropMenuEvent */
6615 int
6616 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6617 {
6618     char *bookHit = 0;
6619
6620     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6621         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6622         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6623         if(WhiteOnMove(currentMove)) {
6624             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6625         } else {
6626             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6627         }
6628     }
6629
6630     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6631        move type in caller when we know the move is a legal promotion */
6632     if(moveType == NormalMove && promoChar)
6633         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6634
6635     /* [HGM] <popupFix> The following if has been moved here from
6636        UserMoveEvent(). Because it seemed to belong here (why not allow
6637        piece drops in training games?), and because it can only be
6638        performed after it is known to what we promote. */
6639     if (gameMode == Training) {
6640       /* compare the move played on the board to the next move in the
6641        * game. If they match, display the move and the opponent's response.
6642        * If they don't match, display an error message.
6643        */
6644       int saveAnimate;
6645       Board testBoard;
6646       CopyBoard(testBoard, boards[currentMove]);
6647       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6648
6649       if (CompareBoards(testBoard, boards[currentMove+1])) {
6650         ForwardInner(currentMove+1);
6651
6652         /* Autoplay the opponent's response.
6653          * if appData.animate was TRUE when Training mode was entered,
6654          * the response will be animated.
6655          */
6656         saveAnimate = appData.animate;
6657         appData.animate = animateTraining;
6658         ForwardInner(currentMove+1);
6659         appData.animate = saveAnimate;
6660
6661         /* check for the end of the game */
6662         if (currentMove >= forwardMostMove) {
6663           gameMode = PlayFromGameFile;
6664           ModeHighlight();
6665           SetTrainingModeOff();
6666           DisplayInformation(_("End of game"));
6667         }
6668       } else {
6669         DisplayError(_("Incorrect move"), 0);
6670       }
6671       return 1;
6672     }
6673
6674   /* Ok, now we know that the move is good, so we can kill
6675      the previous line in Analysis Mode */
6676   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6677                                 && currentMove < forwardMostMove) {
6678     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6679     else forwardMostMove = currentMove;
6680   }
6681
6682   /* If we need the chess program but it's dead, restart it */
6683   ResurrectChessProgram();
6684
6685   /* A user move restarts a paused game*/
6686   if (pausing)
6687     PauseEvent();
6688
6689   thinkOutput[0] = NULLCHAR;
6690
6691   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6692
6693   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6694     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6695     return 1;
6696   }
6697
6698   if (gameMode == BeginningOfGame) {
6699     if (appData.noChessProgram) {
6700       gameMode = EditGame;
6701       SetGameInfo();
6702     } else {
6703       char buf[MSG_SIZ];
6704       gameMode = MachinePlaysBlack;
6705       StartClocks();
6706       SetGameInfo();
6707       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6708       DisplayTitle(buf);
6709       if (first.sendName) {
6710         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6711         SendToProgram(buf, &first);
6712       }
6713       StartClocks();
6714     }
6715     ModeHighlight();
6716   }
6717
6718   /* Relay move to ICS or chess engine */
6719   if (appData.icsActive) {
6720     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6721         gameMode == IcsExamining) {
6722       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6723         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6724         SendToICS("draw ");
6725         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6726       }
6727       // also send plain move, in case ICS does not understand atomic claims
6728       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6729       ics_user_moved = 1;
6730     }
6731   } else {
6732     if (first.sendTime && (gameMode == BeginningOfGame ||
6733                            gameMode == MachinePlaysWhite ||
6734                            gameMode == MachinePlaysBlack)) {
6735       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6736     }
6737     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6738          // [HGM] book: if program might be playing, let it use book
6739         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6740         first.maybeThinking = TRUE;
6741     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6742         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6743         SendBoard(&first, currentMove+1);
6744     } else SendMoveToProgram(forwardMostMove-1, &first);
6745     if (currentMove == cmailOldMove + 1) {
6746       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6747     }
6748   }
6749
6750   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6751
6752   switch (gameMode) {
6753   case EditGame:
6754     if(appData.testLegality)
6755     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6756     case MT_NONE:
6757     case MT_CHECK:
6758       break;
6759     case MT_CHECKMATE:
6760     case MT_STAINMATE:
6761       if (WhiteOnMove(currentMove)) {
6762         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6763       } else {
6764         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6765       }
6766       break;
6767     case MT_STALEMATE:
6768       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6769       break;
6770     }
6771     break;
6772
6773   case MachinePlaysBlack:
6774   case MachinePlaysWhite:
6775     /* disable certain menu options while machine is thinking */
6776     SetMachineThinkingEnables();
6777     break;
6778
6779   default:
6780     break;
6781   }
6782
6783   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6784   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6785
6786   if(bookHit) { // [HGM] book: simulate book reply
6787         static char bookMove[MSG_SIZ]; // a bit generous?
6788
6789         programStats.nodes = programStats.depth = programStats.time =
6790         programStats.score = programStats.got_only_move = 0;
6791         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6792
6793         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6794         strcat(bookMove, bookHit);
6795         HandleMachineMove(bookMove, &first);
6796   }
6797   return 1;
6798 }
6799
6800 void
6801 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6802 {
6803     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6804     Markers *m = (Markers *) closure;
6805     if(rf == fromY && ff == fromX)
6806         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6807                          || kind == WhiteCapturesEnPassant
6808                          || kind == BlackCapturesEnPassant);
6809     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6810 }
6811
6812 void
6813 MarkTargetSquares (int clear)
6814 {
6815   int x, y;
6816   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6817      !appData.testLegality || gameMode == EditPosition) return;
6818   if(clear) {
6819     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6820   } else {
6821     int capt = 0;
6822     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6823     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6824       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6825       if(capt)
6826       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6827     }
6828   }
6829   DrawPosition(TRUE, NULL);
6830 }
6831
6832 int
6833 Explode (Board board, int fromX, int fromY, int toX, int toY)
6834 {
6835     if(gameInfo.variant == VariantAtomic &&
6836        (board[toY][toX] != EmptySquare ||                     // capture?
6837         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6838                          board[fromY][fromX] == BlackPawn   )
6839       )) {
6840         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6841         return TRUE;
6842     }
6843     return FALSE;
6844 }
6845
6846 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6847
6848 int
6849 CanPromote (ChessSquare piece, int y)
6850 {
6851         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6852         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6853         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6854            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6855            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6856                                                   gameInfo.variant == VariantMakruk) return FALSE;
6857         return (piece == BlackPawn && y == 1 ||
6858                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6859                 piece == BlackLance && y == 1 ||
6860                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6861 }
6862
6863 void
6864 LeftClick (ClickType clickType, int xPix, int yPix)
6865 {
6866     int x, y;
6867     Boolean saveAnimate;
6868     static int second = 0, promotionChoice = 0, clearFlag = 0;
6869     char promoChoice = NULLCHAR;
6870     ChessSquare piece;
6871     static TimeMark lastClickTime, prevClickTime;
6872
6873     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6874
6875     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6876
6877     if (clickType == Press) ErrorPopDown();
6878
6879     x = EventToSquare(xPix, BOARD_WIDTH);
6880     y = EventToSquare(yPix, BOARD_HEIGHT);
6881     if (!flipView && y >= 0) {
6882         y = BOARD_HEIGHT - 1 - y;
6883     }
6884     if (flipView && x >= 0) {
6885         x = BOARD_WIDTH - 1 - x;
6886     }
6887
6888     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6889         defaultPromoChoice = promoSweep;
6890         promoSweep = EmptySquare;   // terminate sweep
6891         promoDefaultAltered = TRUE;
6892         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6893     }
6894
6895     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6896         if(clickType == Release) return; // ignore upclick of click-click destination
6897         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6898         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6899         if(gameInfo.holdingsWidth &&
6900                 (WhiteOnMove(currentMove)
6901                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6902                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6903             // click in right holdings, for determining promotion piece
6904             ChessSquare p = boards[currentMove][y][x];
6905             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6906             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6907             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6908                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6909                 fromX = fromY = -1;
6910                 return;
6911             }
6912         }
6913         DrawPosition(FALSE, boards[currentMove]);
6914         return;
6915     }
6916
6917     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6918     if(clickType == Press
6919             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6920               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6921               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6922         return;
6923
6924     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
6925         // could be static click on premove from-square: abort premove
6926         gotPremove = 0;
6927         ClearPremoveHighlights();
6928     }
6929
6930     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
6931         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6932
6933     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6934         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6935                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6936         defaultPromoChoice = DefaultPromoChoice(side);
6937     }
6938
6939     autoQueen = appData.alwaysPromoteToQueen;
6940
6941     if (fromX == -1) {
6942       int originalY = y;
6943       gatingPiece = EmptySquare;
6944       if (clickType != Press) {
6945         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6946             DragPieceEnd(xPix, yPix); dragging = 0;
6947             DrawPosition(FALSE, NULL);
6948         }
6949         return;
6950       }
6951       doubleClick = FALSE;
6952       fromX = x; fromY = y; toX = toY = -1;
6953       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6954          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6955          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6956             /* First square */
6957             if (OKToStartUserMove(fromX, fromY)) {
6958                 second = 0;
6959                 MarkTargetSquares(0);
6960                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6961                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6962                     promoSweep = defaultPromoChoice;
6963                     selectFlag = 0; lastX = xPix; lastY = yPix;
6964                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6965                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6966                 }
6967                 if (appData.highlightDragging) {
6968                     SetHighlights(fromX, fromY, -1, -1);
6969                 }
6970             } else fromX = fromY = -1;
6971             return;
6972         }
6973     }
6974
6975     /* fromX != -1 */
6976     if (clickType == Press && gameMode != EditPosition) {
6977         ChessSquare fromP;
6978         ChessSquare toP;
6979         int frc;
6980
6981         // ignore off-board to clicks
6982         if(y < 0 || x < 0) return;
6983
6984         /* Check if clicking again on the same color piece */
6985         fromP = boards[currentMove][fromY][fromX];
6986         toP = boards[currentMove][y][x];
6987         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6988         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6989              WhitePawn <= toP && toP <= WhiteKing &&
6990              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6991              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6992             (BlackPawn <= fromP && fromP <= BlackKing &&
6993              BlackPawn <= toP && toP <= BlackKing &&
6994              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6995              !(fromP == BlackKing && toP == BlackRook && frc))) {
6996             /* Clicked again on same color piece -- changed his mind */
6997             second = (x == fromX && y == fromY);
6998             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
6999                 second = FALSE; // first double-click rather than scond click
7000                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7001             }
7002             promoDefaultAltered = FALSE;
7003             MarkTargetSquares(1);
7004            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7005             if (appData.highlightDragging) {
7006                 SetHighlights(x, y, -1, -1);
7007             } else {
7008                 ClearHighlights();
7009             }
7010             if (OKToStartUserMove(x, y)) {
7011                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7012                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7013                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7014                  gatingPiece = boards[currentMove][fromY][fromX];
7015                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7016                 fromX = x;
7017                 fromY = y; dragging = 1;
7018                 MarkTargetSquares(0);
7019                 DragPieceBegin(xPix, yPix, FALSE);
7020                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7021                     promoSweep = defaultPromoChoice;
7022                     selectFlag = 0; lastX = xPix; lastY = yPix;
7023                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7024                 }
7025             }
7026            }
7027            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7028            second = FALSE; 
7029         }
7030         // ignore clicks on holdings
7031         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7032     }
7033
7034     if (clickType == Release && x == fromX && y == fromY) {
7035         DragPieceEnd(xPix, yPix); dragging = 0;
7036         if(clearFlag) {
7037             // a deferred attempt to click-click move an empty square on top of a piece
7038             boards[currentMove][y][x] = EmptySquare;
7039             ClearHighlights();
7040             DrawPosition(FALSE, boards[currentMove]);
7041             fromX = fromY = -1; clearFlag = 0;
7042             return;
7043         }
7044         if (appData.animateDragging) {
7045             /* Undo animation damage if any */
7046             DrawPosition(FALSE, NULL);
7047         }
7048         if (second) {
7049             /* Second up/down in same square; just abort move */
7050             second = 0;
7051             fromX = fromY = -1;
7052             gatingPiece = EmptySquare;
7053             ClearHighlights();
7054             gotPremove = 0;
7055             ClearPremoveHighlights();
7056         } else {
7057             /* First upclick in same square; start click-click mode */
7058             SetHighlights(x, y, -1, -1);
7059         }
7060         return;
7061     }
7062
7063     clearFlag = 0;
7064
7065     /* we now have a different from- and (possibly off-board) to-square */
7066     /* Completed move */
7067     toX = x;
7068     toY = y;
7069     saveAnimate = appData.animate;
7070     MarkTargetSquares(1);
7071     if (clickType == Press) {
7072         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7073             // must be Edit Position mode with empty-square selected
7074             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7075             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7076             return;
7077         }
7078         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7079             ChessSquare piece = boards[currentMove][fromY][fromX];
7080             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7081             promoSweep = defaultPromoChoice;
7082             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7083             selectFlag = 0; lastX = xPix; lastY = yPix;
7084             Sweep(0); // Pawn that is going to promote: preview promotion piece
7085             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7086             DrawPosition(FALSE, boards[currentMove]);
7087             return;
7088         }
7089         /* Finish clickclick move */
7090         if (appData.animate || appData.highlightLastMove) {
7091             SetHighlights(fromX, fromY, toX, toY);
7092         } else {
7093             ClearHighlights();
7094         }
7095     } else {
7096         /* Finish drag move */
7097         if (appData.highlightLastMove) {
7098             SetHighlights(fromX, fromY, toX, toY);
7099         } else {
7100             ClearHighlights();
7101         }
7102         DragPieceEnd(xPix, yPix); dragging = 0;
7103         /* Don't animate move and drag both */
7104         appData.animate = FALSE;
7105     }
7106
7107     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7108     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7109         ChessSquare piece = boards[currentMove][fromY][fromX];
7110         if(gameMode == EditPosition && piece != EmptySquare &&
7111            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7112             int n;
7113
7114             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7115                 n = PieceToNumber(piece - (int)BlackPawn);
7116                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7117                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7118                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7119             } else
7120             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7121                 n = PieceToNumber(piece);
7122                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7123                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7124                 boards[currentMove][n][BOARD_WIDTH-2]++;
7125             }
7126             boards[currentMove][fromY][fromX] = EmptySquare;
7127         }
7128         ClearHighlights();
7129         fromX = fromY = -1;
7130         DrawPosition(TRUE, boards[currentMove]);
7131         return;
7132     }
7133
7134     // off-board moves should not be highlighted
7135     if(x < 0 || y < 0) ClearHighlights();
7136
7137     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7138
7139     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7140         SetHighlights(fromX, fromY, toX, toY);
7141         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7142             // [HGM] super: promotion to captured piece selected from holdings
7143             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7144             promotionChoice = TRUE;
7145             // kludge follows to temporarily execute move on display, without promoting yet
7146             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7147             boards[currentMove][toY][toX] = p;
7148             DrawPosition(FALSE, boards[currentMove]);
7149             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7150             boards[currentMove][toY][toX] = q;
7151             DisplayMessage("Click in holdings to choose piece", "");
7152             return;
7153         }
7154         PromotionPopUp();
7155     } else {
7156         int oldMove = currentMove;
7157         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7158         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7159         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7160         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7161            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7162             DrawPosition(TRUE, boards[currentMove]);
7163         fromX = fromY = -1;
7164     }
7165     appData.animate = saveAnimate;
7166     if (appData.animate || appData.animateDragging) {
7167         /* Undo animation damage if needed */
7168         DrawPosition(FALSE, NULL);
7169     }
7170 }
7171
7172 int
7173 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7174 {   // front-end-free part taken out of PieceMenuPopup
7175     int whichMenu; int xSqr, ySqr;
7176
7177     if(seekGraphUp) { // [HGM] seekgraph
7178         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7179         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7180         return -2;
7181     }
7182
7183     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7184          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7185         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7186         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7187         if(action == Press)   {
7188             originalFlip = flipView;
7189             flipView = !flipView; // temporarily flip board to see game from partners perspective
7190             DrawPosition(TRUE, partnerBoard);
7191             DisplayMessage(partnerStatus, "");
7192             partnerUp = TRUE;
7193         } else if(action == Release) {
7194             flipView = originalFlip;
7195             DrawPosition(TRUE, boards[currentMove]);
7196             partnerUp = FALSE;
7197         }
7198         return -2;
7199     }
7200
7201     xSqr = EventToSquare(x, BOARD_WIDTH);
7202     ySqr = EventToSquare(y, BOARD_HEIGHT);
7203     if (action == Release) {
7204         if(pieceSweep != EmptySquare) {
7205             EditPositionMenuEvent(pieceSweep, toX, toY);
7206             pieceSweep = EmptySquare;
7207         } else UnLoadPV(); // [HGM] pv
7208     }
7209     if (action != Press) return -2; // return code to be ignored
7210     switch (gameMode) {
7211       case IcsExamining:
7212         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7213       case EditPosition:
7214         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7215         if (xSqr < 0 || ySqr < 0) return -1;
7216         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7217         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7218         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7219         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7220         NextPiece(0);
7221         return 2; // grab
7222       case IcsObserving:
7223         if(!appData.icsEngineAnalyze) return -1;
7224       case IcsPlayingWhite:
7225       case IcsPlayingBlack:
7226         if(!appData.zippyPlay) goto noZip;
7227       case AnalyzeMode:
7228       case AnalyzeFile:
7229       case MachinePlaysWhite:
7230       case MachinePlaysBlack:
7231       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7232         if (!appData.dropMenu) {
7233           LoadPV(x, y);
7234           return 2; // flag front-end to grab mouse events
7235         }
7236         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7237            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7238       case EditGame:
7239       noZip:
7240         if (xSqr < 0 || ySqr < 0) return -1;
7241         if (!appData.dropMenu || appData.testLegality &&
7242             gameInfo.variant != VariantBughouse &&
7243             gameInfo.variant != VariantCrazyhouse) return -1;
7244         whichMenu = 1; // drop menu
7245         break;
7246       default:
7247         return -1;
7248     }
7249
7250     if (((*fromX = xSqr) < 0) ||
7251         ((*fromY = ySqr) < 0)) {
7252         *fromX = *fromY = -1;
7253         return -1;
7254     }
7255     if (flipView)
7256       *fromX = BOARD_WIDTH - 1 - *fromX;
7257     else
7258       *fromY = BOARD_HEIGHT - 1 - *fromY;
7259
7260     return whichMenu;
7261 }
7262
7263 void
7264 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7265 {
7266 //    char * hint = lastHint;
7267     FrontEndProgramStats stats;
7268
7269     stats.which = cps == &first ? 0 : 1;
7270     stats.depth = cpstats->depth;
7271     stats.nodes = cpstats->nodes;
7272     stats.score = cpstats->score;
7273     stats.time = cpstats->time;
7274     stats.pv = cpstats->movelist;
7275     stats.hint = lastHint;
7276     stats.an_move_index = 0;
7277     stats.an_move_count = 0;
7278
7279     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7280         stats.hint = cpstats->move_name;
7281         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7282         stats.an_move_count = cpstats->nr_moves;
7283     }
7284
7285     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
7286
7287     SetProgramStats( &stats );
7288 }
7289
7290 void
7291 ClearEngineOutputPane (int which)
7292 {
7293     static FrontEndProgramStats dummyStats;
7294     dummyStats.which = which;
7295     dummyStats.pv = "#";
7296     SetProgramStats( &dummyStats );
7297 }
7298
7299 #define MAXPLAYERS 500
7300
7301 char *
7302 TourneyStandings (int display)
7303 {
7304     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7305     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7306     char result, *p, *names[MAXPLAYERS];
7307
7308     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7309         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7310     names[0] = p = strdup(appData.participants);
7311     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7312
7313     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7314
7315     while(result = appData.results[nr]) {
7316         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7317         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7318         wScore = bScore = 0;
7319         switch(result) {
7320           case '+': wScore = 2; break;
7321           case '-': bScore = 2; break;
7322           case '=': wScore = bScore = 1; break;
7323           case ' ':
7324           case '*': return strdup("busy"); // tourney not finished
7325         }
7326         score[w] += wScore;
7327         score[b] += bScore;
7328         games[w]++;
7329         games[b]++;
7330         nr++;
7331     }
7332     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7333     for(w=0; w<nPlayers; w++) {
7334         bScore = -1;
7335         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7336         ranking[w] = b; points[w] = bScore; score[b] = -2;
7337     }
7338     p = malloc(nPlayers*34+1);
7339     for(w=0; w<nPlayers && w<display; w++)
7340         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7341     free(names[0]);
7342     return p;
7343 }
7344
7345 void
7346 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7347 {       // count all piece types
7348         int p, f, r;
7349         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7350         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7351         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7352                 p = board[r][f];
7353                 pCnt[p]++;
7354                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7355                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7356                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7357                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7358                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7359                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7360         }
7361 }
7362
7363 int
7364 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7365 {
7366         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7367         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7368
7369         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7370         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7371         if(myPawns == 2 && nMine == 3) // KPP
7372             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7373         if(myPawns == 1 && nMine == 2) // KP
7374             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7375         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7376             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7377         if(myPawns) return FALSE;
7378         if(pCnt[WhiteRook+side])
7379             return pCnt[BlackRook-side] ||
7380                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7381                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7382                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7383         if(pCnt[WhiteCannon+side]) {
7384             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7385             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7386         }
7387         if(pCnt[WhiteKnight+side])
7388             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7389         return FALSE;
7390 }
7391
7392 int
7393 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7394 {
7395         VariantClass v = gameInfo.variant;
7396
7397         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7398         if(v == VariantShatranj) return TRUE; // always winnable through baring
7399         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7400         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7401
7402         if(v == VariantXiangqi) {
7403                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7404
7405                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7406                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7407                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7408                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7409                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7410                 if(stale) // we have at least one last-rank P plus perhaps C
7411                     return majors // KPKX
7412                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7413                 else // KCA*E*
7414                     return pCnt[WhiteFerz+side] // KCAK
7415                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7416                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7417                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7418
7419         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7420                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7421
7422                 if(nMine == 1) return FALSE; // bare King
7423                 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
7424                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7425                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7426                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7427                 if(pCnt[WhiteKnight+side])
7428                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7429                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7430                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7431                 if(nBishops)
7432                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7433                 if(pCnt[WhiteAlfil+side])
7434                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7435                 if(pCnt[WhiteWazir+side])
7436                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7437         }
7438
7439         return TRUE;
7440 }
7441
7442 int
7443 CompareWithRights (Board b1, Board b2)
7444 {
7445     int rights = 0;
7446     if(!CompareBoards(b1, b2)) return FALSE;
7447     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7448     /* compare castling rights */
7449     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7450            rights++; /* King lost rights, while rook still had them */
7451     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7452         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7453            rights++; /* but at least one rook lost them */
7454     }
7455     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7456            rights++;
7457     if( b1[CASTLING][5] != NoRights ) {
7458         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7459            rights++;
7460     }
7461     return rights == 0;
7462 }
7463
7464 int
7465 Adjudicate (ChessProgramState *cps)
7466 {       // [HGM] some adjudications useful with buggy engines
7467         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7468         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7469         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7470         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7471         int k, count = 0; static int bare = 1;
7472         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7473         Boolean canAdjudicate = !appData.icsActive;
7474
7475         // most tests only when we understand the game, i.e. legality-checking on
7476             if( appData.testLegality )
7477             {   /* [HGM] Some more adjudications for obstinate engines */
7478                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7479                 static int moveCount = 6;
7480                 ChessMove result;
7481                 char *reason = NULL;
7482
7483                 /* Count what is on board. */
7484                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7485
7486                 /* Some material-based adjudications that have to be made before stalemate test */
7487                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7488                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7489                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7490                      if(canAdjudicate && appData.checkMates) {
7491                          if(engineOpponent)
7492                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7493                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7494                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7495                          return 1;
7496                      }
7497                 }
7498
7499                 /* Bare King in Shatranj (loses) or Losers (wins) */
7500                 if( nrW == 1 || nrB == 1) {
7501                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7502                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7503                      if(canAdjudicate && appData.checkMates) {
7504                          if(engineOpponent)
7505                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7506                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7507                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7508                          return 1;
7509                      }
7510                   } else
7511                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7512                   {    /* bare King */
7513                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7514                         if(canAdjudicate && appData.checkMates) {
7515                             /* but only adjudicate if adjudication enabled */
7516                             if(engineOpponent)
7517                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7518                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7519                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7520                             return 1;
7521                         }
7522                   }
7523                 } else bare = 1;
7524
7525
7526             // don't wait for engine to announce game end if we can judge ourselves
7527             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7528               case MT_CHECK:
7529                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7530                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7531                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7532                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7533                             checkCnt++;
7534                         if(checkCnt >= 2) {
7535                             reason = "Xboard adjudication: 3rd check";
7536                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7537                             break;
7538                         }
7539                     }
7540                 }
7541               case MT_NONE:
7542               default:
7543                 break;
7544               case MT_STALEMATE:
7545               case MT_STAINMATE:
7546                 reason = "Xboard adjudication: Stalemate";
7547                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7548                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7549                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7550                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7551                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7552                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7553                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7554                                                                         EP_CHECKMATE : EP_WINS);
7555                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7556                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7557                 }
7558                 break;
7559               case MT_CHECKMATE:
7560                 reason = "Xboard adjudication: Checkmate";
7561                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7562                 break;
7563             }
7564
7565                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7566                     case EP_STALEMATE:
7567                         result = GameIsDrawn; break;
7568                     case EP_CHECKMATE:
7569                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7570                     case EP_WINS:
7571                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7572                     default:
7573                         result = EndOfFile;
7574                 }
7575                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7576                     if(engineOpponent)
7577                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7578                     GameEnds( result, reason, GE_XBOARD );
7579                     return 1;
7580                 }
7581
7582                 /* Next absolutely insufficient mating material. */
7583                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7584                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7585                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7586
7587                      /* always flag draws, for judging claims */
7588                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7589
7590                      if(canAdjudicate && appData.materialDraws) {
7591                          /* but only adjudicate them if adjudication enabled */
7592                          if(engineOpponent) {
7593                            SendToProgram("force\n", engineOpponent); // suppress reply
7594                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7595                          }
7596                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7597                          return 1;
7598                      }
7599                 }
7600
7601                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7602                 if(gameInfo.variant == VariantXiangqi ?
7603                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7604                  : nrW + nrB == 4 &&
7605                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7606                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7607                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7608                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7609                    ) ) {
7610                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7611                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7612                           if(engineOpponent) {
7613                             SendToProgram("force\n", engineOpponent); // suppress reply
7614                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7615                           }
7616                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7617                           return 1;
7618                      }
7619                 } else moveCount = 6;
7620             }
7621
7622         // Repetition draws and 50-move rule can be applied independently of legality testing
7623
7624                 /* Check for rep-draws */
7625                 count = 0;
7626                 for(k = forwardMostMove-2;
7627                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7628                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7629                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7630                     k-=2)
7631                 {   int rights=0;
7632                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7633                         /* compare castling rights */
7634                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7635                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7636                                 rights++; /* King lost rights, while rook still had them */
7637                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7638                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7639                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7640                                    rights++; /* but at least one rook lost them */
7641                         }
7642                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7643                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7644                                 rights++;
7645                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7646                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7647                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7648                                    rights++;
7649                         }
7650                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7651                             && appData.drawRepeats > 1) {
7652                              /* adjudicate after user-specified nr of repeats */
7653                              int result = GameIsDrawn;
7654                              char *details = "XBoard adjudication: repetition draw";
7655                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7656                                 // [HGM] xiangqi: check for forbidden perpetuals
7657                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7658                                 for(m=forwardMostMove; m>k; m-=2) {
7659                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7660                                         ourPerpetual = 0; // the current mover did not always check
7661                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7662                                         hisPerpetual = 0; // the opponent did not always check
7663                                 }
7664                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7665                                                                         ourPerpetual, hisPerpetual);
7666                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7667                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7668                                     details = "Xboard adjudication: perpetual checking";
7669                                 } else
7670                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7671                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7672                                 } else
7673                                 // Now check for perpetual chases
7674                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7675                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7676                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7677                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7678                                         static char resdet[MSG_SIZ];
7679                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7680                                         details = resdet;
7681                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7682                                     } else
7683                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7684                                         break; // Abort repetition-checking loop.
7685                                 }
7686                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7687                              }
7688                              if(engineOpponent) {
7689                                SendToProgram("force\n", engineOpponent); // suppress reply
7690                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7691                              }
7692                              GameEnds( result, details, GE_XBOARD );
7693                              return 1;
7694                         }
7695                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7696                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7697                     }
7698                 }
7699
7700                 /* Now we test for 50-move draws. Determine ply count */
7701                 count = forwardMostMove;
7702                 /* look for last irreversble move */
7703                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7704                     count--;
7705                 /* if we hit starting position, add initial plies */
7706                 if( count == backwardMostMove )
7707                     count -= initialRulePlies;
7708                 count = forwardMostMove - count;
7709                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7710                         // adjust reversible move counter for checks in Xiangqi
7711                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7712                         if(i < backwardMostMove) i = backwardMostMove;
7713                         while(i <= forwardMostMove) {
7714                                 lastCheck = inCheck; // check evasion does not count
7715                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7716                                 if(inCheck || lastCheck) count--; // check does not count
7717                                 i++;
7718                         }
7719                 }
7720                 if( count >= 100)
7721                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7722                          /* this is used to judge if draw claims are legal */
7723                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7724                          if(engineOpponent) {
7725                            SendToProgram("force\n", engineOpponent); // suppress reply
7726                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7727                          }
7728                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7729                          return 1;
7730                 }
7731
7732                 /* if draw offer is pending, treat it as a draw claim
7733                  * when draw condition present, to allow engines a way to
7734                  * claim draws before making their move to avoid a race
7735                  * condition occurring after their move
7736                  */
7737                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7738                          char *p = NULL;
7739                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7740                              p = "Draw claim: 50-move rule";
7741                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7742                              p = "Draw claim: 3-fold repetition";
7743                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7744                              p = "Draw claim: insufficient mating material";
7745                          if( p != NULL && canAdjudicate) {
7746                              if(engineOpponent) {
7747                                SendToProgram("force\n", engineOpponent); // suppress reply
7748                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7749                              }
7750                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7751                              return 1;
7752                          }
7753                 }
7754
7755                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7756                     if(engineOpponent) {
7757                       SendToProgram("force\n", engineOpponent); // suppress reply
7758                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7759                     }
7760                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7761                     return 1;
7762                 }
7763         return 0;
7764 }
7765
7766 char *
7767 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7768 {   // [HGM] book: this routine intercepts moves to simulate book replies
7769     char *bookHit = NULL;
7770
7771     //first determine if the incoming move brings opponent into his book
7772     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7773         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7774     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7775     if(bookHit != NULL && !cps->bookSuspend) {
7776         // make sure opponent is not going to reply after receiving move to book position
7777         SendToProgram("force\n", cps);
7778         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7779     }
7780     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7781     // now arrange restart after book miss
7782     if(bookHit) {
7783         // after a book hit we never send 'go', and the code after the call to this routine
7784         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7785         char buf[MSG_SIZ], *move = bookHit;
7786         if(cps->useSAN) {
7787             int fromX, fromY, toX, toY;
7788             char promoChar;
7789             ChessMove moveType;
7790             move = buf + 30;
7791             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7792                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7793                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7794                                     PosFlags(forwardMostMove),
7795                                     fromY, fromX, toY, toX, promoChar, move);
7796             } else {
7797                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7798                 bookHit = NULL;
7799             }
7800         }
7801         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7802         SendToProgram(buf, cps);
7803         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7804     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7805         SendToProgram("go\n", cps);
7806         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7807     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7808         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7809             SendToProgram("go\n", cps);
7810         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7811     }
7812     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7813 }
7814
7815 char *savedMessage;
7816 ChessProgramState *savedState;
7817 void
7818 DeferredBookMove (void)
7819 {
7820         if(savedState->lastPing != savedState->lastPong)
7821                     ScheduleDelayedEvent(DeferredBookMove, 10);
7822         else
7823         HandleMachineMove(savedMessage, savedState);
7824 }
7825
7826 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7827
7828 void
7829 HandleMachineMove (char *message, ChessProgramState *cps)
7830 {
7831     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7832     char realname[MSG_SIZ];
7833     int fromX, fromY, toX, toY;
7834     ChessMove moveType;
7835     char promoChar;
7836     char *p, *pv=buf1;
7837     int machineWhite;
7838     char *bookHit;
7839
7840     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7841         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7842         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7843             DisplayError(_("Invalid pairing from pairing engine"), 0);
7844             return;
7845         }
7846         pairingReceived = 1;
7847         NextMatchGame();
7848         return; // Skim the pairing messages here.
7849     }
7850
7851     cps->userError = 0;
7852
7853 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7854     /*
7855      * Kludge to ignore BEL characters
7856      */
7857     while (*message == '\007') message++;
7858
7859     /*
7860      * [HGM] engine debug message: ignore lines starting with '#' character
7861      */
7862     if(cps->debug && *message == '#') return;
7863
7864     /*
7865      * Look for book output
7866      */
7867     if (cps == &first && bookRequested) {
7868         if (message[0] == '\t' || message[0] == ' ') {
7869             /* Part of the book output is here; append it */
7870             strcat(bookOutput, message);
7871             strcat(bookOutput, "  \n");
7872             return;
7873         } else if (bookOutput[0] != NULLCHAR) {
7874             /* All of book output has arrived; display it */
7875             char *p = bookOutput;
7876             while (*p != NULLCHAR) {
7877                 if (*p == '\t') *p = ' ';
7878                 p++;
7879             }
7880             DisplayInformation(bookOutput);
7881             bookRequested = FALSE;
7882             /* Fall through to parse the current output */
7883         }
7884     }
7885
7886     /*
7887      * Look for machine move.
7888      */
7889     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7890         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7891     {
7892         /* This method is only useful on engines that support ping */
7893         if (cps->lastPing != cps->lastPong) {
7894           if (gameMode == BeginningOfGame) {
7895             /* Extra move from before last new; ignore */
7896             if (appData.debugMode) {
7897                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7898             }
7899           } else {
7900             if (appData.debugMode) {
7901                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7902                         cps->which, gameMode);
7903             }
7904
7905             SendToProgram("undo\n", cps);
7906           }
7907           return;
7908         }
7909
7910         switch (gameMode) {
7911           case BeginningOfGame:
7912             /* Extra move from before last reset; ignore */
7913             if (appData.debugMode) {
7914                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7915             }
7916             return;
7917
7918           case EndOfGame:
7919           case IcsIdle:
7920           default:
7921             /* Extra move after we tried to stop.  The mode test is
7922                not a reliable way of detecting this problem, but it's
7923                the best we can do on engines that don't support ping.
7924             */
7925             if (appData.debugMode) {
7926                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7927                         cps->which, gameMode);
7928             }
7929             SendToProgram("undo\n", cps);
7930             return;
7931
7932           case MachinePlaysWhite:
7933           case IcsPlayingWhite:
7934             machineWhite = TRUE;
7935             break;
7936
7937           case MachinePlaysBlack:
7938           case IcsPlayingBlack:
7939             machineWhite = FALSE;
7940             break;
7941
7942           case TwoMachinesPlay:
7943             machineWhite = (cps->twoMachinesColor[0] == 'w');
7944             break;
7945         }
7946         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7947             if (appData.debugMode) {
7948                 fprintf(debugFP,
7949                         "Ignoring move out of turn by %s, gameMode %d"
7950                         ", forwardMost %d\n",
7951                         cps->which, gameMode, forwardMostMove);
7952             }
7953             return;
7954         }
7955
7956         if(cps->alphaRank) AlphaRank(machineMove, 4);
7957         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7958                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7959             /* Machine move could not be parsed; ignore it. */
7960           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7961                     machineMove, _(cps->which));
7962             DisplayError(buf1, 0);
7963             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7964                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7965             if (gameMode == TwoMachinesPlay) {
7966               GameEnds(machineWhite ? BlackWins : WhiteWins,
7967                        buf1, GE_XBOARD);
7968             }
7969             return;
7970         }
7971
7972         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7973         /* So we have to redo legality test with true e.p. status here,  */
7974         /* to make sure an illegal e.p. capture does not slip through,   */
7975         /* to cause a forfeit on a justified illegal-move complaint      */
7976         /* of the opponent.                                              */
7977         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7978            ChessMove moveType;
7979            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7980                              fromY, fromX, toY, toX, promoChar);
7981             if(moveType == IllegalMove) {
7982               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7983                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7984                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7985                            buf1, GE_XBOARD);
7986                 return;
7987            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7988            /* [HGM] Kludge to handle engines that send FRC-style castling
7989               when they shouldn't (like TSCP-Gothic) */
7990            switch(moveType) {
7991              case WhiteASideCastleFR:
7992              case BlackASideCastleFR:
7993                toX+=2;
7994                currentMoveString[2]++;
7995                break;
7996              case WhiteHSideCastleFR:
7997              case BlackHSideCastleFR:
7998                toX--;
7999                currentMoveString[2]--;
8000                break;
8001              default: ; // nothing to do, but suppresses warning of pedantic compilers
8002            }
8003         }
8004         hintRequested = FALSE;
8005         lastHint[0] = NULLCHAR;
8006         bookRequested = FALSE;
8007         /* Program may be pondering now */
8008         cps->maybeThinking = TRUE;
8009         if (cps->sendTime == 2) cps->sendTime = 1;
8010         if (cps->offeredDraw) cps->offeredDraw--;
8011
8012         /* [AS] Save move info*/
8013         pvInfoList[ forwardMostMove ].score = programStats.score;
8014         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8015         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8016
8017         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8018
8019         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8020         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8021             int count = 0;
8022
8023             while( count < adjudicateLossPlies ) {
8024                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8025
8026                 if( count & 1 ) {
8027                     score = -score; /* Flip score for winning side */
8028                 }
8029
8030                 if( score > adjudicateLossThreshold ) {
8031                     break;
8032                 }
8033
8034                 count++;
8035             }
8036
8037             if( count >= adjudicateLossPlies ) {
8038                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8039
8040                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8041                     "Xboard adjudication",
8042                     GE_XBOARD );
8043
8044                 return;
8045             }
8046         }
8047
8048         if(Adjudicate(cps)) {
8049             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8050             return; // [HGM] adjudicate: for all automatic game ends
8051         }
8052
8053 #if ZIPPY
8054         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8055             first.initDone) {
8056           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8057                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8058                 SendToICS("draw ");
8059                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8060           }
8061           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8062           ics_user_moved = 1;
8063           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8064                 char buf[3*MSG_SIZ];
8065
8066                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8067                         programStats.score / 100.,
8068                         programStats.depth,
8069                         programStats.time / 100.,
8070                         (unsigned int)programStats.nodes,
8071                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8072                         programStats.movelist);
8073                 SendToICS(buf);
8074 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8075           }
8076         }
8077 #endif
8078
8079         /* [AS] Clear stats for next move */
8080         ClearProgramStats();
8081         thinkOutput[0] = NULLCHAR;
8082         hiddenThinkOutputState = 0;
8083
8084         bookHit = NULL;
8085         if (gameMode == TwoMachinesPlay) {
8086             /* [HGM] relaying draw offers moved to after reception of move */
8087             /* and interpreting offer as claim if it brings draw condition */
8088             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8089                 SendToProgram("draw\n", cps->other);
8090             }
8091             if (cps->other->sendTime) {
8092                 SendTimeRemaining(cps->other,
8093                                   cps->other->twoMachinesColor[0] == 'w');
8094             }
8095             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8096             if (firstMove && !bookHit) {
8097                 firstMove = FALSE;
8098                 if (cps->other->useColors) {
8099                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8100                 }
8101                 SendToProgram("go\n", cps->other);
8102             }
8103             cps->other->maybeThinking = TRUE;
8104         }
8105
8106         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8107
8108         if (!pausing && appData.ringBellAfterMoves) {
8109             RingBell();
8110         }
8111
8112         /*
8113          * Reenable menu items that were disabled while
8114          * machine was thinking
8115          */
8116         if (gameMode != TwoMachinesPlay)
8117             SetUserThinkingEnables();
8118
8119         // [HGM] book: after book hit opponent has received move and is now in force mode
8120         // force the book reply into it, and then fake that it outputted this move by jumping
8121         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8122         if(bookHit) {
8123                 static char bookMove[MSG_SIZ]; // a bit generous?
8124
8125                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8126                 strcat(bookMove, bookHit);
8127                 message = bookMove;
8128                 cps = cps->other;
8129                 programStats.nodes = programStats.depth = programStats.time =
8130                 programStats.score = programStats.got_only_move = 0;
8131                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8132
8133                 if(cps->lastPing != cps->lastPong) {
8134                     savedMessage = message; // args for deferred call
8135                     savedState = cps;
8136                     ScheduleDelayedEvent(DeferredBookMove, 10);
8137                     return;
8138                 }
8139                 goto FakeBookMove;
8140         }
8141
8142         return;
8143     }
8144
8145     /* Set special modes for chess engines.  Later something general
8146      *  could be added here; for now there is just one kludge feature,
8147      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8148      *  when "xboard" is given as an interactive command.
8149      */
8150     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8151         cps->useSigint = FALSE;
8152         cps->useSigterm = FALSE;
8153     }
8154     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8155       ParseFeatures(message+8, cps);
8156       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8157     }
8158
8159     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8160                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8161       int dummy, s=6; char buf[MSG_SIZ];
8162       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8163       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8164       if(startedFromSetupPosition) return;
8165       ParseFEN(boards[0], &dummy, message+s);
8166       DrawPosition(TRUE, boards[0]);
8167       startedFromSetupPosition = TRUE;
8168       return;
8169     }
8170     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8171      * want this, I was asked to put it in, and obliged.
8172      */
8173     if (!strncmp(message, "setboard ", 9)) {
8174         Board initial_position;
8175
8176         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8177
8178         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8179             DisplayError(_("Bad FEN received from engine"), 0);
8180             return ;
8181         } else {
8182            Reset(TRUE, FALSE);
8183            CopyBoard(boards[0], initial_position);
8184            initialRulePlies = FENrulePlies;
8185            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8186            else gameMode = MachinePlaysBlack;
8187            DrawPosition(FALSE, boards[currentMove]);
8188         }
8189         return;
8190     }
8191
8192     /*
8193      * Look for communication commands
8194      */
8195     if (!strncmp(message, "telluser ", 9)) {
8196         if(message[9] == '\\' && message[10] == '\\')
8197             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8198         PlayTellSound();
8199         DisplayNote(message + 9);
8200         return;
8201     }
8202     if (!strncmp(message, "tellusererror ", 14)) {
8203         cps->userError = 1;
8204         if(message[14] == '\\' && message[15] == '\\')
8205             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8206         PlayTellSound();
8207         DisplayError(message + 14, 0);
8208         return;
8209     }
8210     if (!strncmp(message, "tellopponent ", 13)) {
8211       if (appData.icsActive) {
8212         if (loggedOn) {
8213           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8214           SendToICS(buf1);
8215         }
8216       } else {
8217         DisplayNote(message + 13);
8218       }
8219       return;
8220     }
8221     if (!strncmp(message, "tellothers ", 11)) {
8222       if (appData.icsActive) {
8223         if (loggedOn) {
8224           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8225           SendToICS(buf1);
8226         }
8227       }
8228       return;
8229     }
8230     if (!strncmp(message, "tellall ", 8)) {
8231       if (appData.icsActive) {
8232         if (loggedOn) {
8233           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8234           SendToICS(buf1);
8235         }
8236       } else {
8237         DisplayNote(message + 8);
8238       }
8239       return;
8240     }
8241     if (strncmp(message, "warning", 7) == 0) {
8242         /* Undocumented feature, use tellusererror in new code */
8243         DisplayError(message, 0);
8244         return;
8245     }
8246     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8247         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8248         strcat(realname, " query");
8249         AskQuestion(realname, buf2, buf1, cps->pr);
8250         return;
8251     }
8252     /* Commands from the engine directly to ICS.  We don't allow these to be
8253      *  sent until we are logged on. Crafty kibitzes have been known to
8254      *  interfere with the login process.
8255      */
8256     if (loggedOn) {
8257         if (!strncmp(message, "tellics ", 8)) {
8258             SendToICS(message + 8);
8259             SendToICS("\n");
8260             return;
8261         }
8262         if (!strncmp(message, "tellicsnoalias ", 15)) {
8263             SendToICS(ics_prefix);
8264             SendToICS(message + 15);
8265             SendToICS("\n");
8266             return;
8267         }
8268         /* The following are for backward compatibility only */
8269         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8270             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8271             SendToICS(ics_prefix);
8272             SendToICS(message);
8273             SendToICS("\n");
8274             return;
8275         }
8276     }
8277     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8278         return;
8279     }
8280     /*
8281      * If the move is illegal, cancel it and redraw the board.
8282      * Also deal with other error cases.  Matching is rather loose
8283      * here to accommodate engines written before the spec.
8284      */
8285     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8286         strncmp(message, "Error", 5) == 0) {
8287         if (StrStr(message, "name") ||
8288             StrStr(message, "rating") || StrStr(message, "?") ||
8289             StrStr(message, "result") || StrStr(message, "board") ||
8290             StrStr(message, "bk") || StrStr(message, "computer") ||
8291             StrStr(message, "variant") || StrStr(message, "hint") ||
8292             StrStr(message, "random") || StrStr(message, "depth") ||
8293             StrStr(message, "accepted")) {
8294             return;
8295         }
8296         if (StrStr(message, "protover")) {
8297           /* Program is responding to input, so it's apparently done
8298              initializing, and this error message indicates it is
8299              protocol version 1.  So we don't need to wait any longer
8300              for it to initialize and send feature commands. */
8301           FeatureDone(cps, 1);
8302           cps->protocolVersion = 1;
8303           return;
8304         }
8305         cps->maybeThinking = FALSE;
8306
8307         if (StrStr(message, "draw")) {
8308             /* Program doesn't have "draw" command */
8309             cps->sendDrawOffers = 0;
8310             return;
8311         }
8312         if (cps->sendTime != 1 &&
8313             (StrStr(message, "time") || StrStr(message, "otim"))) {
8314           /* Program apparently doesn't have "time" or "otim" command */
8315           cps->sendTime = 0;
8316           return;
8317         }
8318         if (StrStr(message, "analyze")) {
8319             cps->analysisSupport = FALSE;
8320             cps->analyzing = FALSE;
8321 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8322             EditGameEvent(); // [HGM] try to preserve loaded game
8323             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8324             DisplayError(buf2, 0);
8325             return;
8326         }
8327         if (StrStr(message, "(no matching move)st")) {
8328           /* Special kludge for GNU Chess 4 only */
8329           cps->stKludge = TRUE;
8330           SendTimeControl(cps, movesPerSession, timeControl,
8331                           timeIncrement, appData.searchDepth,
8332                           searchTime);
8333           return;
8334         }
8335         if (StrStr(message, "(no matching move)sd")) {
8336           /* Special kludge for GNU Chess 4 only */
8337           cps->sdKludge = TRUE;
8338           SendTimeControl(cps, movesPerSession, timeControl,
8339                           timeIncrement, appData.searchDepth,
8340                           searchTime);
8341           return;
8342         }
8343         if (!StrStr(message, "llegal")) {
8344             return;
8345         }
8346         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8347             gameMode == IcsIdle) return;
8348         if (forwardMostMove <= backwardMostMove) return;
8349         if (pausing) PauseEvent();
8350       if(appData.forceIllegal) {
8351             // [HGM] illegal: machine refused move; force position after move into it
8352           SendToProgram("force\n", cps);
8353           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8354                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8355                 // when black is to move, while there might be nothing on a2 or black
8356                 // might already have the move. So send the board as if white has the move.
8357                 // But first we must change the stm of the engine, as it refused the last move
8358                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8359                 if(WhiteOnMove(forwardMostMove)) {
8360                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8361                     SendBoard(cps, forwardMostMove); // kludgeless board
8362                 } else {
8363                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8364                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8365                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8366                 }
8367           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8368             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8369                  gameMode == TwoMachinesPlay)
8370               SendToProgram("go\n", cps);
8371             return;
8372       } else
8373         if (gameMode == PlayFromGameFile) {
8374             /* Stop reading this game file */
8375             gameMode = EditGame;
8376             ModeHighlight();
8377         }
8378         /* [HGM] illegal-move claim should forfeit game when Xboard */
8379         /* only passes fully legal moves                            */
8380         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8381             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8382                                 "False illegal-move claim", GE_XBOARD );
8383             return; // do not take back move we tested as valid
8384         }
8385         currentMove = forwardMostMove-1;
8386         DisplayMove(currentMove-1); /* before DisplayMoveError */
8387         SwitchClocks(forwardMostMove-1); // [HGM] race
8388         DisplayBothClocks();
8389         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8390                 parseList[currentMove], _(cps->which));
8391         DisplayMoveError(buf1);
8392         DrawPosition(FALSE, boards[currentMove]);
8393
8394         SetUserThinkingEnables();
8395         return;
8396     }
8397     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8398         /* Program has a broken "time" command that
8399            outputs a string not ending in newline.
8400            Don't use it. */
8401         cps->sendTime = 0;
8402     }
8403
8404     /*
8405      * If chess program startup fails, exit with an error message.
8406      * Attempts to recover here are futile. [HGM] Well, we try anyway
8407      */
8408     if ((StrStr(message, "unknown host") != NULL)
8409         || (StrStr(message, "No remote directory") != NULL)
8410         || (StrStr(message, "not found") != NULL)
8411         || (StrStr(message, "No such file") != NULL)
8412         || (StrStr(message, "can't alloc") != NULL)
8413         || (StrStr(message, "Permission denied") != NULL)) {
8414
8415         cps->maybeThinking = FALSE;
8416         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8417                 _(cps->which), cps->program, cps->host, message);
8418         RemoveInputSource(cps->isr);
8419         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8420             cps->isr = NULL;
8421             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8422             cps->pr = NoProc; 
8423             if(cps == &first) {
8424                 appData.noChessProgram = TRUE;
8425                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8426                 gameMode = BeginningOfGame; ModeHighlight();
8427                 SetNCPMode();
8428             }
8429             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8430             DisplayMessage("", ""); // erase waiting message
8431             DisplayError(buf1, 0);
8432         }
8433         return;
8434     }
8435
8436     /*
8437      * Look for hint output
8438      */
8439     if (sscanf(message, "Hint: %s", buf1) == 1) {
8440         if (cps == &first && hintRequested) {
8441             hintRequested = FALSE;
8442             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8443                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8444                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8445                                     PosFlags(forwardMostMove),
8446                                     fromY, fromX, toY, toX, promoChar, buf1);
8447                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8448                 DisplayInformation(buf2);
8449             } else {
8450                 /* Hint move could not be parsed!? */
8451               snprintf(buf2, sizeof(buf2),
8452                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8453                         buf1, _(cps->which));
8454                 DisplayError(buf2, 0);
8455             }
8456         } else {
8457           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8458         }
8459         return;
8460     }
8461
8462     /*
8463      * Ignore other messages if game is not in progress
8464      */
8465     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8466         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8467
8468     /*
8469      * look for win, lose, draw, or draw offer
8470      */
8471     if (strncmp(message, "1-0", 3) == 0) {
8472         char *p, *q, *r = "";
8473         p = strchr(message, '{');
8474         if (p) {
8475             q = strchr(p, '}');
8476             if (q) {
8477                 *q = NULLCHAR;
8478                 r = p + 1;
8479             }
8480         }
8481         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8482         return;
8483     } else if (strncmp(message, "0-1", 3) == 0) {
8484         char *p, *q, *r = "";
8485         p = strchr(message, '{');
8486         if (p) {
8487             q = strchr(p, '}');
8488             if (q) {
8489                 *q = NULLCHAR;
8490                 r = p + 1;
8491             }
8492         }
8493         /* Kludge for Arasan 4.1 bug */
8494         if (strcmp(r, "Black resigns") == 0) {
8495             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8496             return;
8497         }
8498         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8499         return;
8500     } else if (strncmp(message, "1/2", 3) == 0) {
8501         char *p, *q, *r = "";
8502         p = strchr(message, '{');
8503         if (p) {
8504             q = strchr(p, '}');
8505             if (q) {
8506                 *q = NULLCHAR;
8507                 r = p + 1;
8508             }
8509         }
8510
8511         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8512         return;
8513
8514     } else if (strncmp(message, "White resign", 12) == 0) {
8515         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8516         return;
8517     } else if (strncmp(message, "Black resign", 12) == 0) {
8518         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8519         return;
8520     } else if (strncmp(message, "White matches", 13) == 0 ||
8521                strncmp(message, "Black matches", 13) == 0   ) {
8522         /* [HGM] ignore GNUShogi noises */
8523         return;
8524     } else if (strncmp(message, "White", 5) == 0 &&
8525                message[5] != '(' &&
8526                StrStr(message, "Black") == NULL) {
8527         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8528         return;
8529     } else if (strncmp(message, "Black", 5) == 0 &&
8530                message[5] != '(') {
8531         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8532         return;
8533     } else if (strcmp(message, "resign") == 0 ||
8534                strcmp(message, "computer resigns") == 0) {
8535         switch (gameMode) {
8536           case MachinePlaysBlack:
8537           case IcsPlayingBlack:
8538             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8539             break;
8540           case MachinePlaysWhite:
8541           case IcsPlayingWhite:
8542             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8543             break;
8544           case TwoMachinesPlay:
8545             if (cps->twoMachinesColor[0] == 'w')
8546               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8547             else
8548               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8549             break;
8550           default:
8551             /* can't happen */
8552             break;
8553         }
8554         return;
8555     } else if (strncmp(message, "opponent mates", 14) == 0) {
8556         switch (gameMode) {
8557           case MachinePlaysBlack:
8558           case IcsPlayingBlack:
8559             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8560             break;
8561           case MachinePlaysWhite:
8562           case IcsPlayingWhite:
8563             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8564             break;
8565           case TwoMachinesPlay:
8566             if (cps->twoMachinesColor[0] == 'w')
8567               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8568             else
8569               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8570             break;
8571           default:
8572             /* can't happen */
8573             break;
8574         }
8575         return;
8576     } else if (strncmp(message, "computer mates", 14) == 0) {
8577         switch (gameMode) {
8578           case MachinePlaysBlack:
8579           case IcsPlayingBlack:
8580             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8581             break;
8582           case MachinePlaysWhite:
8583           case IcsPlayingWhite:
8584             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8585             break;
8586           case TwoMachinesPlay:
8587             if (cps->twoMachinesColor[0] == 'w')
8588               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8589             else
8590               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8591             break;
8592           default:
8593             /* can't happen */
8594             break;
8595         }
8596         return;
8597     } else if (strncmp(message, "checkmate", 9) == 0) {
8598         if (WhiteOnMove(forwardMostMove)) {
8599             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8600         } else {
8601             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8602         }
8603         return;
8604     } else if (strstr(message, "Draw") != NULL ||
8605                strstr(message, "game is a draw") != NULL) {
8606         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8607         return;
8608     } else if (strstr(message, "offer") != NULL &&
8609                strstr(message, "draw") != NULL) {
8610 #if ZIPPY
8611         if (appData.zippyPlay && first.initDone) {
8612             /* Relay offer to ICS */
8613             SendToICS(ics_prefix);
8614             SendToICS("draw\n");
8615         }
8616 #endif
8617         cps->offeredDraw = 2; /* valid until this engine moves twice */
8618         if (gameMode == TwoMachinesPlay) {
8619             if (cps->other->offeredDraw) {
8620                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8621             /* [HGM] in two-machine mode we delay relaying draw offer      */
8622             /* until after we also have move, to see if it is really claim */
8623             }
8624         } else if (gameMode == MachinePlaysWhite ||
8625                    gameMode == MachinePlaysBlack) {
8626           if (userOfferedDraw) {
8627             DisplayInformation(_("Machine accepts your draw offer"));
8628             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8629           } else {
8630             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8631           }
8632         }
8633     }
8634
8635
8636     /*
8637      * Look for thinking output
8638      */
8639     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8640           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8641                                 ) {
8642         int plylev, mvleft, mvtot, curscore, time;
8643         char mvname[MOVE_LEN];
8644         u64 nodes; // [DM]
8645         char plyext;
8646         int ignore = FALSE;
8647         int prefixHint = FALSE;
8648         mvname[0] = NULLCHAR;
8649
8650         switch (gameMode) {
8651           case MachinePlaysBlack:
8652           case IcsPlayingBlack:
8653             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8654             break;
8655           case MachinePlaysWhite:
8656           case IcsPlayingWhite:
8657             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8658             break;
8659           case AnalyzeMode:
8660           case AnalyzeFile:
8661             break;
8662           case IcsObserving: /* [DM] icsEngineAnalyze */
8663             if (!appData.icsEngineAnalyze) ignore = TRUE;
8664             break;
8665           case TwoMachinesPlay:
8666             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8667                 ignore = TRUE;
8668             }
8669             break;
8670           default:
8671             ignore = TRUE;
8672             break;
8673         }
8674
8675         if (!ignore) {
8676             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8677             buf1[0] = NULLCHAR;
8678             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8679                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8680
8681                 if (plyext != ' ' && plyext != '\t') {
8682                     time *= 100;
8683                 }
8684
8685                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8686                 if( cps->scoreIsAbsolute &&
8687                     ( gameMode == MachinePlaysBlack ||
8688                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8689                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8690                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8691                      !WhiteOnMove(currentMove)
8692                     ) )
8693                 {
8694                     curscore = -curscore;
8695                 }
8696
8697                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8698
8699                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8700                         char buf[MSG_SIZ];
8701                         FILE *f;
8702                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8703                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8704                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8705                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8706                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8707                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8708                                 fclose(f);
8709                         } else DisplayError(_("failed writing PV"), 0);
8710                 }
8711
8712                 tempStats.depth = plylev;
8713                 tempStats.nodes = nodes;
8714                 tempStats.time = time;
8715                 tempStats.score = curscore;
8716                 tempStats.got_only_move = 0;
8717
8718                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8719                         int ticklen;
8720
8721                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8722                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8723                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8724                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8725                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8726                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8727                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8728                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8729                 }
8730
8731                 /* Buffer overflow protection */
8732                 if (pv[0] != NULLCHAR) {
8733                     if (strlen(pv) >= sizeof(tempStats.movelist)
8734                         && appData.debugMode) {
8735                         fprintf(debugFP,
8736                                 "PV is too long; using the first %u bytes.\n",
8737                                 (unsigned) sizeof(tempStats.movelist) - 1);
8738                     }
8739
8740                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8741                 } else {
8742                     sprintf(tempStats.movelist, " no PV\n");
8743                 }
8744
8745                 if (tempStats.seen_stat) {
8746                     tempStats.ok_to_send = 1;
8747                 }
8748
8749                 if (strchr(tempStats.movelist, '(') != NULL) {
8750                     tempStats.line_is_book = 1;
8751                     tempStats.nr_moves = 0;
8752                     tempStats.moves_left = 0;
8753                 } else {
8754                     tempStats.line_is_book = 0;
8755                 }
8756
8757                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8758                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8759
8760                 SendProgramStatsToFrontend( cps, &tempStats );
8761
8762                 /*
8763                     [AS] Protect the thinkOutput buffer from overflow... this
8764                     is only useful if buf1 hasn't overflowed first!
8765                 */
8766                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8767                          plylev,
8768                          (gameMode == TwoMachinesPlay ?
8769                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8770                          ((double) curscore) / 100.0,
8771                          prefixHint ? lastHint : "",
8772                          prefixHint ? " " : "" );
8773
8774                 if( buf1[0] != NULLCHAR ) {
8775                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8776
8777                     if( strlen(pv) > max_len ) {
8778                         if( appData.debugMode) {
8779                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8780                         }
8781                         pv[max_len+1] = '\0';
8782                     }
8783
8784                     strcat( thinkOutput, pv);
8785                 }
8786
8787                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8788                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8789                     DisplayMove(currentMove - 1);
8790                 }
8791                 return;
8792
8793             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8794                 /* crafty (9.25+) says "(only move) <move>"
8795                  * if there is only 1 legal move
8796                  */
8797                 sscanf(p, "(only move) %s", buf1);
8798                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8799                 sprintf(programStats.movelist, "%s (only move)", buf1);
8800                 programStats.depth = 1;
8801                 programStats.nr_moves = 1;
8802                 programStats.moves_left = 1;
8803                 programStats.nodes = 1;
8804                 programStats.time = 1;
8805                 programStats.got_only_move = 1;
8806
8807                 /* Not really, but we also use this member to
8808                    mean "line isn't going to change" (Crafty
8809                    isn't searching, so stats won't change) */
8810                 programStats.line_is_book = 1;
8811
8812                 SendProgramStatsToFrontend( cps, &programStats );
8813
8814                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8815                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8816                     DisplayMove(currentMove - 1);
8817                 }
8818                 return;
8819             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8820                               &time, &nodes, &plylev, &mvleft,
8821                               &mvtot, mvname) >= 5) {
8822                 /* The stat01: line is from Crafty (9.29+) in response
8823                    to the "." command */
8824                 programStats.seen_stat = 1;
8825                 cps->maybeThinking = TRUE;
8826
8827                 if (programStats.got_only_move || !appData.periodicUpdates)
8828                   return;
8829
8830                 programStats.depth = plylev;
8831                 programStats.time = time;
8832                 programStats.nodes = nodes;
8833                 programStats.moves_left = mvleft;
8834                 programStats.nr_moves = mvtot;
8835                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8836                 programStats.ok_to_send = 1;
8837                 programStats.movelist[0] = '\0';
8838
8839                 SendProgramStatsToFrontend( cps, &programStats );
8840
8841                 return;
8842
8843             } else if (strncmp(message,"++",2) == 0) {
8844                 /* Crafty 9.29+ outputs this */
8845                 programStats.got_fail = 2;
8846                 return;
8847
8848             } else if (strncmp(message,"--",2) == 0) {
8849                 /* Crafty 9.29+ outputs this */
8850                 programStats.got_fail = 1;
8851                 return;
8852
8853             } else if (thinkOutput[0] != NULLCHAR &&
8854                        strncmp(message, "    ", 4) == 0) {
8855                 unsigned message_len;
8856
8857                 p = message;
8858                 while (*p && *p == ' ') p++;
8859
8860                 message_len = strlen( p );
8861
8862                 /* [AS] Avoid buffer overflow */
8863                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8864                     strcat(thinkOutput, " ");
8865                     strcat(thinkOutput, p);
8866                 }
8867
8868                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8869                     strcat(programStats.movelist, " ");
8870                     strcat(programStats.movelist, p);
8871                 }
8872
8873                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8874                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8875                     DisplayMove(currentMove - 1);
8876                 }
8877                 return;
8878             }
8879         }
8880         else {
8881             buf1[0] = NULLCHAR;
8882
8883             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8884                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8885             {
8886                 ChessProgramStats cpstats;
8887
8888                 if (plyext != ' ' && plyext != '\t') {
8889                     time *= 100;
8890                 }
8891
8892                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8893                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8894                     curscore = -curscore;
8895                 }
8896
8897                 cpstats.depth = plylev;
8898                 cpstats.nodes = nodes;
8899                 cpstats.time = time;
8900                 cpstats.score = curscore;
8901                 cpstats.got_only_move = 0;
8902                 cpstats.movelist[0] = '\0';
8903
8904                 if (buf1[0] != NULLCHAR) {
8905                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8906                 }
8907
8908                 cpstats.ok_to_send = 0;
8909                 cpstats.line_is_book = 0;
8910                 cpstats.nr_moves = 0;
8911                 cpstats.moves_left = 0;
8912
8913                 SendProgramStatsToFrontend( cps, &cpstats );
8914             }
8915         }
8916     }
8917 }
8918
8919
8920 /* Parse a game score from the character string "game", and
8921    record it as the history of the current game.  The game
8922    score is NOT assumed to start from the standard position.
8923    The display is not updated in any way.
8924    */
8925 void
8926 ParseGameHistory (char *game)
8927 {
8928     ChessMove moveType;
8929     int fromX, fromY, toX, toY, boardIndex;
8930     char promoChar;
8931     char *p, *q;
8932     char buf[MSG_SIZ];
8933
8934     if (appData.debugMode)
8935       fprintf(debugFP, "Parsing game history: %s\n", game);
8936
8937     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8938     gameInfo.site = StrSave(appData.icsHost);
8939     gameInfo.date = PGNDate();
8940     gameInfo.round = StrSave("-");
8941
8942     /* Parse out names of players */
8943     while (*game == ' ') game++;
8944     p = buf;
8945     while (*game != ' ') *p++ = *game++;
8946     *p = NULLCHAR;
8947     gameInfo.white = StrSave(buf);
8948     while (*game == ' ') game++;
8949     p = buf;
8950     while (*game != ' ' && *game != '\n') *p++ = *game++;
8951     *p = NULLCHAR;
8952     gameInfo.black = StrSave(buf);
8953
8954     /* Parse moves */
8955     boardIndex = blackPlaysFirst ? 1 : 0;
8956     yynewstr(game);
8957     for (;;) {
8958         yyboardindex = boardIndex;
8959         moveType = (ChessMove) Myylex();
8960         switch (moveType) {
8961           case IllegalMove:             /* maybe suicide chess, etc. */
8962   if (appData.debugMode) {
8963     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8965     setbuf(debugFP, NULL);
8966   }
8967           case WhitePromotion:
8968           case BlackPromotion:
8969           case WhiteNonPromotion:
8970           case BlackNonPromotion:
8971           case NormalMove:
8972           case WhiteCapturesEnPassant:
8973           case BlackCapturesEnPassant:
8974           case WhiteKingSideCastle:
8975           case WhiteQueenSideCastle:
8976           case BlackKingSideCastle:
8977           case BlackQueenSideCastle:
8978           case WhiteKingSideCastleWild:
8979           case WhiteQueenSideCastleWild:
8980           case BlackKingSideCastleWild:
8981           case BlackQueenSideCastleWild:
8982           /* PUSH Fabien */
8983           case WhiteHSideCastleFR:
8984           case WhiteASideCastleFR:
8985           case BlackHSideCastleFR:
8986           case BlackASideCastleFR:
8987           /* POP Fabien */
8988             fromX = currentMoveString[0] - AAA;
8989             fromY = currentMoveString[1] - ONE;
8990             toX = currentMoveString[2] - AAA;
8991             toY = currentMoveString[3] - ONE;
8992             promoChar = currentMoveString[4];
8993             break;
8994           case WhiteDrop:
8995           case BlackDrop:
8996             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8997             fromX = moveType == WhiteDrop ?
8998               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8999             (int) CharToPiece(ToLower(currentMoveString[0]));
9000             fromY = DROP_RANK;
9001             toX = currentMoveString[2] - AAA;
9002             toY = currentMoveString[3] - ONE;
9003             promoChar = NULLCHAR;
9004             break;
9005           case AmbiguousMove:
9006             /* bug? */
9007             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9008   if (appData.debugMode) {
9009     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9010     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9011     setbuf(debugFP, NULL);
9012   }
9013             DisplayError(buf, 0);
9014             return;
9015           case ImpossibleMove:
9016             /* bug? */
9017             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9018   if (appData.debugMode) {
9019     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9020     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9021     setbuf(debugFP, NULL);
9022   }
9023             DisplayError(buf, 0);
9024             return;
9025           case EndOfFile:
9026             if (boardIndex < backwardMostMove) {
9027                 /* Oops, gap.  How did that happen? */
9028                 DisplayError(_("Gap in move list"), 0);
9029                 return;
9030             }
9031             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9032             if (boardIndex > forwardMostMove) {
9033                 forwardMostMove = boardIndex;
9034             }
9035             return;
9036           case ElapsedTime:
9037             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9038                 strcat(parseList[boardIndex-1], " ");
9039                 strcat(parseList[boardIndex-1], yy_text);
9040             }
9041             continue;
9042           case Comment:
9043           case PGNTag:
9044           case NAG:
9045           default:
9046             /* ignore */
9047             continue;
9048           case WhiteWins:
9049           case BlackWins:
9050           case GameIsDrawn:
9051           case GameUnfinished:
9052             if (gameMode == IcsExamining) {
9053                 if (boardIndex < backwardMostMove) {
9054                     /* Oops, gap.  How did that happen? */
9055                     return;
9056                 }
9057                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9058                 return;
9059             }
9060             gameInfo.result = moveType;
9061             p = strchr(yy_text, '{');
9062             if (p == NULL) p = strchr(yy_text, '(');
9063             if (p == NULL) {
9064                 p = yy_text;
9065                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9066             } else {
9067                 q = strchr(p, *p == '{' ? '}' : ')');
9068                 if (q != NULL) *q = NULLCHAR;
9069                 p++;
9070             }
9071             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9072             gameInfo.resultDetails = StrSave(p);
9073             continue;
9074         }
9075         if (boardIndex >= forwardMostMove &&
9076             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9077             backwardMostMove = blackPlaysFirst ? 1 : 0;
9078             return;
9079         }
9080         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9081                                  fromY, fromX, toY, toX, promoChar,
9082                                  parseList[boardIndex]);
9083         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9084         /* currentMoveString is set as a side-effect of yylex */
9085         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9086         strcat(moveList[boardIndex], "\n");
9087         boardIndex++;
9088         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9089         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9090           case MT_NONE:
9091           case MT_STALEMATE:
9092           default:
9093             break;
9094           case MT_CHECK:
9095             if(gameInfo.variant != VariantShogi)
9096                 strcat(parseList[boardIndex - 1], "+");
9097             break;
9098           case MT_CHECKMATE:
9099           case MT_STAINMATE:
9100             strcat(parseList[boardIndex - 1], "#");
9101             break;
9102         }
9103     }
9104 }
9105
9106
9107 /* Apply a move to the given board  */
9108 void
9109 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9110 {
9111   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9112   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9113
9114     /* [HGM] compute & store e.p. status and castling rights for new position */
9115     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9116
9117       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9118       oldEP = (signed char)board[EP_STATUS];
9119       board[EP_STATUS] = EP_NONE;
9120
9121   if (fromY == DROP_RANK) {
9122         /* must be first */
9123         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9124             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9125             return;
9126         }
9127         piece = board[toY][toX] = (ChessSquare) fromX;
9128   } else {
9129       int i;
9130
9131       if( board[toY][toX] != EmptySquare )
9132            board[EP_STATUS] = EP_CAPTURE;
9133
9134       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9135            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9136                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9137       } else
9138       if( board[fromY][fromX] == WhitePawn ) {
9139            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9140                board[EP_STATUS] = EP_PAWN_MOVE;
9141            if( toY-fromY==2) {
9142                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9143                         gameInfo.variant != VariantBerolina || toX < fromX)
9144                       board[EP_STATUS] = toX | berolina;
9145                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9146                         gameInfo.variant != VariantBerolina || toX > fromX)
9147                       board[EP_STATUS] = toX;
9148            }
9149       } else
9150       if( board[fromY][fromX] == BlackPawn ) {
9151            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9152                board[EP_STATUS] = EP_PAWN_MOVE;
9153            if( toY-fromY== -2) {
9154                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9155                         gameInfo.variant != VariantBerolina || toX < fromX)
9156                       board[EP_STATUS] = toX | berolina;
9157                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9158                         gameInfo.variant != VariantBerolina || toX > fromX)
9159                       board[EP_STATUS] = toX;
9160            }
9161        }
9162
9163        for(i=0; i<nrCastlingRights; i++) {
9164            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9165               board[CASTLING][i] == toX   && castlingRank[i] == toY
9166              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9167        }
9168
9169      if (fromX == toX && fromY == toY) return;
9170
9171      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9172      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9173      if(gameInfo.variant == VariantKnightmate)
9174          king += (int) WhiteUnicorn - (int) WhiteKing;
9175
9176     /* Code added by Tord: */
9177     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9178     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9179         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9180       board[fromY][fromX] = EmptySquare;
9181       board[toY][toX] = EmptySquare;
9182       if((toX > fromX) != (piece == WhiteRook)) {
9183         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9184       } else {
9185         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9186       }
9187     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9188                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9189       board[fromY][fromX] = EmptySquare;
9190       board[toY][toX] = EmptySquare;
9191       if((toX > fromX) != (piece == BlackRook)) {
9192         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9193       } else {
9194         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9195       }
9196     /* End of code added by Tord */
9197
9198     } else if (board[fromY][fromX] == king
9199         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9200         && toY == fromY && toX > fromX+1) {
9201         board[fromY][fromX] = EmptySquare;
9202         board[toY][toX] = king;
9203         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9204         board[fromY][BOARD_RGHT-1] = EmptySquare;
9205     } else if (board[fromY][fromX] == king
9206         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9207                && toY == fromY && toX < fromX-1) {
9208         board[fromY][fromX] = EmptySquare;
9209         board[toY][toX] = king;
9210         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9211         board[fromY][BOARD_LEFT] = EmptySquare;
9212     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9213                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9214                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9215                ) {
9216         /* white pawn promotion */
9217         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9218         if(gameInfo.variant==VariantBughouse ||
9219            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9220             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9221         board[fromY][fromX] = EmptySquare;
9222     } else if ((fromY >= BOARD_HEIGHT>>1)
9223                && (toX != fromX)
9224                && gameInfo.variant != VariantXiangqi
9225                && gameInfo.variant != VariantBerolina
9226                && (board[fromY][fromX] == WhitePawn)
9227                && (board[toY][toX] == EmptySquare)) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = WhitePawn;
9230         captured = board[toY - 1][toX];
9231         board[toY - 1][toX] = EmptySquare;
9232     } else if ((fromY == BOARD_HEIGHT-4)
9233                && (toX == fromX)
9234                && gameInfo.variant == VariantBerolina
9235                && (board[fromY][fromX] == WhitePawn)
9236                && (board[toY][toX] == EmptySquare)) {
9237         board[fromY][fromX] = EmptySquare;
9238         board[toY][toX] = WhitePawn;
9239         if(oldEP & EP_BEROLIN_A) {
9240                 captured = board[fromY][fromX-1];
9241                 board[fromY][fromX-1] = EmptySquare;
9242         }else{  captured = board[fromY][fromX+1];
9243                 board[fromY][fromX+1] = EmptySquare;
9244         }
9245     } else if (board[fromY][fromX] == king
9246         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9247                && toY == fromY && toX > fromX+1) {
9248         board[fromY][fromX] = EmptySquare;
9249         board[toY][toX] = king;
9250         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9251         board[fromY][BOARD_RGHT-1] = EmptySquare;
9252     } else if (board[fromY][fromX] == king
9253         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9254                && toY == fromY && toX < fromX-1) {
9255         board[fromY][fromX] = EmptySquare;
9256         board[toY][toX] = king;
9257         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9258         board[fromY][BOARD_LEFT] = EmptySquare;
9259     } else if (fromY == 7 && fromX == 3
9260                && board[fromY][fromX] == BlackKing
9261                && toY == 7 && toX == 5) {
9262         board[fromY][fromX] = EmptySquare;
9263         board[toY][toX] = BlackKing;
9264         board[fromY][7] = EmptySquare;
9265         board[toY][4] = BlackRook;
9266     } else if (fromY == 7 && fromX == 3
9267                && board[fromY][fromX] == BlackKing
9268                && toY == 7 && toX == 1) {
9269         board[fromY][fromX] = EmptySquare;
9270         board[toY][toX] = BlackKing;
9271         board[fromY][0] = EmptySquare;
9272         board[toY][2] = BlackRook;
9273     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9274                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9275                && toY < promoRank && promoChar
9276                ) {
9277         /* black pawn promotion */
9278         board[toY][toX] = CharToPiece(ToLower(promoChar));
9279         if(gameInfo.variant==VariantBughouse ||
9280            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9281             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9282         board[fromY][fromX] = EmptySquare;
9283     } else if ((fromY < BOARD_HEIGHT>>1)
9284                && (toX != fromX)
9285                && gameInfo.variant != VariantXiangqi
9286                && gameInfo.variant != VariantBerolina
9287                && (board[fromY][fromX] == BlackPawn)
9288                && (board[toY][toX] == EmptySquare)) {
9289         board[fromY][fromX] = EmptySquare;
9290         board[toY][toX] = BlackPawn;
9291         captured = board[toY + 1][toX];
9292         board[toY + 1][toX] = EmptySquare;
9293     } else if ((fromY == 3)
9294                && (toX == fromX)
9295                && gameInfo.variant == VariantBerolina
9296                && (board[fromY][fromX] == BlackPawn)
9297                && (board[toY][toX] == EmptySquare)) {
9298         board[fromY][fromX] = EmptySquare;
9299         board[toY][toX] = BlackPawn;
9300         if(oldEP & EP_BEROLIN_A) {
9301                 captured = board[fromY][fromX-1];
9302                 board[fromY][fromX-1] = EmptySquare;
9303         }else{  captured = board[fromY][fromX+1];
9304                 board[fromY][fromX+1] = EmptySquare;
9305         }
9306     } else {
9307         board[toY][toX] = board[fromY][fromX];
9308         board[fromY][fromX] = EmptySquare;
9309     }
9310   }
9311
9312     if (gameInfo.holdingsWidth != 0) {
9313
9314       /* !!A lot more code needs to be written to support holdings  */
9315       /* [HGM] OK, so I have written it. Holdings are stored in the */
9316       /* penultimate board files, so they are automaticlly stored   */
9317       /* in the game history.                                       */
9318       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9319                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9320         /* Delete from holdings, by decreasing count */
9321         /* and erasing image if necessary            */
9322         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9323         if(p < (int) BlackPawn) { /* white drop */
9324              p -= (int)WhitePawn;
9325                  p = PieceToNumber((ChessSquare)p);
9326              if(p >= gameInfo.holdingsSize) p = 0;
9327              if(--board[p][BOARD_WIDTH-2] <= 0)
9328                   board[p][BOARD_WIDTH-1] = EmptySquare;
9329              if((int)board[p][BOARD_WIDTH-2] < 0)
9330                         board[p][BOARD_WIDTH-2] = 0;
9331         } else {                  /* black drop */
9332              p -= (int)BlackPawn;
9333                  p = PieceToNumber((ChessSquare)p);
9334              if(p >= gameInfo.holdingsSize) p = 0;
9335              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9336                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9337              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9338                         board[BOARD_HEIGHT-1-p][1] = 0;
9339         }
9340       }
9341       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9342           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9343         /* [HGM] holdings: Add to holdings, if holdings exist */
9344         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9345                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9346                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9347         }
9348         p = (int) captured;
9349         if (p >= (int) BlackPawn) {
9350           p -= (int)BlackPawn;
9351           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9352                   /* in Shogi restore piece to its original  first */
9353                   captured = (ChessSquare) (DEMOTED captured);
9354                   p = DEMOTED p;
9355           }
9356           p = PieceToNumber((ChessSquare)p);
9357           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9358           board[p][BOARD_WIDTH-2]++;
9359           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9360         } else {
9361           p -= (int)WhitePawn;
9362           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9363                   captured = (ChessSquare) (DEMOTED captured);
9364                   p = DEMOTED p;
9365           }
9366           p = PieceToNumber((ChessSquare)p);
9367           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9368           board[BOARD_HEIGHT-1-p][1]++;
9369           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9370         }
9371       }
9372     } else if (gameInfo.variant == VariantAtomic) {
9373       if (captured != EmptySquare) {
9374         int y, x;
9375         for (y = toY-1; y <= toY+1; y++) {
9376           for (x = toX-1; x <= toX+1; x++) {
9377             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9378                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9379               board[y][x] = EmptySquare;
9380             }
9381           }
9382         }
9383         board[toY][toX] = EmptySquare;
9384       }
9385     }
9386     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9387         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9388     } else
9389     if(promoChar == '+') {
9390         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9391         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9392     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9393         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9394     }
9395     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9396                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9397         // [HGM] superchess: take promotion piece out of holdings
9398         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9399         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9400             if(!--board[k][BOARD_WIDTH-2])
9401                 board[k][BOARD_WIDTH-1] = EmptySquare;
9402         } else {
9403             if(!--board[BOARD_HEIGHT-1-k][1])
9404                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9405         }
9406     }
9407
9408 }
9409
9410 /* Updates forwardMostMove */
9411 void
9412 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9413 {
9414 //    forwardMostMove++; // [HGM] bare: moved downstream
9415
9416     (void) CoordsToAlgebraic(boards[forwardMostMove],
9417                              PosFlags(forwardMostMove),
9418                              fromY, fromX, toY, toX, promoChar,
9419                              parseList[forwardMostMove]);
9420
9421     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9422         int timeLeft; static int lastLoadFlag=0; int king, piece;
9423         piece = boards[forwardMostMove][fromY][fromX];
9424         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9425         if(gameInfo.variant == VariantKnightmate)
9426             king += (int) WhiteUnicorn - (int) WhiteKing;
9427         if(forwardMostMove == 0) {
9428             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9429                 fprintf(serverMoves, "%s;", UserName());
9430             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9431                 fprintf(serverMoves, "%s;", second.tidy);
9432             fprintf(serverMoves, "%s;", first.tidy);
9433             if(gameMode == MachinePlaysWhite)
9434                 fprintf(serverMoves, "%s;", UserName());
9435             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9436                 fprintf(serverMoves, "%s;", second.tidy);
9437         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9438         lastLoadFlag = loadFlag;
9439         // print base move
9440         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9441         // print castling suffix
9442         if( toY == fromY && piece == king ) {
9443             if(toX-fromX > 1)
9444                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9445             if(fromX-toX >1)
9446                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9447         }
9448         // e.p. suffix
9449         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9450              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9451              boards[forwardMostMove][toY][toX] == EmptySquare
9452              && fromX != toX && fromY != toY)
9453                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9454         // promotion suffix
9455         if(promoChar != NULLCHAR)
9456                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9457         if(!loadFlag) {
9458                 char buf[MOVE_LEN*2], *p; int len;
9459             fprintf(serverMoves, "/%d/%d",
9460                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9461             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9462             else                      timeLeft = blackTimeRemaining/1000;
9463             fprintf(serverMoves, "/%d", timeLeft);
9464                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9465                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9466                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9467             fprintf(serverMoves, "/%s", buf);
9468         }
9469         fflush(serverMoves);
9470     }
9471
9472     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9473         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9474       return;
9475     }
9476     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9477     if (commentList[forwardMostMove+1] != NULL) {
9478         free(commentList[forwardMostMove+1]);
9479         commentList[forwardMostMove+1] = NULL;
9480     }
9481     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9482     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9483     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9484     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9485     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9486     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9487     adjustedClock = FALSE;
9488     gameInfo.result = GameUnfinished;
9489     if (gameInfo.resultDetails != NULL) {
9490         free(gameInfo.resultDetails);
9491         gameInfo.resultDetails = NULL;
9492     }
9493     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9494                               moveList[forwardMostMove - 1]);
9495     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9496       case MT_NONE:
9497       case MT_STALEMATE:
9498       default:
9499         break;
9500       case MT_CHECK:
9501         if(gameInfo.variant != VariantShogi)
9502             strcat(parseList[forwardMostMove - 1], "+");
9503         break;
9504       case MT_CHECKMATE:
9505       case MT_STAINMATE:
9506         strcat(parseList[forwardMostMove - 1], "#");
9507         break;
9508     }
9509
9510 }
9511
9512 /* Updates currentMove if not pausing */
9513 void
9514 ShowMove (int fromX, int fromY, int toX, int toY)
9515 {
9516     int instant = (gameMode == PlayFromGameFile) ?
9517         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9518     if(appData.noGUI) return;
9519     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9520         if (!instant) {
9521             if (forwardMostMove == currentMove + 1) {
9522                 AnimateMove(boards[forwardMostMove - 1],
9523                             fromX, fromY, toX, toY);
9524             }
9525             if (appData.highlightLastMove) {
9526                 SetHighlights(fromX, fromY, toX, toY);
9527             }
9528         }
9529         currentMove = forwardMostMove;
9530     }
9531
9532     if (instant) return;
9533
9534     DisplayMove(currentMove - 1);
9535     DrawPosition(FALSE, boards[currentMove]);
9536     DisplayBothClocks();
9537     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9538 }
9539
9540 void
9541 SendEgtPath (ChessProgramState *cps)
9542 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9543         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9544
9545         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9546
9547         while(*p) {
9548             char c, *q = name+1, *r, *s;
9549
9550             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9551             while(*p && *p != ',') *q++ = *p++;
9552             *q++ = ':'; *q = 0;
9553             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9554                 strcmp(name, ",nalimov:") == 0 ) {
9555                 // take nalimov path from the menu-changeable option first, if it is defined
9556               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9557                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9558             } else
9559             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9560                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9561                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9562                 s = r = StrStr(s, ":") + 1; // beginning of path info
9563                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9564                 c = *r; *r = 0;             // temporarily null-terminate path info
9565                     *--q = 0;               // strip of trailig ':' from name
9566                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9567                 *r = c;
9568                 SendToProgram(buf,cps);     // send egtbpath command for this format
9569             }
9570             if(*p == ',') p++; // read away comma to position for next format name
9571         }
9572 }
9573
9574 void
9575 InitChessProgram (ChessProgramState *cps, int setup)
9576 /* setup needed to setup FRC opening position */
9577 {
9578     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9579     if (appData.noChessProgram) return;
9580     hintRequested = FALSE;
9581     bookRequested = FALSE;
9582
9583     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9584     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9585     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9586     if(cps->memSize) { /* [HGM] memory */
9587       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9588         SendToProgram(buf, cps);
9589     }
9590     SendEgtPath(cps); /* [HGM] EGT */
9591     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9592       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9593         SendToProgram(buf, cps);
9594     }
9595
9596     SendToProgram(cps->initString, cps);
9597     if (gameInfo.variant != VariantNormal &&
9598         gameInfo.variant != VariantLoadable
9599         /* [HGM] also send variant if board size non-standard */
9600         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9601                                             ) {
9602       char *v = VariantName(gameInfo.variant);
9603       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9604         /* [HGM] in protocol 1 we have to assume all variants valid */
9605         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9606         DisplayFatalError(buf, 0, 1);
9607         return;
9608       }
9609
9610       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9611       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9612       if( gameInfo.variant == VariantXiangqi )
9613            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9614       if( gameInfo.variant == VariantShogi )
9615            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9616       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9617            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9618       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9619           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9620            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9621       if( gameInfo.variant == VariantCourier )
9622            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9623       if( gameInfo.variant == VariantSuper )
9624            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9625       if( gameInfo.variant == VariantGreat )
9626            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9627       if( gameInfo.variant == VariantSChess )
9628            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9629       if( gameInfo.variant == VariantGrand )
9630            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9631
9632       if(overruled) {
9633         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9634                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9635            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9636            if(StrStr(cps->variants, b) == NULL) {
9637                // specific sized variant not known, check if general sizing allowed
9638                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9639                    if(StrStr(cps->variants, "boardsize") == NULL) {
9640                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9641                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9642                        DisplayFatalError(buf, 0, 1);
9643                        return;
9644                    }
9645                    /* [HGM] here we really should compare with the maximum supported board size */
9646                }
9647            }
9648       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9649       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9650       SendToProgram(buf, cps);
9651     }
9652     currentlyInitializedVariant = gameInfo.variant;
9653
9654     /* [HGM] send opening position in FRC to first engine */
9655     if(setup) {
9656           SendToProgram("force\n", cps);
9657           SendBoard(cps, 0);
9658           /* engine is now in force mode! Set flag to wake it up after first move. */
9659           setboardSpoiledMachineBlack = 1;
9660     }
9661
9662     if (cps->sendICS) {
9663       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9664       SendToProgram(buf, cps);
9665     }
9666     cps->maybeThinking = FALSE;
9667     cps->offeredDraw = 0;
9668     if (!appData.icsActive) {
9669         SendTimeControl(cps, movesPerSession, timeControl,
9670                         timeIncrement, appData.searchDepth,
9671                         searchTime);
9672     }
9673     if (appData.showThinking
9674         // [HGM] thinking: four options require thinking output to be sent
9675         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9676                                 ) {
9677         SendToProgram("post\n", cps);
9678     }
9679     SendToProgram("hard\n", cps);
9680     if (!appData.ponderNextMove) {
9681         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9682            it without being sure what state we are in first.  "hard"
9683            is not a toggle, so that one is OK.
9684          */
9685         SendToProgram("easy\n", cps);
9686     }
9687     if (cps->usePing) {
9688       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9689       SendToProgram(buf, cps);
9690     }
9691     cps->initDone = TRUE;
9692     ClearEngineOutputPane(cps == &second);
9693 }
9694
9695
9696 void
9697 StartChessProgram (ChessProgramState *cps)
9698 {
9699     char buf[MSG_SIZ];
9700     int err;
9701
9702     if (appData.noChessProgram) return;
9703     cps->initDone = FALSE;
9704
9705     if (strcmp(cps->host, "localhost") == 0) {
9706         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9707     } else if (*appData.remoteShell == NULLCHAR) {
9708         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9709     } else {
9710         if (*appData.remoteUser == NULLCHAR) {
9711           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9712                     cps->program);
9713         } else {
9714           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9715                     cps->host, appData.remoteUser, cps->program);
9716         }
9717         err = StartChildProcess(buf, "", &cps->pr);
9718     }
9719
9720     if (err != 0) {
9721       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9722         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9723         if(cps != &first) return;
9724         appData.noChessProgram = TRUE;
9725         ThawUI();
9726         SetNCPMode();
9727 //      DisplayFatalError(buf, err, 1);
9728 //      cps->pr = NoProc;
9729 //      cps->isr = NULL;
9730         return;
9731     }
9732
9733     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9734     if (cps->protocolVersion > 1) {
9735       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9736       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9737       cps->comboCnt = 0;  //                and values of combo boxes
9738       SendToProgram(buf, cps);
9739     } else {
9740       SendToProgram("xboard\n", cps);
9741     }
9742 }
9743
9744 void
9745 TwoMachinesEventIfReady P((void))
9746 {
9747   static int curMess = 0;
9748   if (first.lastPing != first.lastPong) {
9749     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9750     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9751     return;
9752   }
9753   if (second.lastPing != second.lastPong) {
9754     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9755     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9756     return;
9757   }
9758   DisplayMessage("", ""); curMess = 0;
9759   ThawUI();
9760   TwoMachinesEvent();
9761 }
9762
9763 char *
9764 MakeName (char *template)
9765 {
9766     time_t clock;
9767     struct tm *tm;
9768     static char buf[MSG_SIZ];
9769     char *p = buf;
9770     int i;
9771
9772     clock = time((time_t *)NULL);
9773     tm = localtime(&clock);
9774
9775     while(*p++ = *template++) if(p[-1] == '%') {
9776         switch(*template++) {
9777           case 0:   *p = 0; return buf;
9778           case 'Y': i = tm->tm_year+1900; break;
9779           case 'y': i = tm->tm_year-100; break;
9780           case 'M': i = tm->tm_mon+1; break;
9781           case 'd': i = tm->tm_mday; break;
9782           case 'h': i = tm->tm_hour; break;
9783           case 'm': i = tm->tm_min; break;
9784           case 's': i = tm->tm_sec; break;
9785           default:  i = 0;
9786         }
9787         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9788     }
9789     return buf;
9790 }
9791
9792 int
9793 CountPlayers (char *p)
9794 {
9795     int n = 0;
9796     while(p = strchr(p, '\n')) p++, n++; // count participants
9797     return n;
9798 }
9799
9800 FILE *
9801 WriteTourneyFile (char *results, FILE *f)
9802 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9803     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9804     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9805         // create a file with tournament description
9806         fprintf(f, "-participants {%s}\n", appData.participants);
9807         fprintf(f, "-seedBase %d\n", appData.seedBase);
9808         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9809         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9810         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9811         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9812         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9813         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9814         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9815         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9816         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9817         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9818         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9819         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9820         if(searchTime > 0)
9821                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9822         else {
9823                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9824                 fprintf(f, "-tc %s\n", appData.timeControl);
9825                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9826         }
9827         fprintf(f, "-results \"%s\"\n", results);
9828     }
9829     return f;
9830 }
9831
9832 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9833
9834 void
9835 Substitute (char *participants, int expunge)
9836 {
9837     int i, changed, changes=0, nPlayers=0;
9838     char *p, *q, *r, buf[MSG_SIZ];
9839     if(participants == NULL) return;
9840     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9841     r = p = participants; q = appData.participants;
9842     while(*p && *p == *q) {
9843         if(*p == '\n') r = p+1, nPlayers++;
9844         p++; q++;
9845     }
9846     if(*p) { // difference
9847         while(*p && *p++ != '\n');
9848         while(*q && *q++ != '\n');
9849       changed = nPlayers;
9850         changes = 1 + (strcmp(p, q) != 0);
9851     }
9852     if(changes == 1) { // a single engine mnemonic was changed
9853         q = r; while(*q) nPlayers += (*q++ == '\n');
9854         p = buf; while(*r && (*p = *r++) != '\n') p++;
9855         *p = NULLCHAR;
9856         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9857         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9858         if(mnemonic[i]) { // The substitute is valid
9859             FILE *f;
9860             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9861                 flock(fileno(f), LOCK_EX);
9862                 ParseArgsFromFile(f);
9863                 fseek(f, 0, SEEK_SET);
9864                 FREE(appData.participants); appData.participants = participants;
9865                 if(expunge) { // erase results of replaced engine
9866                     int len = strlen(appData.results), w, b, dummy;
9867                     for(i=0; i<len; i++) {
9868                         Pairing(i, nPlayers, &w, &b, &dummy);
9869                         if((w == changed || b == changed) && appData.results[i] == '*') {
9870                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9871                             fclose(f);
9872                             return;
9873                         }
9874                     }
9875                     for(i=0; i<len; i++) {
9876                         Pairing(i, nPlayers, &w, &b, &dummy);
9877                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9878                     }
9879                 }
9880                 WriteTourneyFile(appData.results, f);
9881                 fclose(f); // release lock
9882                 return;
9883             }
9884         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9885     }
9886     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9887     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9888     free(participants);
9889     return;
9890 }
9891
9892 int
9893 CreateTourney (char *name)
9894 {
9895         FILE *f;
9896         if(matchMode && strcmp(name, appData.tourneyFile)) {
9897              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9898         }
9899         if(name[0] == NULLCHAR) {
9900             if(appData.participants[0])
9901                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9902             return 0;
9903         }
9904         f = fopen(name, "r");
9905         if(f) { // file exists
9906             ASSIGN(appData.tourneyFile, name);
9907             ParseArgsFromFile(f); // parse it
9908         } else {
9909             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9910             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9911                 DisplayError(_("Not enough participants"), 0);
9912                 return 0;
9913             }
9914             ASSIGN(appData.tourneyFile, name);
9915             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9916             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9917         }
9918         fclose(f);
9919         appData.noChessProgram = FALSE;
9920         appData.clockMode = TRUE;
9921         SetGNUMode();
9922         return 1;
9923 }
9924
9925 int
9926 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9927 {
9928     char buf[MSG_SIZ], *p, *q;
9929     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9930     skip = !all && group[0]; // if group requested, we start in skip mode
9931     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9932         p = names; q = buf; header = 0;
9933         while(*p && *p != '\n') *q++ = *p++;
9934         *q = 0;
9935         if(*p == '\n') p++;
9936         if(buf[0] == '#') {
9937             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9938             depth++; // we must be entering a new group
9939             if(all) continue; // suppress printing group headers when complete list requested
9940             header = 1;
9941             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9942         }
9943         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9944         if(engineList[i]) free(engineList[i]);
9945         engineList[i] = strdup(buf);
9946         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9947         if(engineMnemonic[i]) free(engineMnemonic[i]);
9948         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9949             strcat(buf, " (");
9950             sscanf(q + 8, "%s", buf + strlen(buf));
9951             strcat(buf, ")");
9952         }
9953         engineMnemonic[i] = strdup(buf);
9954         i++;
9955     }
9956     engineList[i] = engineMnemonic[i] = NULL;
9957     return i;
9958 }
9959
9960 // following implemented as macro to avoid type limitations
9961 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9962
9963 void
9964 SwapEngines (int n)
9965 {   // swap settings for first engine and other engine (so far only some selected options)
9966     int h;
9967     char *p;
9968     if(n == 0) return;
9969     SWAP(directory, p)
9970     SWAP(chessProgram, p)
9971     SWAP(isUCI, h)
9972     SWAP(hasOwnBookUCI, h)
9973     SWAP(protocolVersion, h)
9974     SWAP(reuse, h)
9975     SWAP(scoreIsAbsolute, h)
9976     SWAP(timeOdds, h)
9977     SWAP(logo, p)
9978     SWAP(pgnName, p)
9979     SWAP(pvSAN, h)
9980     SWAP(engOptions, p)
9981 }
9982
9983 int
9984 SetPlayer (int player, char *p)
9985 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9986     int i;
9987     char buf[MSG_SIZ], *engineName;
9988     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9989     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9990     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9991     if(mnemonic[i]) {
9992         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9993         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9994         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9995         ParseArgsFromString(buf);
9996     }
9997     free(engineName);
9998     return i;
9999 }
10000
10001 char *recentEngines;
10002
10003 void
10004 RecentEngineEvent (int nr)
10005 {
10006     int n;
10007 //    SwapEngines(1); // bump first to second
10008 //    ReplaceEngine(&second, 1); // and load it there
10009     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10010     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10011     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10012         ReplaceEngine(&first, 0);
10013         FloatToFront(&appData.recentEngineList, command[n]);
10014     }
10015 }
10016
10017 int
10018 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10019 {   // determine players from game number
10020     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10021
10022     if(appData.tourneyType == 0) {
10023         roundsPerCycle = (nPlayers - 1) | 1;
10024         pairingsPerRound = nPlayers / 2;
10025     } else if(appData.tourneyType > 0) {
10026         roundsPerCycle = nPlayers - appData.tourneyType;
10027         pairingsPerRound = appData.tourneyType;
10028     }
10029     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10030     gamesPerCycle = gamesPerRound * roundsPerCycle;
10031     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10032     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10033     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10034     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10035     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10036     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10037
10038     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10039     if(appData.roundSync) *syncInterval = gamesPerRound;
10040
10041     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10042
10043     if(appData.tourneyType == 0) {
10044         if(curPairing == (nPlayers-1)/2 ) {
10045             *whitePlayer = curRound;
10046             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10047         } else {
10048             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10049             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10050             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10051             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10052         }
10053     } else if(appData.tourneyType > 1) {
10054         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10055         *whitePlayer = curRound + appData.tourneyType;
10056     } else if(appData.tourneyType > 0) {
10057         *whitePlayer = curPairing;
10058         *blackPlayer = curRound + appData.tourneyType;
10059     }
10060
10061     // take care of white/black alternation per round. 
10062     // For cycles and games this is already taken care of by default, derived from matchGame!
10063     return curRound & 1;
10064 }
10065
10066 int
10067 NextTourneyGame (int nr, int *swapColors)
10068 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10069     char *p, *q;
10070     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10071     FILE *tf;
10072     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10073     tf = fopen(appData.tourneyFile, "r");
10074     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10075     ParseArgsFromFile(tf); fclose(tf);
10076     InitTimeControls(); // TC might be altered from tourney file
10077
10078     nPlayers = CountPlayers(appData.participants); // count participants
10079     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10080     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10081
10082     if(syncInterval) {
10083         p = q = appData.results;
10084         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10085         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10086             DisplayMessage(_("Waiting for other game(s)"),"");
10087             waitingForGame = TRUE;
10088             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10089             return 0;
10090         }
10091         waitingForGame = FALSE;
10092     }
10093
10094     if(appData.tourneyType < 0) {
10095         if(nr>=0 && !pairingReceived) {
10096             char buf[1<<16];
10097             if(pairing.pr == NoProc) {
10098                 if(!appData.pairingEngine[0]) {
10099                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10100                     return 0;
10101                 }
10102                 StartChessProgram(&pairing); // starts the pairing engine
10103             }
10104             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10105             SendToProgram(buf, &pairing);
10106             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10107             SendToProgram(buf, &pairing);
10108             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10109         }
10110         pairingReceived = 0;                              // ... so we continue here 
10111         *swapColors = 0;
10112         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10113         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10114         matchGame = 1; roundNr = nr / syncInterval + 1;
10115     }
10116
10117     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10118
10119     // redefine engines, engine dir, etc.
10120     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10121     if(first.pr == NoProc) {
10122       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10123       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10124     }
10125     if(second.pr == NoProc) {
10126       SwapEngines(1);
10127       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10128       SwapEngines(1);         // and make that valid for second engine by swapping
10129       InitEngine(&second, 1);
10130     }
10131     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10132     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10133     return 1;
10134 }
10135
10136 void
10137 NextMatchGame ()
10138 {   // performs game initialization that does not invoke engines, and then tries to start the game
10139     int res, firstWhite, swapColors = 0;
10140     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10141     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
10142         char buf[MSG_SIZ];
10143         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10144         if(strcmp(buf, currentDebugFile)) { // name has changed
10145             FILE *f = fopen(buf, "w");
10146             if(f) { // if opening the new file failed, just keep using the old one
10147                 ASSIGN(currentDebugFile, buf);
10148                 fclose(debugFP);
10149                 debugFP = f;
10150             }
10151             if(appData.serverFileName) {
10152                 if(serverFP) fclose(serverFP);
10153                 serverFP = fopen(appData.serverFileName, "w");
10154                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10155                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10156             }
10157         }
10158     }
10159     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10160     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10161     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10162     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10163     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10164     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10165     Reset(FALSE, first.pr != NoProc);
10166     res = LoadGameOrPosition(matchGame); // setup game
10167     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10168     if(!res) return; // abort when bad game/pos file
10169     TwoMachinesEvent();
10170 }
10171
10172 void
10173 UserAdjudicationEvent (int result)
10174 {
10175     ChessMove gameResult = GameIsDrawn;
10176
10177     if( result > 0 ) {
10178         gameResult = WhiteWins;
10179     }
10180     else if( result < 0 ) {
10181         gameResult = BlackWins;
10182     }
10183
10184     if( gameMode == TwoMachinesPlay ) {
10185         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10186     }
10187 }
10188
10189
10190 // [HGM] save: calculate checksum of game to make games easily identifiable
10191 int
10192 StringCheckSum (char *s)
10193 {
10194         int i = 0;
10195         if(s==NULL) return 0;
10196         while(*s) i = i*259 + *s++;
10197         return i;
10198 }
10199
10200 int
10201 GameCheckSum ()
10202 {
10203         int i, sum=0;
10204         for(i=backwardMostMove; i<forwardMostMove; i++) {
10205                 sum += pvInfoList[i].depth;
10206                 sum += StringCheckSum(parseList[i]);
10207                 sum += StringCheckSum(commentList[i]);
10208                 sum *= 261;
10209         }
10210         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10211         return sum + StringCheckSum(commentList[i]);
10212 } // end of save patch
10213
10214 void
10215 GameEnds (ChessMove result, char *resultDetails, int whosays)
10216 {
10217     GameMode nextGameMode;
10218     int isIcsGame;
10219     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10220
10221     if(endingGame) return; /* [HGM] crash: forbid recursion */
10222     endingGame = 1;
10223     if(twoBoards) { // [HGM] dual: switch back to one board
10224         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10225         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10226     }
10227     if (appData.debugMode) {
10228       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10229               result, resultDetails ? resultDetails : "(null)", whosays);
10230     }
10231
10232     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10233
10234     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10235         /* If we are playing on ICS, the server decides when the
10236            game is over, but the engine can offer to draw, claim
10237            a draw, or resign.
10238          */
10239 #if ZIPPY
10240         if (appData.zippyPlay && first.initDone) {
10241             if (result == GameIsDrawn) {
10242                 /* In case draw still needs to be claimed */
10243                 SendToICS(ics_prefix);
10244                 SendToICS("draw\n");
10245             } else if (StrCaseStr(resultDetails, "resign")) {
10246                 SendToICS(ics_prefix);
10247                 SendToICS("resign\n");
10248             }
10249         }
10250 #endif
10251         endingGame = 0; /* [HGM] crash */
10252         return;
10253     }
10254
10255     /* If we're loading the game from a file, stop */
10256     if (whosays == GE_FILE) {
10257       (void) StopLoadGameTimer();
10258       gameFileFP = NULL;
10259     }
10260
10261     /* Cancel draw offers */
10262     first.offeredDraw = second.offeredDraw = 0;
10263
10264     /* If this is an ICS game, only ICS can really say it's done;
10265        if not, anyone can. */
10266     isIcsGame = (gameMode == IcsPlayingWhite ||
10267                  gameMode == IcsPlayingBlack ||
10268                  gameMode == IcsObserving    ||
10269                  gameMode == IcsExamining);
10270
10271     if (!isIcsGame || whosays == GE_ICS) {
10272         /* OK -- not an ICS game, or ICS said it was done */
10273         StopClocks();
10274         if (!isIcsGame && !appData.noChessProgram)
10275           SetUserThinkingEnables();
10276
10277         /* [HGM] if a machine claims the game end we verify this claim */
10278         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10279             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10280                 char claimer;
10281                 ChessMove trueResult = (ChessMove) -1;
10282
10283                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10284                                             first.twoMachinesColor[0] :
10285                                             second.twoMachinesColor[0] ;
10286
10287                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10288                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10289                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10290                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10291                 } else
10292                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10293                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10294                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10295                 } else
10296                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10297                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10298                 }
10299
10300                 // now verify win claims, but not in drop games, as we don't understand those yet
10301                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10302                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10303                     (result == WhiteWins && claimer == 'w' ||
10304                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10305                       if (appData.debugMode) {
10306                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10307                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10308                       }
10309                       if(result != trueResult) {
10310                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10311                               result = claimer == 'w' ? BlackWins : WhiteWins;
10312                               resultDetails = buf;
10313                       }
10314                 } else
10315                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10316                     && (forwardMostMove <= backwardMostMove ||
10317                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10318                         (claimer=='b')==(forwardMostMove&1))
10319                                                                                   ) {
10320                       /* [HGM] verify: draws that were not flagged are false claims */
10321                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10322                       result = claimer == 'w' ? BlackWins : WhiteWins;
10323                       resultDetails = buf;
10324                 }
10325                 /* (Claiming a loss is accepted no questions asked!) */
10326             }
10327             /* [HGM] bare: don't allow bare King to win */
10328             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10329                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10330                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10331                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10332                && result != GameIsDrawn)
10333             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10334                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10335                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10336                         if(p >= 0 && p <= (int)WhiteKing) k++;
10337                 }
10338                 if (appData.debugMode) {
10339                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10340                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10341                 }
10342                 if(k <= 1) {
10343                         result = GameIsDrawn;
10344                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10345                         resultDetails = buf;
10346                 }
10347             }
10348         }
10349
10350
10351         if(serverMoves != NULL && !loadFlag) { char c = '=';
10352             if(result==WhiteWins) c = '+';
10353             if(result==BlackWins) c = '-';
10354             if(resultDetails != NULL)
10355                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10356         }
10357         if (resultDetails != NULL) {
10358             gameInfo.result = result;
10359             gameInfo.resultDetails = StrSave(resultDetails);
10360
10361             /* display last move only if game was not loaded from file */
10362             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10363                 DisplayMove(currentMove - 1);
10364
10365             if (forwardMostMove != 0) {
10366                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10367                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10368                                                                 ) {
10369                     if (*appData.saveGameFile != NULLCHAR) {
10370                         SaveGameToFile(appData.saveGameFile, TRUE);
10371                     } else if (appData.autoSaveGames) {
10372                         AutoSaveGame();
10373                     }
10374                     if (*appData.savePositionFile != NULLCHAR) {
10375                         SavePositionToFile(appData.savePositionFile);
10376                     }
10377                 }
10378             }
10379
10380             /* Tell program how game ended in case it is learning */
10381             /* [HGM] Moved this to after saving the PGN, just in case */
10382             /* engine died and we got here through time loss. In that */
10383             /* case we will get a fatal error writing the pipe, which */
10384             /* would otherwise lose us the PGN.                       */
10385             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10386             /* output during GameEnds should never be fatal anymore   */
10387             if (gameMode == MachinePlaysWhite ||
10388                 gameMode == MachinePlaysBlack ||
10389                 gameMode == TwoMachinesPlay ||
10390                 gameMode == IcsPlayingWhite ||
10391                 gameMode == IcsPlayingBlack ||
10392                 gameMode == BeginningOfGame) {
10393                 char buf[MSG_SIZ];
10394                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10395                         resultDetails);
10396                 if (first.pr != NoProc) {
10397                     SendToProgram(buf, &first);
10398                 }
10399                 if (second.pr != NoProc &&
10400                     gameMode == TwoMachinesPlay) {
10401                     SendToProgram(buf, &second);
10402                 }
10403             }
10404         }
10405
10406         if (appData.icsActive) {
10407             if (appData.quietPlay &&
10408                 (gameMode == IcsPlayingWhite ||
10409                  gameMode == IcsPlayingBlack)) {
10410                 SendToICS(ics_prefix);
10411                 SendToICS("set shout 1\n");
10412             }
10413             nextGameMode = IcsIdle;
10414             ics_user_moved = FALSE;
10415             /* clean up premove.  It's ugly when the game has ended and the
10416              * premove highlights are still on the board.
10417              */
10418             if (gotPremove) {
10419               gotPremove = FALSE;
10420               ClearPremoveHighlights();
10421               DrawPosition(FALSE, boards[currentMove]);
10422             }
10423             if (whosays == GE_ICS) {
10424                 switch (result) {
10425                 case WhiteWins:
10426                     if (gameMode == IcsPlayingWhite)
10427                         PlayIcsWinSound();
10428                     else if(gameMode == IcsPlayingBlack)
10429                         PlayIcsLossSound();
10430                     break;
10431                 case BlackWins:
10432                     if (gameMode == IcsPlayingBlack)
10433                         PlayIcsWinSound();
10434                     else if(gameMode == IcsPlayingWhite)
10435                         PlayIcsLossSound();
10436                     break;
10437                 case GameIsDrawn:
10438                     PlayIcsDrawSound();
10439                     break;
10440                 default:
10441                     PlayIcsUnfinishedSound();
10442                 }
10443             }
10444         } else if (gameMode == EditGame ||
10445                    gameMode == PlayFromGameFile ||
10446                    gameMode == AnalyzeMode ||
10447                    gameMode == AnalyzeFile) {
10448             nextGameMode = gameMode;
10449         } else {
10450             nextGameMode = EndOfGame;
10451         }
10452         pausing = FALSE;
10453         ModeHighlight();
10454     } else {
10455         nextGameMode = gameMode;
10456     }
10457
10458     if (appData.noChessProgram) {
10459         gameMode = nextGameMode;
10460         ModeHighlight();
10461         endingGame = 0; /* [HGM] crash */
10462         return;
10463     }
10464
10465     if (first.reuse) {
10466         /* Put first chess program into idle state */
10467         if (first.pr != NoProc &&
10468             (gameMode == MachinePlaysWhite ||
10469              gameMode == MachinePlaysBlack ||
10470              gameMode == TwoMachinesPlay ||
10471              gameMode == IcsPlayingWhite ||
10472              gameMode == IcsPlayingBlack ||
10473              gameMode == BeginningOfGame)) {
10474             SendToProgram("force\n", &first);
10475             if (first.usePing) {
10476               char buf[MSG_SIZ];
10477               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10478               SendToProgram(buf, &first);
10479             }
10480         }
10481     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10482         /* Kill off first chess program */
10483         if (first.isr != NULL)
10484           RemoveInputSource(first.isr);
10485         first.isr = NULL;
10486
10487         if (first.pr != NoProc) {
10488             ExitAnalyzeMode();
10489             DoSleep( appData.delayBeforeQuit );
10490             SendToProgram("quit\n", &first);
10491             DoSleep( appData.delayAfterQuit );
10492             DestroyChildProcess(first.pr, first.useSigterm);
10493         }
10494         first.pr = NoProc;
10495     }
10496     if (second.reuse) {
10497         /* Put second chess program into idle state */
10498         if (second.pr != NoProc &&
10499             gameMode == TwoMachinesPlay) {
10500             SendToProgram("force\n", &second);
10501             if (second.usePing) {
10502               char buf[MSG_SIZ];
10503               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10504               SendToProgram(buf, &second);
10505             }
10506         }
10507     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10508         /* Kill off second chess program */
10509         if (second.isr != NULL)
10510           RemoveInputSource(second.isr);
10511         second.isr = NULL;
10512
10513         if (second.pr != NoProc) {
10514             DoSleep( appData.delayBeforeQuit );
10515             SendToProgram("quit\n", &second);
10516             DoSleep( appData.delayAfterQuit );
10517             DestroyChildProcess(second.pr, second.useSigterm);
10518         }
10519         second.pr = NoProc;
10520     }
10521
10522     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10523         char resChar = '=';
10524         switch (result) {
10525         case WhiteWins:
10526           resChar = '+';
10527           if (first.twoMachinesColor[0] == 'w') {
10528             first.matchWins++;
10529           } else {
10530             second.matchWins++;
10531           }
10532           break;
10533         case BlackWins:
10534           resChar = '-';
10535           if (first.twoMachinesColor[0] == 'b') {
10536             first.matchWins++;
10537           } else {
10538             second.matchWins++;
10539           }
10540           break;
10541         case GameUnfinished:
10542           resChar = ' ';
10543         default:
10544           break;
10545         }
10546
10547         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10548         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10549             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10550             ReserveGame(nextGame, resChar); // sets nextGame
10551             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10552             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10553         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10554
10555         if (nextGame <= appData.matchGames && !abortMatch) {
10556             gameMode = nextGameMode;
10557             matchGame = nextGame; // this will be overruled in tourney mode!
10558             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10559             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10560             endingGame = 0; /* [HGM] crash */
10561             return;
10562         } else {
10563             gameMode = nextGameMode;
10564             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10565                      first.tidy, second.tidy,
10566                      first.matchWins, second.matchWins,
10567                      appData.matchGames - (first.matchWins + second.matchWins));
10568             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10569             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10570             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10571             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10572                 first.twoMachinesColor = "black\n";
10573                 second.twoMachinesColor = "white\n";
10574             } else {
10575                 first.twoMachinesColor = "white\n";
10576                 second.twoMachinesColor = "black\n";
10577             }
10578         }
10579     }
10580     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10581         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10582       ExitAnalyzeMode();
10583     gameMode = nextGameMode;
10584     ModeHighlight();
10585     endingGame = 0;  /* [HGM] crash */
10586     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10587         if(matchMode == TRUE) { // match through command line: exit with or without popup
10588             if(ranking) {
10589                 ToNrEvent(forwardMostMove);
10590                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10591                 else ExitEvent(0);
10592             } else DisplayFatalError(buf, 0, 0);
10593         } else { // match through menu; just stop, with or without popup
10594             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10595             ModeHighlight();
10596             if(ranking){
10597                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10598             } else DisplayNote(buf);
10599       }
10600       if(ranking) free(ranking);
10601     }
10602 }
10603
10604 /* Assumes program was just initialized (initString sent).
10605    Leaves program in force mode. */
10606 void
10607 FeedMovesToProgram (ChessProgramState *cps, int upto)
10608 {
10609     int i;
10610
10611     if (appData.debugMode)
10612       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10613               startedFromSetupPosition ? "position and " : "",
10614               backwardMostMove, upto, cps->which);
10615     if(currentlyInitializedVariant != gameInfo.variant) {
10616       char buf[MSG_SIZ];
10617         // [HGM] variantswitch: make engine aware of new variant
10618         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10619                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10620         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10621         SendToProgram(buf, cps);
10622         currentlyInitializedVariant = gameInfo.variant;
10623     }
10624     SendToProgram("force\n", cps);
10625     if (startedFromSetupPosition) {
10626         SendBoard(cps, backwardMostMove);
10627     if (appData.debugMode) {
10628         fprintf(debugFP, "feedMoves\n");
10629     }
10630     }
10631     for (i = backwardMostMove; i < upto; i++) {
10632         SendMoveToProgram(i, cps);
10633     }
10634 }
10635
10636
10637 int
10638 ResurrectChessProgram ()
10639 {
10640      /* The chess program may have exited.
10641         If so, restart it and feed it all the moves made so far. */
10642     static int doInit = 0;
10643
10644     if (appData.noChessProgram) return 1;
10645
10646     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10647         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10648         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10649         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10650     } else {
10651         if (first.pr != NoProc) return 1;
10652         StartChessProgram(&first);
10653     }
10654     InitChessProgram(&first, FALSE);
10655     FeedMovesToProgram(&first, currentMove);
10656
10657     if (!first.sendTime) {
10658         /* can't tell gnuchess what its clock should read,
10659            so we bow to its notion. */
10660         ResetClocks();
10661         timeRemaining[0][currentMove] = whiteTimeRemaining;
10662         timeRemaining[1][currentMove] = blackTimeRemaining;
10663     }
10664
10665     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10666                 appData.icsEngineAnalyze) && first.analysisSupport) {
10667       SendToProgram("analyze\n", &first);
10668       first.analyzing = TRUE;
10669     }
10670     return 1;
10671 }
10672
10673 /*
10674  * Button procedures
10675  */
10676 void
10677 Reset (int redraw, int init)
10678 {
10679     int i;
10680
10681     if (appData.debugMode) {
10682         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10683                 redraw, init, gameMode);
10684     }
10685     CleanupTail(); // [HGM] vari: delete any stored variations
10686     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10687     pausing = pauseExamInvalid = FALSE;
10688     startedFromSetupPosition = blackPlaysFirst = FALSE;
10689     firstMove = TRUE;
10690     whiteFlag = blackFlag = FALSE;
10691     userOfferedDraw = FALSE;
10692     hintRequested = bookRequested = FALSE;
10693     first.maybeThinking = FALSE;
10694     second.maybeThinking = FALSE;
10695     first.bookSuspend = FALSE; // [HGM] book
10696     second.bookSuspend = FALSE;
10697     thinkOutput[0] = NULLCHAR;
10698     lastHint[0] = NULLCHAR;
10699     ClearGameInfo(&gameInfo);
10700     gameInfo.variant = StringToVariant(appData.variant);
10701     ics_user_moved = ics_clock_paused = FALSE;
10702     ics_getting_history = H_FALSE;
10703     ics_gamenum = -1;
10704     white_holding[0] = black_holding[0] = NULLCHAR;
10705     ClearProgramStats();
10706     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10707
10708     ResetFrontEnd();
10709     ClearHighlights();
10710     flipView = appData.flipView;
10711     ClearPremoveHighlights();
10712     gotPremove = FALSE;
10713     alarmSounded = FALSE;
10714
10715     GameEnds(EndOfFile, NULL, GE_PLAYER);
10716     if(appData.serverMovesName != NULL) {
10717         /* [HGM] prepare to make moves file for broadcasting */
10718         clock_t t = clock();
10719         if(serverMoves != NULL) fclose(serverMoves);
10720         serverMoves = fopen(appData.serverMovesName, "r");
10721         if(serverMoves != NULL) {
10722             fclose(serverMoves);
10723             /* delay 15 sec before overwriting, so all clients can see end */
10724             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10725         }
10726         serverMoves = fopen(appData.serverMovesName, "w");
10727     }
10728
10729     ExitAnalyzeMode();
10730     gameMode = BeginningOfGame;
10731     ModeHighlight();
10732     if(appData.icsActive) gameInfo.variant = VariantNormal;
10733     currentMove = forwardMostMove = backwardMostMove = 0;
10734     MarkTargetSquares(1);
10735     InitPosition(redraw);
10736     for (i = 0; i < MAX_MOVES; i++) {
10737         if (commentList[i] != NULL) {
10738             free(commentList[i]);
10739             commentList[i] = NULL;
10740         }
10741     }
10742     ResetClocks();
10743     timeRemaining[0][0] = whiteTimeRemaining;
10744     timeRemaining[1][0] = blackTimeRemaining;
10745
10746     if (first.pr == NoProc) {
10747         StartChessProgram(&first);
10748     }
10749     if (init) {
10750             InitChessProgram(&first, startedFromSetupPosition);
10751     }
10752     DisplayTitle("");
10753     DisplayMessage("", "");
10754     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10755     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10756     mappedMove = -1;   // [HGM] exclude: invalidate map
10757 }
10758
10759 void
10760 AutoPlayGameLoop ()
10761 {
10762     for (;;) {
10763         if (!AutoPlayOneMove())
10764           return;
10765         if (matchMode || appData.timeDelay == 0)
10766           continue;
10767         if (appData.timeDelay < 0)
10768           return;
10769         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10770         break;
10771     }
10772 }
10773
10774
10775 int
10776 AutoPlayOneMove ()
10777 {
10778     int fromX, fromY, toX, toY;
10779
10780     if (appData.debugMode) {
10781       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10782     }
10783
10784     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10785       return FALSE;
10786
10787     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10788       pvInfoList[currentMove].depth = programStats.depth;
10789       pvInfoList[currentMove].score = programStats.score;
10790       pvInfoList[currentMove].time  = 0;
10791       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10792     }
10793
10794     if (currentMove >= forwardMostMove) {
10795       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10796 //      gameMode = EndOfGame;
10797 //      ModeHighlight();
10798
10799       /* [AS] Clear current move marker at the end of a game */
10800       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10801
10802       return FALSE;
10803     }
10804
10805     toX = moveList[currentMove][2] - AAA;
10806     toY = moveList[currentMove][3] - ONE;
10807
10808     if (moveList[currentMove][1] == '@') {
10809         if (appData.highlightLastMove) {
10810             SetHighlights(-1, -1, toX, toY);
10811         }
10812     } else {
10813         fromX = moveList[currentMove][0] - AAA;
10814         fromY = moveList[currentMove][1] - ONE;
10815
10816         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10817
10818         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10819
10820         if (appData.highlightLastMove) {
10821             SetHighlights(fromX, fromY, toX, toY);
10822         }
10823     }
10824     DisplayMove(currentMove);
10825     SendMoveToProgram(currentMove++, &first);
10826     DisplayBothClocks();
10827     DrawPosition(FALSE, boards[currentMove]);
10828     // [HGM] PV info: always display, routine tests if empty
10829     DisplayComment(currentMove - 1, commentList[currentMove]);
10830     return TRUE;
10831 }
10832
10833
10834 int
10835 LoadGameOneMove (ChessMove readAhead)
10836 {
10837     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10838     char promoChar = NULLCHAR;
10839     ChessMove moveType;
10840     char move[MSG_SIZ];
10841     char *p, *q;
10842
10843     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10844         gameMode != AnalyzeMode && gameMode != Training) {
10845         gameFileFP = NULL;
10846         return FALSE;
10847     }
10848
10849     yyboardindex = forwardMostMove;
10850     if (readAhead != EndOfFile) {
10851       moveType = readAhead;
10852     } else {
10853       if (gameFileFP == NULL)
10854           return FALSE;
10855       moveType = (ChessMove) Myylex();
10856     }
10857
10858     done = FALSE;
10859     switch (moveType) {
10860       case Comment:
10861         if (appData.debugMode)
10862           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10863         p = yy_text;
10864
10865         /* append the comment but don't display it */
10866         AppendComment(currentMove, p, FALSE);
10867         return TRUE;
10868
10869       case WhiteCapturesEnPassant:
10870       case BlackCapturesEnPassant:
10871       case WhitePromotion:
10872       case BlackPromotion:
10873       case WhiteNonPromotion:
10874       case BlackNonPromotion:
10875       case NormalMove:
10876       case WhiteKingSideCastle:
10877       case WhiteQueenSideCastle:
10878       case BlackKingSideCastle:
10879       case BlackQueenSideCastle:
10880       case WhiteKingSideCastleWild:
10881       case WhiteQueenSideCastleWild:
10882       case BlackKingSideCastleWild:
10883       case BlackQueenSideCastleWild:
10884       /* PUSH Fabien */
10885       case WhiteHSideCastleFR:
10886       case WhiteASideCastleFR:
10887       case BlackHSideCastleFR:
10888       case BlackASideCastleFR:
10889       /* POP Fabien */
10890         if (appData.debugMode)
10891           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10892         fromX = currentMoveString[0] - AAA;
10893         fromY = currentMoveString[1] - ONE;
10894         toX = currentMoveString[2] - AAA;
10895         toY = currentMoveString[3] - ONE;
10896         promoChar = currentMoveString[4];
10897         break;
10898
10899       case WhiteDrop:
10900       case BlackDrop:
10901         if (appData.debugMode)
10902           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10903         fromX = moveType == WhiteDrop ?
10904           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10905         (int) CharToPiece(ToLower(currentMoveString[0]));
10906         fromY = DROP_RANK;
10907         toX = currentMoveString[2] - AAA;
10908         toY = currentMoveString[3] - ONE;
10909         break;
10910
10911       case WhiteWins:
10912       case BlackWins:
10913       case GameIsDrawn:
10914       case GameUnfinished:
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10917         p = strchr(yy_text, '{');
10918         if (p == NULL) p = strchr(yy_text, '(');
10919         if (p == NULL) {
10920             p = yy_text;
10921             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10922         } else {
10923             q = strchr(p, *p == '{' ? '}' : ')');
10924             if (q != NULL) *q = NULLCHAR;
10925             p++;
10926         }
10927         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10928         GameEnds(moveType, p, GE_FILE);
10929         done = TRUE;
10930         if (cmailMsgLoaded) {
10931             ClearHighlights();
10932             flipView = WhiteOnMove(currentMove);
10933             if (moveType == GameUnfinished) flipView = !flipView;
10934             if (appData.debugMode)
10935               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10936         }
10937         break;
10938
10939       case EndOfFile:
10940         if (appData.debugMode)
10941           fprintf(debugFP, "Parser hit end of file\n");
10942         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10943           case MT_NONE:
10944           case MT_CHECK:
10945             break;
10946           case MT_CHECKMATE:
10947           case MT_STAINMATE:
10948             if (WhiteOnMove(currentMove)) {
10949                 GameEnds(BlackWins, "Black mates", GE_FILE);
10950             } else {
10951                 GameEnds(WhiteWins, "White mates", GE_FILE);
10952             }
10953             break;
10954           case MT_STALEMATE:
10955             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10956             break;
10957         }
10958         done = TRUE;
10959         break;
10960
10961       case MoveNumberOne:
10962         if (lastLoadGameStart == GNUChessGame) {
10963             /* GNUChessGames have numbers, but they aren't move numbers */
10964             if (appData.debugMode)
10965               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10966                       yy_text, (int) moveType);
10967             return LoadGameOneMove(EndOfFile); /* tail recursion */
10968         }
10969         /* else fall thru */
10970
10971       case XBoardGame:
10972       case GNUChessGame:
10973       case PGNTag:
10974         /* Reached start of next game in file */
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10977         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10978           case MT_NONE:
10979           case MT_CHECK:
10980             break;
10981           case MT_CHECKMATE:
10982           case MT_STAINMATE:
10983             if (WhiteOnMove(currentMove)) {
10984                 GameEnds(BlackWins, "Black mates", GE_FILE);
10985             } else {
10986                 GameEnds(WhiteWins, "White mates", GE_FILE);
10987             }
10988             break;
10989           case MT_STALEMATE:
10990             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10991             break;
10992         }
10993         done = TRUE;
10994         break;
10995
10996       case PositionDiagram:     /* should not happen; ignore */
10997       case ElapsedTime:         /* ignore */
10998       case NAG:                 /* ignore */
10999         if (appData.debugMode)
11000           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11001                   yy_text, (int) moveType);
11002         return LoadGameOneMove(EndOfFile); /* tail recursion */
11003
11004       case IllegalMove:
11005         if (appData.testLegality) {
11006             if (appData.debugMode)
11007               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11008             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11009                     (forwardMostMove / 2) + 1,
11010                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11011             DisplayError(move, 0);
11012             done = TRUE;
11013         } else {
11014             if (appData.debugMode)
11015               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11016                       yy_text, currentMoveString);
11017             fromX = currentMoveString[0] - AAA;
11018             fromY = currentMoveString[1] - ONE;
11019             toX = currentMoveString[2] - AAA;
11020             toY = currentMoveString[3] - ONE;
11021             promoChar = currentMoveString[4];
11022         }
11023         break;
11024
11025       case AmbiguousMove:
11026         if (appData.debugMode)
11027           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11028         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11029                 (forwardMostMove / 2) + 1,
11030                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11031         DisplayError(move, 0);
11032         done = TRUE;
11033         break;
11034
11035       default:
11036       case ImpossibleMove:
11037         if (appData.debugMode)
11038           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11039         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11040                 (forwardMostMove / 2) + 1,
11041                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11042         DisplayError(move, 0);
11043         done = TRUE;
11044         break;
11045     }
11046
11047     if (done) {
11048         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11049             DrawPosition(FALSE, boards[currentMove]);
11050             DisplayBothClocks();
11051             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11052               DisplayComment(currentMove - 1, commentList[currentMove]);
11053         }
11054         (void) StopLoadGameTimer();
11055         gameFileFP = NULL;
11056         cmailOldMove = forwardMostMove;
11057         return FALSE;
11058     } else {
11059         /* currentMoveString is set as a side-effect of yylex */
11060
11061         thinkOutput[0] = NULLCHAR;
11062         MakeMove(fromX, fromY, toX, toY, promoChar);
11063         currentMove = forwardMostMove;
11064         return TRUE;
11065     }
11066 }
11067
11068 /* Load the nth game from the given file */
11069 int
11070 LoadGameFromFile (char *filename, int n, char *title, int useList)
11071 {
11072     FILE *f;
11073     char buf[MSG_SIZ];
11074
11075     if (strcmp(filename, "-") == 0) {
11076         f = stdin;
11077         title = "stdin";
11078     } else {
11079         f = fopen(filename, "rb");
11080         if (f == NULL) {
11081           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11082             DisplayError(buf, errno);
11083             return FALSE;
11084         }
11085     }
11086     if (fseek(f, 0, 0) == -1) {
11087         /* f is not seekable; probably a pipe */
11088         useList = FALSE;
11089     }
11090     if (useList && n == 0) {
11091         int error = GameListBuild(f);
11092         if (error) {
11093             DisplayError(_("Cannot build game list"), error);
11094         } else if (!ListEmpty(&gameList) &&
11095                    ((ListGame *) gameList.tailPred)->number > 1) {
11096             GameListPopUp(f, title);
11097             return TRUE;
11098         }
11099         GameListDestroy();
11100         n = 1;
11101     }
11102     if (n == 0) n = 1;
11103     return LoadGame(f, n, title, FALSE);
11104 }
11105
11106
11107 void
11108 MakeRegisteredMove ()
11109 {
11110     int fromX, fromY, toX, toY;
11111     char promoChar;
11112     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11113         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11114           case CMAIL_MOVE:
11115           case CMAIL_DRAW:
11116             if (appData.debugMode)
11117               fprintf(debugFP, "Restoring %s for game %d\n",
11118                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11119
11120             thinkOutput[0] = NULLCHAR;
11121             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11122             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11123             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11124             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11125             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11126             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11127             MakeMove(fromX, fromY, toX, toY, promoChar);
11128             ShowMove(fromX, fromY, toX, toY);
11129
11130             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11131               case MT_NONE:
11132               case MT_CHECK:
11133                 break;
11134
11135               case MT_CHECKMATE:
11136               case MT_STAINMATE:
11137                 if (WhiteOnMove(currentMove)) {
11138                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11139                 } else {
11140                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11141                 }
11142                 break;
11143
11144               case MT_STALEMATE:
11145                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11146                 break;
11147             }
11148
11149             break;
11150
11151           case CMAIL_RESIGN:
11152             if (WhiteOnMove(currentMove)) {
11153                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11154             } else {
11155                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11156             }
11157             break;
11158
11159           case CMAIL_ACCEPT:
11160             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11161             break;
11162
11163           default:
11164             break;
11165         }
11166     }
11167
11168     return;
11169 }
11170
11171 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11172 int
11173 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11174 {
11175     int retVal;
11176
11177     if (gameNumber > nCmailGames) {
11178         DisplayError(_("No more games in this message"), 0);
11179         return FALSE;
11180     }
11181     if (f == lastLoadGameFP) {
11182         int offset = gameNumber - lastLoadGameNumber;
11183         if (offset == 0) {
11184             cmailMsg[0] = NULLCHAR;
11185             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11186                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11187                 nCmailMovesRegistered--;
11188             }
11189             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11190             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11191                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11192             }
11193         } else {
11194             if (! RegisterMove()) return FALSE;
11195         }
11196     }
11197
11198     retVal = LoadGame(f, gameNumber, title, useList);
11199
11200     /* Make move registered during previous look at this game, if any */
11201     MakeRegisteredMove();
11202
11203     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11204         commentList[currentMove]
11205           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11206         DisplayComment(currentMove - 1, commentList[currentMove]);
11207     }
11208
11209     return retVal;
11210 }
11211
11212 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11213 int
11214 ReloadGame (int offset)
11215 {
11216     int gameNumber = lastLoadGameNumber + offset;
11217     if (lastLoadGameFP == NULL) {
11218         DisplayError(_("No game has been loaded yet"), 0);
11219         return FALSE;
11220     }
11221     if (gameNumber <= 0) {
11222         DisplayError(_("Can't back up any further"), 0);
11223         return FALSE;
11224     }
11225     if (cmailMsgLoaded) {
11226         return CmailLoadGame(lastLoadGameFP, gameNumber,
11227                              lastLoadGameTitle, lastLoadGameUseList);
11228     } else {
11229         return LoadGame(lastLoadGameFP, gameNumber,
11230                         lastLoadGameTitle, lastLoadGameUseList);
11231     }
11232 }
11233
11234 int keys[EmptySquare+1];
11235
11236 int
11237 PositionMatches (Board b1, Board b2)
11238 {
11239     int r, f, sum=0;
11240     switch(appData.searchMode) {
11241         case 1: return CompareWithRights(b1, b2);
11242         case 2:
11243             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11244                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11245             }
11246             return TRUE;
11247         case 3:
11248             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11249               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11250                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11251             }
11252             return sum==0;
11253         case 4:
11254             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11255                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11256             }
11257             return sum==0;
11258     }
11259     return TRUE;
11260 }
11261
11262 #define Q_PROMO  4
11263 #define Q_EP     3
11264 #define Q_BCASTL 2
11265 #define Q_WCASTL 1
11266
11267 int pieceList[256], quickBoard[256];
11268 ChessSquare pieceType[256] = { EmptySquare };
11269 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11270 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11271 int soughtTotal, turn;
11272 Boolean epOK, flipSearch;
11273
11274 typedef struct {
11275     unsigned char piece, to;
11276 } Move;
11277
11278 #define DSIZE (250000)
11279
11280 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11281 Move *moveDatabase = initialSpace;
11282 unsigned int movePtr, dataSize = DSIZE;
11283
11284 int
11285 MakePieceList (Board board, int *counts)
11286 {
11287     int r, f, n=Q_PROMO, total=0;
11288     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11289     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11290         int sq = f + (r<<4);
11291         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11292             quickBoard[sq] = ++n;
11293             pieceList[n] = sq;
11294             pieceType[n] = board[r][f];
11295             counts[board[r][f]]++;
11296             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11297             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11298             total++;
11299         }
11300     }
11301     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11302     return total;
11303 }
11304
11305 void
11306 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11307 {
11308     int sq = fromX + (fromY<<4);
11309     int piece = quickBoard[sq];
11310     quickBoard[sq] = 0;
11311     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11312     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11313         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11314         moveDatabase[movePtr++].piece = Q_WCASTL;
11315         quickBoard[sq] = piece;
11316         piece = quickBoard[from]; quickBoard[from] = 0;
11317         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11318     } else
11319     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11320         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11321         moveDatabase[movePtr++].piece = Q_BCASTL;
11322         quickBoard[sq] = piece;
11323         piece = quickBoard[from]; quickBoard[from] = 0;
11324         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11325     } else
11326     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11327         quickBoard[(fromY<<4)+toX] = 0;
11328         moveDatabase[movePtr].piece = Q_EP;
11329         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11330         moveDatabase[movePtr].to = sq;
11331     } else
11332     if(promoPiece != pieceType[piece]) {
11333         moveDatabase[movePtr++].piece = Q_PROMO;
11334         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11335     }
11336     moveDatabase[movePtr].piece = piece;
11337     quickBoard[sq] = piece;
11338     movePtr++;
11339 }
11340
11341 int
11342 PackGame (Board board)
11343 {
11344     Move *newSpace = NULL;
11345     moveDatabase[movePtr].piece = 0; // terminate previous game
11346     if(movePtr > dataSize) {
11347         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11348         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11349         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11350         if(newSpace) {
11351             int i;
11352             Move *p = moveDatabase, *q = newSpace;
11353             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11354             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11355             moveDatabase = newSpace;
11356         } else { // calloc failed, we must be out of memory. Too bad...
11357             dataSize = 0; // prevent calloc events for all subsequent games
11358             return 0;     // and signal this one isn't cached
11359         }
11360     }
11361     movePtr++;
11362     MakePieceList(board, counts);
11363     return movePtr;
11364 }
11365
11366 int
11367 QuickCompare (Board board, int *minCounts, int *maxCounts)
11368 {   // compare according to search mode
11369     int r, f;
11370     switch(appData.searchMode)
11371     {
11372       case 1: // exact position match
11373         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11374         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11375             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11376         }
11377         break;
11378       case 2: // can have extra material on empty squares
11379         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11380             if(board[r][f] == EmptySquare) continue;
11381             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11382         }
11383         break;
11384       case 3: // material with exact Pawn structure
11385         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11386             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11387             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11388         } // fall through to material comparison
11389       case 4: // exact material
11390         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11391         break;
11392       case 6: // material range with given imbalance
11393         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11394         // fall through to range comparison
11395       case 5: // material range
11396         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11397     }
11398     return TRUE;
11399 }
11400
11401 int
11402 QuickScan (Board board, Move *move)
11403 {   // reconstruct game,and compare all positions in it
11404     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11405     do {
11406         int piece = move->piece;
11407         int to = move->to, from = pieceList[piece];
11408         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11409           if(!piece) return -1;
11410           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11411             piece = (++move)->piece;
11412             from = pieceList[piece];
11413             counts[pieceType[piece]]--;
11414             pieceType[piece] = (ChessSquare) move->to;
11415             counts[move->to]++;
11416           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11417             counts[pieceType[quickBoard[to]]]--;
11418             quickBoard[to] = 0; total--;
11419             move++;
11420             continue;
11421           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11422             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11423             from  = pieceList[piece]; // so this must be King
11424             quickBoard[from] = 0;
11425             quickBoard[to] = piece;
11426             pieceList[piece] = to;
11427             move++;
11428             continue;
11429           }
11430         }
11431         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11432         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11433         quickBoard[from] = 0;
11434         quickBoard[to] = piece;
11435         pieceList[piece] = to;
11436         cnt++; turn ^= 3;
11437         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11438            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11439            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11440                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11441           ) {
11442             static int lastCounts[EmptySquare+1];
11443             int i;
11444             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11445             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11446         } else stretch = 0;
11447         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11448         move++;
11449     } while(1);
11450 }
11451
11452 void
11453 InitSearch ()
11454 {
11455     int r, f;
11456     flipSearch = FALSE;
11457     CopyBoard(soughtBoard, boards[currentMove]);
11458     soughtTotal = MakePieceList(soughtBoard, maxSought);
11459     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11460     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11461     CopyBoard(reverseBoard, boards[currentMove]);
11462     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11463         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11464         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11465         reverseBoard[r][f] = piece;
11466     }
11467     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11468     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11469     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11470                  || (boards[currentMove][CASTLING][2] == NoRights || 
11471                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11472                  && (boards[currentMove][CASTLING][5] == NoRights || 
11473                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11474       ) {
11475         flipSearch = TRUE;
11476         CopyBoard(flipBoard, soughtBoard);
11477         CopyBoard(rotateBoard, reverseBoard);
11478         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11479             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11480             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11481         }
11482     }
11483     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11484     if(appData.searchMode >= 5) {
11485         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11486         MakePieceList(soughtBoard, minSought);
11487         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11488     }
11489     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11490         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11491 }
11492
11493 GameInfo dummyInfo;
11494
11495 int
11496 GameContainsPosition (FILE *f, ListGame *lg)
11497 {
11498     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11499     int fromX, fromY, toX, toY;
11500     char promoChar;
11501     static int initDone=FALSE;
11502
11503     // weed out games based on numerical tag comparison
11504     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11505     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11506     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11507     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11508     if(!initDone) {
11509         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11510         initDone = TRUE;
11511     }
11512     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11513     else CopyBoard(boards[scratch], initialPosition); // default start position
11514     if(lg->moves) {
11515         turn = btm + 1;
11516         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11517         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11518     }
11519     if(btm) plyNr++;
11520     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11521     fseek(f, lg->offset, 0);
11522     yynewfile(f);
11523     while(1) {
11524         yyboardindex = scratch;
11525         quickFlag = plyNr+1;
11526         next = Myylex();
11527         quickFlag = 0;
11528         switch(next) {
11529             case PGNTag:
11530                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11531             default:
11532                 continue;
11533
11534             case XBoardGame:
11535             case GNUChessGame:
11536                 if(plyNr) return -1; // after we have seen moves, this is for new game
11537               continue;
11538
11539             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11540             case ImpossibleMove:
11541             case WhiteWins: // game ends here with these four
11542             case BlackWins:
11543             case GameIsDrawn:
11544             case GameUnfinished:
11545                 return -1;
11546
11547             case IllegalMove:
11548                 if(appData.testLegality) return -1;
11549             case WhiteCapturesEnPassant:
11550             case BlackCapturesEnPassant:
11551             case WhitePromotion:
11552             case BlackPromotion:
11553             case WhiteNonPromotion:
11554             case BlackNonPromotion:
11555             case NormalMove:
11556             case WhiteKingSideCastle:
11557             case WhiteQueenSideCastle:
11558             case BlackKingSideCastle:
11559             case BlackQueenSideCastle:
11560             case WhiteKingSideCastleWild:
11561             case WhiteQueenSideCastleWild:
11562             case BlackKingSideCastleWild:
11563             case BlackQueenSideCastleWild:
11564             case WhiteHSideCastleFR:
11565             case WhiteASideCastleFR:
11566             case BlackHSideCastleFR:
11567             case BlackASideCastleFR:
11568                 fromX = currentMoveString[0] - AAA;
11569                 fromY = currentMoveString[1] - ONE;
11570                 toX = currentMoveString[2] - AAA;
11571                 toY = currentMoveString[3] - ONE;
11572                 promoChar = currentMoveString[4];
11573                 break;
11574             case WhiteDrop:
11575             case BlackDrop:
11576                 fromX = next == WhiteDrop ?
11577                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11578                   (int) CharToPiece(ToLower(currentMoveString[0]));
11579                 fromY = DROP_RANK;
11580                 toX = currentMoveString[2] - AAA;
11581                 toY = currentMoveString[3] - ONE;
11582                 promoChar = 0;
11583                 break;
11584         }
11585         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11586         plyNr++;
11587         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11588         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11589         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11590         if(appData.findMirror) {
11591             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11592             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11593         }
11594     }
11595 }
11596
11597 /* Load the nth game from open file f */
11598 int
11599 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11600 {
11601     ChessMove cm;
11602     char buf[MSG_SIZ];
11603     int gn = gameNumber;
11604     ListGame *lg = NULL;
11605     int numPGNTags = 0;
11606     int err, pos = -1;
11607     GameMode oldGameMode;
11608     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11609
11610     if (appData.debugMode)
11611         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11612
11613     if (gameMode == Training )
11614         SetTrainingModeOff();
11615
11616     oldGameMode = gameMode;
11617     if (gameMode != BeginningOfGame) {
11618       Reset(FALSE, TRUE);
11619     }
11620
11621     gameFileFP = f;
11622     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11623         fclose(lastLoadGameFP);
11624     }
11625
11626     if (useList) {
11627         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11628
11629         if (lg) {
11630             fseek(f, lg->offset, 0);
11631             GameListHighlight(gameNumber);
11632             pos = lg->position;
11633             gn = 1;
11634         }
11635         else {
11636             DisplayError(_("Game number out of range"), 0);
11637             return FALSE;
11638         }
11639     } else {
11640         GameListDestroy();
11641         if (fseek(f, 0, 0) == -1) {
11642             if (f == lastLoadGameFP ?
11643                 gameNumber == lastLoadGameNumber + 1 :
11644                 gameNumber == 1) {
11645                 gn = 1;
11646             } else {
11647                 DisplayError(_("Can't seek on game file"), 0);
11648                 return FALSE;
11649             }
11650         }
11651     }
11652     lastLoadGameFP = f;
11653     lastLoadGameNumber = gameNumber;
11654     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11655     lastLoadGameUseList = useList;
11656
11657     yynewfile(f);
11658
11659     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11660       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11661                 lg->gameInfo.black);
11662             DisplayTitle(buf);
11663     } else if (*title != NULLCHAR) {
11664         if (gameNumber > 1) {
11665           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11666             DisplayTitle(buf);
11667         } else {
11668             DisplayTitle(title);
11669         }
11670     }
11671
11672     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11673         gameMode = PlayFromGameFile;
11674         ModeHighlight();
11675     }
11676
11677     currentMove = forwardMostMove = backwardMostMove = 0;
11678     CopyBoard(boards[0], initialPosition);
11679     StopClocks();
11680
11681     /*
11682      * Skip the first gn-1 games in the file.
11683      * Also skip over anything that precedes an identifiable
11684      * start of game marker, to avoid being confused by
11685      * garbage at the start of the file.  Currently
11686      * recognized start of game markers are the move number "1",
11687      * the pattern "gnuchess .* game", the pattern
11688      * "^[#;%] [^ ]* game file", and a PGN tag block.
11689      * A game that starts with one of the latter two patterns
11690      * will also have a move number 1, possibly
11691      * following a position diagram.
11692      * 5-4-02: Let's try being more lenient and allowing a game to
11693      * start with an unnumbered move.  Does that break anything?
11694      */
11695     cm = lastLoadGameStart = EndOfFile;
11696     while (gn > 0) {
11697         yyboardindex = forwardMostMove;
11698         cm = (ChessMove) Myylex();
11699         switch (cm) {
11700           case EndOfFile:
11701             if (cmailMsgLoaded) {
11702                 nCmailGames = CMAIL_MAX_GAMES - gn;
11703             } else {
11704                 Reset(TRUE, TRUE);
11705                 DisplayError(_("Game not found in file"), 0);
11706             }
11707             return FALSE;
11708
11709           case GNUChessGame:
11710           case XBoardGame:
11711             gn--;
11712             lastLoadGameStart = cm;
11713             break;
11714
11715           case MoveNumberOne:
11716             switch (lastLoadGameStart) {
11717               case GNUChessGame:
11718               case XBoardGame:
11719               case PGNTag:
11720                 break;
11721               case MoveNumberOne:
11722               case EndOfFile:
11723                 gn--;           /* count this game */
11724                 lastLoadGameStart = cm;
11725                 break;
11726               default:
11727                 /* impossible */
11728                 break;
11729             }
11730             break;
11731
11732           case PGNTag:
11733             switch (lastLoadGameStart) {
11734               case GNUChessGame:
11735               case PGNTag:
11736               case MoveNumberOne:
11737               case EndOfFile:
11738                 gn--;           /* count this game */
11739                 lastLoadGameStart = cm;
11740                 break;
11741               case XBoardGame:
11742                 lastLoadGameStart = cm; /* game counted already */
11743                 break;
11744               default:
11745                 /* impossible */
11746                 break;
11747             }
11748             if (gn > 0) {
11749                 do {
11750                     yyboardindex = forwardMostMove;
11751                     cm = (ChessMove) Myylex();
11752                 } while (cm == PGNTag || cm == Comment);
11753             }
11754             break;
11755
11756           case WhiteWins:
11757           case BlackWins:
11758           case GameIsDrawn:
11759             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11760                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11761                     != CMAIL_OLD_RESULT) {
11762                     nCmailResults ++ ;
11763                     cmailResult[  CMAIL_MAX_GAMES
11764                                 - gn - 1] = CMAIL_OLD_RESULT;
11765                 }
11766             }
11767             break;
11768
11769           case NormalMove:
11770             /* Only a NormalMove can be at the start of a game
11771              * without a position diagram. */
11772             if (lastLoadGameStart == EndOfFile ) {
11773               gn--;
11774               lastLoadGameStart = MoveNumberOne;
11775             }
11776             break;
11777
11778           default:
11779             break;
11780         }
11781     }
11782
11783     if (appData.debugMode)
11784       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11785
11786     if (cm == XBoardGame) {
11787         /* Skip any header junk before position diagram and/or move 1 */
11788         for (;;) {
11789             yyboardindex = forwardMostMove;
11790             cm = (ChessMove) Myylex();
11791
11792             if (cm == EndOfFile ||
11793                 cm == GNUChessGame || cm == XBoardGame) {
11794                 /* Empty game; pretend end-of-file and handle later */
11795                 cm = EndOfFile;
11796                 break;
11797             }
11798
11799             if (cm == MoveNumberOne || cm == PositionDiagram ||
11800                 cm == PGNTag || cm == Comment)
11801               break;
11802         }
11803     } else if (cm == GNUChessGame) {
11804         if (gameInfo.event != NULL) {
11805             free(gameInfo.event);
11806         }
11807         gameInfo.event = StrSave(yy_text);
11808     }
11809
11810     startedFromSetupPosition = FALSE;
11811     while (cm == PGNTag) {
11812         if (appData.debugMode)
11813           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11814         err = ParsePGNTag(yy_text, &gameInfo);
11815         if (!err) numPGNTags++;
11816
11817         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11818         if(gameInfo.variant != oldVariant) {
11819             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11820             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11821             InitPosition(TRUE);
11822             oldVariant = gameInfo.variant;
11823             if (appData.debugMode)
11824               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11825         }
11826
11827
11828         if (gameInfo.fen != NULL) {
11829           Board initial_position;
11830           startedFromSetupPosition = TRUE;
11831           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11832             Reset(TRUE, TRUE);
11833             DisplayError(_("Bad FEN position in file"), 0);
11834             return FALSE;
11835           }
11836           CopyBoard(boards[0], initial_position);
11837           if (blackPlaysFirst) {
11838             currentMove = forwardMostMove = backwardMostMove = 1;
11839             CopyBoard(boards[1], initial_position);
11840             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11841             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11842             timeRemaining[0][1] = whiteTimeRemaining;
11843             timeRemaining[1][1] = blackTimeRemaining;
11844             if (commentList[0] != NULL) {
11845               commentList[1] = commentList[0];
11846               commentList[0] = NULL;
11847             }
11848           } else {
11849             currentMove = forwardMostMove = backwardMostMove = 0;
11850           }
11851           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11852           {   int i;
11853               initialRulePlies = FENrulePlies;
11854               for( i=0; i< nrCastlingRights; i++ )
11855                   initialRights[i] = initial_position[CASTLING][i];
11856           }
11857           yyboardindex = forwardMostMove;
11858           free(gameInfo.fen);
11859           gameInfo.fen = NULL;
11860         }
11861
11862         yyboardindex = forwardMostMove;
11863         cm = (ChessMove) Myylex();
11864
11865         /* Handle comments interspersed among the tags */
11866         while (cm == Comment) {
11867             char *p;
11868             if (appData.debugMode)
11869               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11870             p = yy_text;
11871             AppendComment(currentMove, p, FALSE);
11872             yyboardindex = forwardMostMove;
11873             cm = (ChessMove) Myylex();
11874         }
11875     }
11876
11877     /* don't rely on existence of Event tag since if game was
11878      * pasted from clipboard the Event tag may not exist
11879      */
11880     if (numPGNTags > 0){
11881         char *tags;
11882         if (gameInfo.variant == VariantNormal) {
11883           VariantClass v = StringToVariant(gameInfo.event);
11884           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11885           if(v < VariantShogi) gameInfo.variant = v;
11886         }
11887         if (!matchMode) {
11888           if( appData.autoDisplayTags ) {
11889             tags = PGNTags(&gameInfo);
11890             TagsPopUp(tags, CmailMsg());
11891             free(tags);
11892           }
11893         }
11894     } else {
11895         /* Make something up, but don't display it now */
11896         SetGameInfo();
11897         TagsPopDown();
11898     }
11899
11900     if (cm == PositionDiagram) {
11901         int i, j;
11902         char *p;
11903         Board initial_position;
11904
11905         if (appData.debugMode)
11906           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11907
11908         if (!startedFromSetupPosition) {
11909             p = yy_text;
11910             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11911               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11912                 switch (*p) {
11913                   case '{':
11914                   case '[':
11915                   case '-':
11916                   case ' ':
11917                   case '\t':
11918                   case '\n':
11919                   case '\r':
11920                     break;
11921                   default:
11922                     initial_position[i][j++] = CharToPiece(*p);
11923                     break;
11924                 }
11925             while (*p == ' ' || *p == '\t' ||
11926                    *p == '\n' || *p == '\r') p++;
11927
11928             if (strncmp(p, "black", strlen("black"))==0)
11929               blackPlaysFirst = TRUE;
11930             else
11931               blackPlaysFirst = FALSE;
11932             startedFromSetupPosition = TRUE;
11933
11934             CopyBoard(boards[0], initial_position);
11935             if (blackPlaysFirst) {
11936                 currentMove = forwardMostMove = backwardMostMove = 1;
11937                 CopyBoard(boards[1], initial_position);
11938                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11939                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11940                 timeRemaining[0][1] = whiteTimeRemaining;
11941                 timeRemaining[1][1] = blackTimeRemaining;
11942                 if (commentList[0] != NULL) {
11943                     commentList[1] = commentList[0];
11944                     commentList[0] = NULL;
11945                 }
11946             } else {
11947                 currentMove = forwardMostMove = backwardMostMove = 0;
11948             }
11949         }
11950         yyboardindex = forwardMostMove;
11951         cm = (ChessMove) Myylex();
11952     }
11953
11954     if (first.pr == NoProc) {
11955         StartChessProgram(&first);
11956     }
11957     InitChessProgram(&first, FALSE);
11958     SendToProgram("force\n", &first);
11959     if (startedFromSetupPosition) {
11960         SendBoard(&first, forwardMostMove);
11961     if (appData.debugMode) {
11962         fprintf(debugFP, "Load Game\n");
11963     }
11964         DisplayBothClocks();
11965     }
11966
11967     /* [HGM] server: flag to write setup moves in broadcast file as one */
11968     loadFlag = appData.suppressLoadMoves;
11969
11970     while (cm == Comment) {
11971         char *p;
11972         if (appData.debugMode)
11973           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11974         p = yy_text;
11975         AppendComment(currentMove, p, FALSE);
11976         yyboardindex = forwardMostMove;
11977         cm = (ChessMove) Myylex();
11978     }
11979
11980     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11981         cm == WhiteWins || cm == BlackWins ||
11982         cm == GameIsDrawn || cm == GameUnfinished) {
11983         DisplayMessage("", _("No moves in game"));
11984         if (cmailMsgLoaded) {
11985             if (appData.debugMode)
11986               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11987             ClearHighlights();
11988             flipView = FALSE;
11989         }
11990         DrawPosition(FALSE, boards[currentMove]);
11991         DisplayBothClocks();
11992         gameMode = EditGame;
11993         ModeHighlight();
11994         gameFileFP = NULL;
11995         cmailOldMove = 0;
11996         return TRUE;
11997     }
11998
11999     // [HGM] PV info: routine tests if comment empty
12000     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12001         DisplayComment(currentMove - 1, commentList[currentMove]);
12002     }
12003     if (!matchMode && appData.timeDelay != 0)
12004       DrawPosition(FALSE, boards[currentMove]);
12005
12006     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12007       programStats.ok_to_send = 1;
12008     }
12009
12010     /* if the first token after the PGN tags is a move
12011      * and not move number 1, retrieve it from the parser
12012      */
12013     if (cm != MoveNumberOne)
12014         LoadGameOneMove(cm);
12015
12016     /* load the remaining moves from the file */
12017     while (LoadGameOneMove(EndOfFile)) {
12018       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12019       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12020     }
12021
12022     /* rewind to the start of the game */
12023     currentMove = backwardMostMove;
12024
12025     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12026
12027     if (oldGameMode == AnalyzeFile ||
12028         oldGameMode == AnalyzeMode) {
12029       AnalyzeFileEvent();
12030     }
12031
12032     if (!matchMode && pos >= 0) {
12033         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12034     } else
12035     if (matchMode || appData.timeDelay == 0) {
12036       ToEndEvent();
12037     } else if (appData.timeDelay > 0) {
12038       AutoPlayGameLoop();
12039     }
12040
12041     if (appData.debugMode)
12042         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12043
12044     loadFlag = 0; /* [HGM] true game starts */
12045     return TRUE;
12046 }
12047
12048 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12049 int
12050 ReloadPosition (int offset)
12051 {
12052     int positionNumber = lastLoadPositionNumber + offset;
12053     if (lastLoadPositionFP == NULL) {
12054         DisplayError(_("No position has been loaded yet"), 0);
12055         return FALSE;
12056     }
12057     if (positionNumber <= 0) {
12058         DisplayError(_("Can't back up any further"), 0);
12059         return FALSE;
12060     }
12061     return LoadPosition(lastLoadPositionFP, positionNumber,
12062                         lastLoadPositionTitle);
12063 }
12064
12065 /* Load the nth position from the given file */
12066 int
12067 LoadPositionFromFile (char *filename, int n, char *title)
12068 {
12069     FILE *f;
12070     char buf[MSG_SIZ];
12071
12072     if (strcmp(filename, "-") == 0) {
12073         return LoadPosition(stdin, n, "stdin");
12074     } else {
12075         f = fopen(filename, "rb");
12076         if (f == NULL) {
12077             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12078             DisplayError(buf, errno);
12079             return FALSE;
12080         } else {
12081             return LoadPosition(f, n, title);
12082         }
12083     }
12084 }
12085
12086 /* Load the nth position from the given open file, and close it */
12087 int
12088 LoadPosition (FILE *f, int positionNumber, char *title)
12089 {
12090     char *p, line[MSG_SIZ];
12091     Board initial_position;
12092     int i, j, fenMode, pn;
12093
12094     if (gameMode == Training )
12095         SetTrainingModeOff();
12096
12097     if (gameMode != BeginningOfGame) {
12098         Reset(FALSE, TRUE);
12099     }
12100     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12101         fclose(lastLoadPositionFP);
12102     }
12103     if (positionNumber == 0) positionNumber = 1;
12104     lastLoadPositionFP = f;
12105     lastLoadPositionNumber = positionNumber;
12106     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12107     if (first.pr == NoProc && !appData.noChessProgram) {
12108       StartChessProgram(&first);
12109       InitChessProgram(&first, FALSE);
12110     }
12111     pn = positionNumber;
12112     if (positionNumber < 0) {
12113         /* Negative position number means to seek to that byte offset */
12114         if (fseek(f, -positionNumber, 0) == -1) {
12115             DisplayError(_("Can't seek on position file"), 0);
12116             return FALSE;
12117         };
12118         pn = 1;
12119     } else {
12120         if (fseek(f, 0, 0) == -1) {
12121             if (f == lastLoadPositionFP ?
12122                 positionNumber == lastLoadPositionNumber + 1 :
12123                 positionNumber == 1) {
12124                 pn = 1;
12125             } else {
12126                 DisplayError(_("Can't seek on position file"), 0);
12127                 return FALSE;
12128             }
12129         }
12130     }
12131     /* See if this file is FEN or old-style xboard */
12132     if (fgets(line, MSG_SIZ, f) == NULL) {
12133         DisplayError(_("Position not found in file"), 0);
12134         return FALSE;
12135     }
12136     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12137     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12138
12139     if (pn >= 2) {
12140         if (fenMode || line[0] == '#') pn--;
12141         while (pn > 0) {
12142             /* skip positions before number pn */
12143             if (fgets(line, MSG_SIZ, f) == NULL) {
12144                 Reset(TRUE, TRUE);
12145                 DisplayError(_("Position not found in file"), 0);
12146                 return FALSE;
12147             }
12148             if (fenMode || line[0] == '#') pn--;
12149         }
12150     }
12151
12152     if (fenMode) {
12153         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12154             DisplayError(_("Bad FEN position in file"), 0);
12155             return FALSE;
12156         }
12157     } else {
12158         (void) fgets(line, MSG_SIZ, f);
12159         (void) fgets(line, MSG_SIZ, f);
12160
12161         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12162             (void) fgets(line, MSG_SIZ, f);
12163             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12164                 if (*p == ' ')
12165                   continue;
12166                 initial_position[i][j++] = CharToPiece(*p);
12167             }
12168         }
12169
12170         blackPlaysFirst = FALSE;
12171         if (!feof(f)) {
12172             (void) fgets(line, MSG_SIZ, f);
12173             if (strncmp(line, "black", strlen("black"))==0)
12174               blackPlaysFirst = TRUE;
12175         }
12176     }
12177     startedFromSetupPosition = TRUE;
12178
12179     CopyBoard(boards[0], initial_position);
12180     if (blackPlaysFirst) {
12181         currentMove = forwardMostMove = backwardMostMove = 1;
12182         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12183         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12184         CopyBoard(boards[1], initial_position);
12185         DisplayMessage("", _("Black to play"));
12186     } else {
12187         currentMove = forwardMostMove = backwardMostMove = 0;
12188         DisplayMessage("", _("White to play"));
12189     }
12190     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12191     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12192         SendToProgram("force\n", &first);
12193         SendBoard(&first, forwardMostMove);
12194     }
12195     if (appData.debugMode) {
12196 int i, j;
12197   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12198   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12199         fprintf(debugFP, "Load Position\n");
12200     }
12201
12202     if (positionNumber > 1) {
12203       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12204         DisplayTitle(line);
12205     } else {
12206         DisplayTitle(title);
12207     }
12208     gameMode = EditGame;
12209     ModeHighlight();
12210     ResetClocks();
12211     timeRemaining[0][1] = whiteTimeRemaining;
12212     timeRemaining[1][1] = blackTimeRemaining;
12213     DrawPosition(FALSE, boards[currentMove]);
12214
12215     return TRUE;
12216 }
12217
12218
12219 void
12220 CopyPlayerNameIntoFileName (char **dest, char *src)
12221 {
12222     while (*src != NULLCHAR && *src != ',') {
12223         if (*src == ' ') {
12224             *(*dest)++ = '_';
12225             src++;
12226         } else {
12227             *(*dest)++ = *src++;
12228         }
12229     }
12230 }
12231
12232 char *
12233 DefaultFileName (char *ext)
12234 {
12235     static char def[MSG_SIZ];
12236     char *p;
12237
12238     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12239         p = def;
12240         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12241         *p++ = '-';
12242         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12243         *p++ = '.';
12244         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12245     } else {
12246         def[0] = NULLCHAR;
12247     }
12248     return def;
12249 }
12250
12251 /* Save the current game to the given file */
12252 int
12253 SaveGameToFile (char *filename, int append)
12254 {
12255     FILE *f;
12256     char buf[MSG_SIZ];
12257     int result, i, t,tot=0;
12258
12259     if (strcmp(filename, "-") == 0) {
12260         return SaveGame(stdout, 0, NULL);
12261     } else {
12262         for(i=0; i<10; i++) { // upto 10 tries
12263              f = fopen(filename, append ? "a" : "w");
12264              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12265              if(f || errno != 13) break;
12266              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12267              tot += t;
12268         }
12269         if (f == NULL) {
12270             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12271             DisplayError(buf, errno);
12272             return FALSE;
12273         } else {
12274             safeStrCpy(buf, lastMsg, MSG_SIZ);
12275             DisplayMessage(_("Waiting for access to save file"), "");
12276             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12277             DisplayMessage(_("Saving game"), "");
12278             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12279             result = SaveGame(f, 0, NULL);
12280             DisplayMessage(buf, "");
12281             return result;
12282         }
12283     }
12284 }
12285
12286 char *
12287 SavePart (char *str)
12288 {
12289     static char buf[MSG_SIZ];
12290     char *p;
12291
12292     p = strchr(str, ' ');
12293     if (p == NULL) return str;
12294     strncpy(buf, str, p - str);
12295     buf[p - str] = NULLCHAR;
12296     return buf;
12297 }
12298
12299 #define PGN_MAX_LINE 75
12300
12301 #define PGN_SIDE_WHITE  0
12302 #define PGN_SIDE_BLACK  1
12303
12304 static int
12305 FindFirstMoveOutOfBook (int side)
12306 {
12307     int result = -1;
12308
12309     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12310         int index = backwardMostMove;
12311         int has_book_hit = 0;
12312
12313         if( (index % 2) != side ) {
12314             index++;
12315         }
12316
12317         while( index < forwardMostMove ) {
12318             /* Check to see if engine is in book */
12319             int depth = pvInfoList[index].depth;
12320             int score = pvInfoList[index].score;
12321             int in_book = 0;
12322
12323             if( depth <= 2 ) {
12324                 in_book = 1;
12325             }
12326             else if( score == 0 && depth == 63 ) {
12327                 in_book = 1; /* Zappa */
12328             }
12329             else if( score == 2 && depth == 99 ) {
12330                 in_book = 1; /* Abrok */
12331             }
12332
12333             has_book_hit += in_book;
12334
12335             if( ! in_book ) {
12336                 result = index;
12337
12338                 break;
12339             }
12340
12341             index += 2;
12342         }
12343     }
12344
12345     return result;
12346 }
12347
12348 void
12349 GetOutOfBookInfo (char * buf)
12350 {
12351     int oob[2];
12352     int i;
12353     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12354
12355     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12356     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12357
12358     *buf = '\0';
12359
12360     if( oob[0] >= 0 || oob[1] >= 0 ) {
12361         for( i=0; i<2; i++ ) {
12362             int idx = oob[i];
12363
12364             if( idx >= 0 ) {
12365                 if( i > 0 && oob[0] >= 0 ) {
12366                     strcat( buf, "   " );
12367                 }
12368
12369                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12370                 sprintf( buf+strlen(buf), "%s%.2f",
12371                     pvInfoList[idx].score >= 0 ? "+" : "",
12372                     pvInfoList[idx].score / 100.0 );
12373             }
12374         }
12375     }
12376 }
12377
12378 /* Save game in PGN style and close the file */
12379 int
12380 SaveGamePGN (FILE *f)
12381 {
12382     int i, offset, linelen, newblock;
12383     time_t tm;
12384 //    char *movetext;
12385     char numtext[32];
12386     int movelen, numlen, blank;
12387     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12388
12389     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12390
12391     tm = time((time_t *) NULL);
12392
12393     PrintPGNTags(f, &gameInfo);
12394
12395     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12396
12397     if (backwardMostMove > 0 || startedFromSetupPosition) {
12398         char *fen = PositionToFEN(backwardMostMove, NULL);
12399         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12400         fprintf(f, "\n{--------------\n");
12401         PrintPosition(f, backwardMostMove);
12402         fprintf(f, "--------------}\n");
12403         free(fen);
12404     }
12405     else {
12406         /* [AS] Out of book annotation */
12407         if( appData.saveOutOfBookInfo ) {
12408             char buf[64];
12409
12410             GetOutOfBookInfo( buf );
12411
12412             if( buf[0] != '\0' ) {
12413                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12414             }
12415         }
12416
12417         fprintf(f, "\n");
12418     }
12419
12420     i = backwardMostMove;
12421     linelen = 0;
12422     newblock = TRUE;
12423
12424     while (i < forwardMostMove) {
12425         /* Print comments preceding this move */
12426         if (commentList[i] != NULL) {
12427             if (linelen > 0) fprintf(f, "\n");
12428             fprintf(f, "%s", commentList[i]);
12429             linelen = 0;
12430             newblock = TRUE;
12431         }
12432
12433         /* Format move number */
12434         if ((i % 2) == 0)
12435           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12436         else
12437           if (newblock)
12438             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12439           else
12440             numtext[0] = NULLCHAR;
12441
12442         numlen = strlen(numtext);
12443         newblock = FALSE;
12444
12445         /* Print move number */
12446         blank = linelen > 0 && numlen > 0;
12447         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12448             fprintf(f, "\n");
12449             linelen = 0;
12450             blank = 0;
12451         }
12452         if (blank) {
12453             fprintf(f, " ");
12454             linelen++;
12455         }
12456         fprintf(f, "%s", numtext);
12457         linelen += numlen;
12458
12459         /* Get move */
12460         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12461         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12462
12463         /* Print move */
12464         blank = linelen > 0 && movelen > 0;
12465         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12466             fprintf(f, "\n");
12467             linelen = 0;
12468             blank = 0;
12469         }
12470         if (blank) {
12471             fprintf(f, " ");
12472             linelen++;
12473         }
12474         fprintf(f, "%s", move_buffer);
12475         linelen += movelen;
12476
12477         /* [AS] Add PV info if present */
12478         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12479             /* [HGM] add time */
12480             char buf[MSG_SIZ]; int seconds;
12481
12482             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12483
12484             if( seconds <= 0)
12485               buf[0] = 0;
12486             else
12487               if( seconds < 30 )
12488                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12489               else
12490                 {
12491                   seconds = (seconds + 4)/10; // round to full seconds
12492                   if( seconds < 60 )
12493                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12494                   else
12495                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12496                 }
12497
12498             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12499                       pvInfoList[i].score >= 0 ? "+" : "",
12500                       pvInfoList[i].score / 100.0,
12501                       pvInfoList[i].depth,
12502                       buf );
12503
12504             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12505
12506             /* Print score/depth */
12507             blank = linelen > 0 && movelen > 0;
12508             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12509                 fprintf(f, "\n");
12510                 linelen = 0;
12511                 blank = 0;
12512             }
12513             if (blank) {
12514                 fprintf(f, " ");
12515                 linelen++;
12516             }
12517             fprintf(f, "%s", move_buffer);
12518             linelen += movelen;
12519         }
12520
12521         i++;
12522     }
12523
12524     /* Start a new line */
12525     if (linelen > 0) fprintf(f, "\n");
12526
12527     /* Print comments after last move */
12528     if (commentList[i] != NULL) {
12529         fprintf(f, "%s\n", commentList[i]);
12530     }
12531
12532     /* Print result */
12533     if (gameInfo.resultDetails != NULL &&
12534         gameInfo.resultDetails[0] != NULLCHAR) {
12535         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12536                 PGNResult(gameInfo.result));
12537     } else {
12538         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12539     }
12540
12541     fclose(f);
12542     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12543     return TRUE;
12544 }
12545
12546 /* Save game in old style and close the file */
12547 int
12548 SaveGameOldStyle (FILE *f)
12549 {
12550     int i, offset;
12551     time_t tm;
12552
12553     tm = time((time_t *) NULL);
12554
12555     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12556     PrintOpponents(f);
12557
12558     if (backwardMostMove > 0 || startedFromSetupPosition) {
12559         fprintf(f, "\n[--------------\n");
12560         PrintPosition(f, backwardMostMove);
12561         fprintf(f, "--------------]\n");
12562     } else {
12563         fprintf(f, "\n");
12564     }
12565
12566     i = backwardMostMove;
12567     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12568
12569     while (i < forwardMostMove) {
12570         if (commentList[i] != NULL) {
12571             fprintf(f, "[%s]\n", commentList[i]);
12572         }
12573
12574         if ((i % 2) == 1) {
12575             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12576             i++;
12577         } else {
12578             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12579             i++;
12580             if (commentList[i] != NULL) {
12581                 fprintf(f, "\n");
12582                 continue;
12583             }
12584             if (i >= forwardMostMove) {
12585                 fprintf(f, "\n");
12586                 break;
12587             }
12588             fprintf(f, "%s\n", parseList[i]);
12589             i++;
12590         }
12591     }
12592
12593     if (commentList[i] != NULL) {
12594         fprintf(f, "[%s]\n", commentList[i]);
12595     }
12596
12597     /* This isn't really the old style, but it's close enough */
12598     if (gameInfo.resultDetails != NULL &&
12599         gameInfo.resultDetails[0] != NULLCHAR) {
12600         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12601                 gameInfo.resultDetails);
12602     } else {
12603         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12604     }
12605
12606     fclose(f);
12607     return TRUE;
12608 }
12609
12610 /* Save the current game to open file f and close the file */
12611 int
12612 SaveGame (FILE *f, int dummy, char *dummy2)
12613 {
12614     if (gameMode == EditPosition) EditPositionDone(TRUE);
12615     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12616     if (appData.oldSaveStyle)
12617       return SaveGameOldStyle(f);
12618     else
12619       return SaveGamePGN(f);
12620 }
12621
12622 /* Save the current position to the given file */
12623 int
12624 SavePositionToFile (char *filename)
12625 {
12626     FILE *f;
12627     char buf[MSG_SIZ];
12628
12629     if (strcmp(filename, "-") == 0) {
12630         return SavePosition(stdout, 0, NULL);
12631     } else {
12632         f = fopen(filename, "a");
12633         if (f == NULL) {
12634             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12635             DisplayError(buf, errno);
12636             return FALSE;
12637         } else {
12638             safeStrCpy(buf, lastMsg, MSG_SIZ);
12639             DisplayMessage(_("Waiting for access to save file"), "");
12640             flock(fileno(f), LOCK_EX); // [HGM] lock
12641             DisplayMessage(_("Saving position"), "");
12642             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12643             SavePosition(f, 0, NULL);
12644             DisplayMessage(buf, "");
12645             return TRUE;
12646         }
12647     }
12648 }
12649
12650 /* Save the current position to the given open file and close the file */
12651 int
12652 SavePosition (FILE *f, int dummy, char *dummy2)
12653 {
12654     time_t tm;
12655     char *fen;
12656
12657     if (gameMode == EditPosition) EditPositionDone(TRUE);
12658     if (appData.oldSaveStyle) {
12659         tm = time((time_t *) NULL);
12660
12661         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12662         PrintOpponents(f);
12663         fprintf(f, "[--------------\n");
12664         PrintPosition(f, currentMove);
12665         fprintf(f, "--------------]\n");
12666     } else {
12667         fen = PositionToFEN(currentMove, NULL);
12668         fprintf(f, "%s\n", fen);
12669         free(fen);
12670     }
12671     fclose(f);
12672     return TRUE;
12673 }
12674
12675 void
12676 ReloadCmailMsgEvent (int unregister)
12677 {
12678 #if !WIN32
12679     static char *inFilename = NULL;
12680     static char *outFilename;
12681     int i;
12682     struct stat inbuf, outbuf;
12683     int status;
12684
12685     /* Any registered moves are unregistered if unregister is set, */
12686     /* i.e. invoked by the signal handler */
12687     if (unregister) {
12688         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12689             cmailMoveRegistered[i] = FALSE;
12690             if (cmailCommentList[i] != NULL) {
12691                 free(cmailCommentList[i]);
12692                 cmailCommentList[i] = NULL;
12693             }
12694         }
12695         nCmailMovesRegistered = 0;
12696     }
12697
12698     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12699         cmailResult[i] = CMAIL_NOT_RESULT;
12700     }
12701     nCmailResults = 0;
12702
12703     if (inFilename == NULL) {
12704         /* Because the filenames are static they only get malloced once  */
12705         /* and they never get freed                                      */
12706         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12707         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12708
12709         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12710         sprintf(outFilename, "%s.out", appData.cmailGameName);
12711     }
12712
12713     status = stat(outFilename, &outbuf);
12714     if (status < 0) {
12715         cmailMailedMove = FALSE;
12716     } else {
12717         status = stat(inFilename, &inbuf);
12718         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12719     }
12720
12721     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12722        counts the games, notes how each one terminated, etc.
12723
12724        It would be nice to remove this kludge and instead gather all
12725        the information while building the game list.  (And to keep it
12726        in the game list nodes instead of having a bunch of fixed-size
12727        parallel arrays.)  Note this will require getting each game's
12728        termination from the PGN tags, as the game list builder does
12729        not process the game moves.  --mann
12730        */
12731     cmailMsgLoaded = TRUE;
12732     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12733
12734     /* Load first game in the file or popup game menu */
12735     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12736
12737 #endif /* !WIN32 */
12738     return;
12739 }
12740
12741 int
12742 RegisterMove ()
12743 {
12744     FILE *f;
12745     char string[MSG_SIZ];
12746
12747     if (   cmailMailedMove
12748         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12749         return TRUE;            /* Allow free viewing  */
12750     }
12751
12752     /* Unregister move to ensure that we don't leave RegisterMove        */
12753     /* with the move registered when the conditions for registering no   */
12754     /* longer hold                                                       */
12755     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12756         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12757         nCmailMovesRegistered --;
12758
12759         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12760           {
12761               free(cmailCommentList[lastLoadGameNumber - 1]);
12762               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12763           }
12764     }
12765
12766     if (cmailOldMove == -1) {
12767         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12768         return FALSE;
12769     }
12770
12771     if (currentMove > cmailOldMove + 1) {
12772         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12773         return FALSE;
12774     }
12775
12776     if (currentMove < cmailOldMove) {
12777         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12778         return FALSE;
12779     }
12780
12781     if (forwardMostMove > currentMove) {
12782         /* Silently truncate extra moves */
12783         TruncateGame();
12784     }
12785
12786     if (   (currentMove == cmailOldMove + 1)
12787         || (   (currentMove == cmailOldMove)
12788             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12789                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12790         if (gameInfo.result != GameUnfinished) {
12791             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12792         }
12793
12794         if (commentList[currentMove] != NULL) {
12795             cmailCommentList[lastLoadGameNumber - 1]
12796               = StrSave(commentList[currentMove]);
12797         }
12798         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12799
12800         if (appData.debugMode)
12801           fprintf(debugFP, "Saving %s for game %d\n",
12802                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12803
12804         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12805
12806         f = fopen(string, "w");
12807         if (appData.oldSaveStyle) {
12808             SaveGameOldStyle(f); /* also closes the file */
12809
12810             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12811             f = fopen(string, "w");
12812             SavePosition(f, 0, NULL); /* also closes the file */
12813         } else {
12814             fprintf(f, "{--------------\n");
12815             PrintPosition(f, currentMove);
12816             fprintf(f, "--------------}\n\n");
12817
12818             SaveGame(f, 0, NULL); /* also closes the file*/
12819         }
12820
12821         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12822         nCmailMovesRegistered ++;
12823     } else if (nCmailGames == 1) {
12824         DisplayError(_("You have not made a move yet"), 0);
12825         return FALSE;
12826     }
12827
12828     return TRUE;
12829 }
12830
12831 void
12832 MailMoveEvent ()
12833 {
12834 #if !WIN32
12835     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12836     FILE *commandOutput;
12837     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12838     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12839     int nBuffers;
12840     int i;
12841     int archived;
12842     char *arcDir;
12843
12844     if (! cmailMsgLoaded) {
12845         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12846         return;
12847     }
12848
12849     if (nCmailGames == nCmailResults) {
12850         DisplayError(_("No unfinished games"), 0);
12851         return;
12852     }
12853
12854 #if CMAIL_PROHIBIT_REMAIL
12855     if (cmailMailedMove) {
12856       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);
12857         DisplayError(msg, 0);
12858         return;
12859     }
12860 #endif
12861
12862     if (! (cmailMailedMove || RegisterMove())) return;
12863
12864     if (   cmailMailedMove
12865         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12866       snprintf(string, MSG_SIZ, partCommandString,
12867                appData.debugMode ? " -v" : "", appData.cmailGameName);
12868         commandOutput = popen(string, "r");
12869
12870         if (commandOutput == NULL) {
12871             DisplayError(_("Failed to invoke cmail"), 0);
12872         } else {
12873             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12874                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12875             }
12876             if (nBuffers > 1) {
12877                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12878                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12879                 nBytes = MSG_SIZ - 1;
12880             } else {
12881                 (void) memcpy(msg, buffer, nBytes);
12882             }
12883             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12884
12885             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12886                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12887
12888                 archived = TRUE;
12889                 for (i = 0; i < nCmailGames; i ++) {
12890                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12891                         archived = FALSE;
12892                     }
12893                 }
12894                 if (   archived
12895                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12896                         != NULL)) {
12897                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12898                            arcDir,
12899                            appData.cmailGameName,
12900                            gameInfo.date);
12901                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12902                     cmailMsgLoaded = FALSE;
12903                 }
12904             }
12905
12906             DisplayInformation(msg);
12907             pclose(commandOutput);
12908         }
12909     } else {
12910         if ((*cmailMsg) != '\0') {
12911             DisplayInformation(cmailMsg);
12912         }
12913     }
12914
12915     return;
12916 #endif /* !WIN32 */
12917 }
12918
12919 char *
12920 CmailMsg ()
12921 {
12922 #if WIN32
12923     return NULL;
12924 #else
12925     int  prependComma = 0;
12926     char number[5];
12927     char string[MSG_SIZ];       /* Space for game-list */
12928     int  i;
12929
12930     if (!cmailMsgLoaded) return "";
12931
12932     if (cmailMailedMove) {
12933       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12934     } else {
12935         /* Create a list of games left */
12936       snprintf(string, MSG_SIZ, "[");
12937         for (i = 0; i < nCmailGames; i ++) {
12938             if (! (   cmailMoveRegistered[i]
12939                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12940                 if (prependComma) {
12941                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12942                 } else {
12943                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12944                     prependComma = 1;
12945                 }
12946
12947                 strcat(string, number);
12948             }
12949         }
12950         strcat(string, "]");
12951
12952         if (nCmailMovesRegistered + nCmailResults == 0) {
12953             switch (nCmailGames) {
12954               case 1:
12955                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12956                 break;
12957
12958               case 2:
12959                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12960                 break;
12961
12962               default:
12963                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12964                          nCmailGames);
12965                 break;
12966             }
12967         } else {
12968             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12969               case 1:
12970                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12971                          string);
12972                 break;
12973
12974               case 0:
12975                 if (nCmailResults == nCmailGames) {
12976                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12977                 } else {
12978                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12979                 }
12980                 break;
12981
12982               default:
12983                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12984                          string);
12985             }
12986         }
12987     }
12988     return cmailMsg;
12989 #endif /* WIN32 */
12990 }
12991
12992 void
12993 ResetGameEvent ()
12994 {
12995     if (gameMode == Training)
12996       SetTrainingModeOff();
12997
12998     Reset(TRUE, TRUE);
12999     cmailMsgLoaded = FALSE;
13000     if (appData.icsActive) {
13001       SendToICS(ics_prefix);
13002       SendToICS("refresh\n");
13003     }
13004 }
13005
13006 void
13007 ExitEvent (int status)
13008 {
13009     exiting++;
13010     if (exiting > 2) {
13011       /* Give up on clean exit */
13012       exit(status);
13013     }
13014     if (exiting > 1) {
13015       /* Keep trying for clean exit */
13016       return;
13017     }
13018
13019     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13020
13021     if (telnetISR != NULL) {
13022       RemoveInputSource(telnetISR);
13023     }
13024     if (icsPR != NoProc) {
13025       DestroyChildProcess(icsPR, TRUE);
13026     }
13027
13028     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13029     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13030
13031     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13032     /* make sure this other one finishes before killing it!                  */
13033     if(endingGame) { int count = 0;
13034         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13035         while(endingGame && count++ < 10) DoSleep(1);
13036         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13037     }
13038
13039     /* Kill off chess programs */
13040     if (first.pr != NoProc) {
13041         ExitAnalyzeMode();
13042
13043         DoSleep( appData.delayBeforeQuit );
13044         SendToProgram("quit\n", &first);
13045         DoSleep( appData.delayAfterQuit );
13046         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13047     }
13048     if (second.pr != NoProc) {
13049         DoSleep( appData.delayBeforeQuit );
13050         SendToProgram("quit\n", &second);
13051         DoSleep( appData.delayAfterQuit );
13052         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13053     }
13054     if (first.isr != NULL) {
13055         RemoveInputSource(first.isr);
13056     }
13057     if (second.isr != NULL) {
13058         RemoveInputSource(second.isr);
13059     }
13060
13061     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13062     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13063
13064     ShutDownFrontEnd();
13065     exit(status);
13066 }
13067
13068 void
13069 PauseEvent ()
13070 {
13071     if (appData.debugMode)
13072         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13073     if (pausing) {
13074         pausing = FALSE;
13075         ModeHighlight();
13076         if (gameMode == MachinePlaysWhite ||
13077             gameMode == MachinePlaysBlack) {
13078             StartClocks();
13079         } else {
13080             DisplayBothClocks();
13081         }
13082         if (gameMode == PlayFromGameFile) {
13083             if (appData.timeDelay >= 0)
13084                 AutoPlayGameLoop();
13085         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13086             Reset(FALSE, TRUE);
13087             SendToICS(ics_prefix);
13088             SendToICS("refresh\n");
13089         } else if (currentMove < forwardMostMove) {
13090             ForwardInner(forwardMostMove);
13091         }
13092         pauseExamInvalid = FALSE;
13093     } else {
13094         switch (gameMode) {
13095           default:
13096             return;
13097           case IcsExamining:
13098             pauseExamForwardMostMove = forwardMostMove;
13099             pauseExamInvalid = FALSE;
13100             /* fall through */
13101           case IcsObserving:
13102           case IcsPlayingWhite:
13103           case IcsPlayingBlack:
13104             pausing = TRUE;
13105             ModeHighlight();
13106             return;
13107           case PlayFromGameFile:
13108             (void) StopLoadGameTimer();
13109             pausing = TRUE;
13110             ModeHighlight();
13111             break;
13112           case BeginningOfGame:
13113             if (appData.icsActive) return;
13114             /* else fall through */
13115           case MachinePlaysWhite:
13116           case MachinePlaysBlack:
13117           case TwoMachinesPlay:
13118             if (forwardMostMove == 0)
13119               return;           /* don't pause if no one has moved */
13120             if ((gameMode == MachinePlaysWhite &&
13121                  !WhiteOnMove(forwardMostMove)) ||
13122                 (gameMode == MachinePlaysBlack &&
13123                  WhiteOnMove(forwardMostMove))) {
13124                 StopClocks();
13125             }
13126             pausing = TRUE;
13127             ModeHighlight();
13128             break;
13129         }
13130     }
13131 }
13132
13133 void
13134 EditCommentEvent ()
13135 {
13136     char title[MSG_SIZ];
13137
13138     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13139       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13140     } else {
13141       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13142                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13143                parseList[currentMove - 1]);
13144     }
13145
13146     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13147 }
13148
13149
13150 void
13151 EditTagsEvent ()
13152 {
13153     char *tags = PGNTags(&gameInfo);
13154     bookUp = FALSE;
13155     EditTagsPopUp(tags, NULL);
13156     free(tags);
13157 }
13158
13159 void
13160 AnalyzeModeEvent ()
13161 {
13162     if (appData.noChessProgram || gameMode == AnalyzeMode)
13163       return;
13164
13165     if (gameMode != AnalyzeFile) {
13166         if (!appData.icsEngineAnalyze) {
13167                EditGameEvent();
13168                if (gameMode != EditGame) return;
13169         }
13170         ResurrectChessProgram();
13171         SendToProgram("analyze\n", &first);
13172         first.analyzing = TRUE;
13173         /*first.maybeThinking = TRUE;*/
13174         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13175         EngineOutputPopUp();
13176     }
13177     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13178     pausing = FALSE;
13179     ModeHighlight();
13180     SetGameInfo();
13181
13182     StartAnalysisClock();
13183     GetTimeMark(&lastNodeCountTime);
13184     lastNodeCount = 0;
13185 }
13186
13187 void
13188 AnalyzeFileEvent ()
13189 {
13190     if (appData.noChessProgram || gameMode == AnalyzeFile)
13191       return;
13192
13193     if (gameMode != AnalyzeMode) {
13194         EditGameEvent();
13195         if (gameMode != EditGame) return;
13196         ResurrectChessProgram();
13197         SendToProgram("analyze\n", &first);
13198         first.analyzing = TRUE;
13199         /*first.maybeThinking = TRUE;*/
13200         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13201         EngineOutputPopUp();
13202     }
13203     gameMode = AnalyzeFile;
13204     pausing = FALSE;
13205     ModeHighlight();
13206     SetGameInfo();
13207
13208     StartAnalysisClock();
13209     GetTimeMark(&lastNodeCountTime);
13210     lastNodeCount = 0;
13211     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13212 }
13213
13214 void
13215 MachineWhiteEvent ()
13216 {
13217     char buf[MSG_SIZ];
13218     char *bookHit = NULL;
13219
13220     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13221       return;
13222
13223
13224     if (gameMode == PlayFromGameFile ||
13225         gameMode == TwoMachinesPlay  ||
13226         gameMode == Training         ||
13227         gameMode == AnalyzeMode      ||
13228         gameMode == EndOfGame)
13229         EditGameEvent();
13230
13231     if (gameMode == EditPosition)
13232         EditPositionDone(TRUE);
13233
13234     if (!WhiteOnMove(currentMove)) {
13235         DisplayError(_("It is not White's turn"), 0);
13236         return;
13237     }
13238
13239     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13240       ExitAnalyzeMode();
13241
13242     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13243         gameMode == AnalyzeFile)
13244         TruncateGame();
13245
13246     ResurrectChessProgram();    /* in case it isn't running */
13247     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13248         gameMode = MachinePlaysWhite;
13249         ResetClocks();
13250     } else
13251     gameMode = MachinePlaysWhite;
13252     pausing = FALSE;
13253     ModeHighlight();
13254     SetGameInfo();
13255     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13256     DisplayTitle(buf);
13257     if (first.sendName) {
13258       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13259       SendToProgram(buf, &first);
13260     }
13261     if (first.sendTime) {
13262       if (first.useColors) {
13263         SendToProgram("black\n", &first); /*gnu kludge*/
13264       }
13265       SendTimeRemaining(&first, TRUE);
13266     }
13267     if (first.useColors) {
13268       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13269     }
13270     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13271     SetMachineThinkingEnables();
13272     first.maybeThinking = TRUE;
13273     StartClocks();
13274     firstMove = FALSE;
13275
13276     if (appData.autoFlipView && !flipView) {
13277       flipView = !flipView;
13278       DrawPosition(FALSE, NULL);
13279       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13280     }
13281
13282     if(bookHit) { // [HGM] book: simulate book reply
13283         static char bookMove[MSG_SIZ]; // a bit generous?
13284
13285         programStats.nodes = programStats.depth = programStats.time =
13286         programStats.score = programStats.got_only_move = 0;
13287         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13288
13289         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13290         strcat(bookMove, bookHit);
13291         HandleMachineMove(bookMove, &first);
13292     }
13293 }
13294
13295 void
13296 MachineBlackEvent ()
13297 {
13298   char buf[MSG_SIZ];
13299   char *bookHit = NULL;
13300
13301     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13302         return;
13303
13304
13305     if (gameMode == PlayFromGameFile ||
13306         gameMode == TwoMachinesPlay  ||
13307         gameMode == Training         ||
13308         gameMode == AnalyzeMode      ||
13309         gameMode == EndOfGame)
13310         EditGameEvent();
13311
13312     if (gameMode == EditPosition)
13313         EditPositionDone(TRUE);
13314
13315     if (WhiteOnMove(currentMove)) {
13316         DisplayError(_("It is not Black's turn"), 0);
13317         return;
13318     }
13319
13320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13321       ExitAnalyzeMode();
13322
13323     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13324         gameMode == AnalyzeFile)
13325         TruncateGame();
13326
13327     ResurrectChessProgram();    /* in case it isn't running */
13328     gameMode = MachinePlaysBlack;
13329     pausing = FALSE;
13330     ModeHighlight();
13331     SetGameInfo();
13332     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13333     DisplayTitle(buf);
13334     if (first.sendName) {
13335       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13336       SendToProgram(buf, &first);
13337     }
13338     if (first.sendTime) {
13339       if (first.useColors) {
13340         SendToProgram("white\n", &first); /*gnu kludge*/
13341       }
13342       SendTimeRemaining(&first, FALSE);
13343     }
13344     if (first.useColors) {
13345       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13346     }
13347     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13348     SetMachineThinkingEnables();
13349     first.maybeThinking = TRUE;
13350     StartClocks();
13351
13352     if (appData.autoFlipView && flipView) {
13353       flipView = !flipView;
13354       DrawPosition(FALSE, NULL);
13355       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13356     }
13357     if(bookHit) { // [HGM] book: simulate book reply
13358         static char bookMove[MSG_SIZ]; // a bit generous?
13359
13360         programStats.nodes = programStats.depth = programStats.time =
13361         programStats.score = programStats.got_only_move = 0;
13362         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13363
13364         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13365         strcat(bookMove, bookHit);
13366         HandleMachineMove(bookMove, &first);
13367     }
13368 }
13369
13370
13371 void
13372 DisplayTwoMachinesTitle ()
13373 {
13374     char buf[MSG_SIZ];
13375     if (appData.matchGames > 0) {
13376         if(appData.tourneyFile[0]) {
13377           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13378                    gameInfo.white, _("vs."), gameInfo.black,
13379                    nextGame+1, appData.matchGames+1,
13380                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13381         } else 
13382         if (first.twoMachinesColor[0] == 'w') {
13383           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13384                    gameInfo.white, _("vs."),  gameInfo.black,
13385                    first.matchWins, second.matchWins,
13386                    matchGame - 1 - (first.matchWins + second.matchWins));
13387         } else {
13388           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13389                    gameInfo.white, _("vs."), gameInfo.black,
13390                    second.matchWins, first.matchWins,
13391                    matchGame - 1 - (first.matchWins + second.matchWins));
13392         }
13393     } else {
13394       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13395     }
13396     DisplayTitle(buf);
13397 }
13398
13399 void
13400 SettingsMenuIfReady ()
13401 {
13402   if (second.lastPing != second.lastPong) {
13403     DisplayMessage("", _("Waiting for second chess program"));
13404     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13405     return;
13406   }
13407   ThawUI();
13408   DisplayMessage("", "");
13409   SettingsPopUp(&second);
13410 }
13411
13412 int
13413 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13414 {
13415     char buf[MSG_SIZ];
13416     if (cps->pr == NoProc) {
13417         StartChessProgram(cps);
13418         if (cps->protocolVersion == 1) {
13419           retry();
13420         } else {
13421           /* kludge: allow timeout for initial "feature" command */
13422           FreezeUI();
13423           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13424           DisplayMessage("", buf);
13425           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13426         }
13427         return 1;
13428     }
13429     return 0;
13430 }
13431
13432 void
13433 TwoMachinesEvent P((void))
13434 {
13435     int i;
13436     char buf[MSG_SIZ];
13437     ChessProgramState *onmove;
13438     char *bookHit = NULL;
13439     static int stalling = 0;
13440     TimeMark now;
13441     long wait;
13442
13443     if (appData.noChessProgram) return;
13444
13445     switch (gameMode) {
13446       case TwoMachinesPlay:
13447         return;
13448       case MachinePlaysWhite:
13449       case MachinePlaysBlack:
13450         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13451             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13452             return;
13453         }
13454         /* fall through */
13455       case BeginningOfGame:
13456       case PlayFromGameFile:
13457       case EndOfGame:
13458         EditGameEvent();
13459         if (gameMode != EditGame) return;
13460         break;
13461       case EditPosition:
13462         EditPositionDone(TRUE);
13463         break;
13464       case AnalyzeMode:
13465       case AnalyzeFile:
13466         ExitAnalyzeMode();
13467         break;
13468       case EditGame:
13469       default:
13470         break;
13471     }
13472
13473 //    forwardMostMove = currentMove;
13474     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13475
13476     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13477
13478     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13479     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13480       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13481       return;
13482     }
13483     if(!stalling) {
13484       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13485       SendToProgram("force\n", &second);
13486       stalling = 1;
13487       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13488       return;
13489     }
13490     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13491     if(appData.matchPause>10000 || appData.matchPause<10)
13492                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13493     wait = SubtractTimeMarks(&now, &pauseStart);
13494     if(wait < appData.matchPause) {
13495         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13496         return;
13497     }
13498     // we are now committed to starting the game
13499     stalling = 0;
13500     DisplayMessage("", "");
13501     if (startedFromSetupPosition) {
13502         SendBoard(&second, backwardMostMove);
13503     if (appData.debugMode) {
13504         fprintf(debugFP, "Two Machines\n");
13505     }
13506     }
13507     for (i = backwardMostMove; i < forwardMostMove; i++) {
13508         SendMoveToProgram(i, &second);
13509     }
13510
13511     gameMode = TwoMachinesPlay;
13512     pausing = FALSE;
13513     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13514     SetGameInfo();
13515     DisplayTwoMachinesTitle();
13516     firstMove = TRUE;
13517     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13518         onmove = &first;
13519     } else {
13520         onmove = &second;
13521     }
13522     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13523     SendToProgram(first.computerString, &first);
13524     if (first.sendName) {
13525       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13526       SendToProgram(buf, &first);
13527     }
13528     SendToProgram(second.computerString, &second);
13529     if (second.sendName) {
13530       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13531       SendToProgram(buf, &second);
13532     }
13533
13534     ResetClocks();
13535     if (!first.sendTime || !second.sendTime) {
13536         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13537         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13538     }
13539     if (onmove->sendTime) {
13540       if (onmove->useColors) {
13541         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13542       }
13543       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13544     }
13545     if (onmove->useColors) {
13546       SendToProgram(onmove->twoMachinesColor, onmove);
13547     }
13548     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13549 //    SendToProgram("go\n", onmove);
13550     onmove->maybeThinking = TRUE;
13551     SetMachineThinkingEnables();
13552
13553     StartClocks();
13554
13555     if(bookHit) { // [HGM] book: simulate book reply
13556         static char bookMove[MSG_SIZ]; // a bit generous?
13557
13558         programStats.nodes = programStats.depth = programStats.time =
13559         programStats.score = programStats.got_only_move = 0;
13560         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13561
13562         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13563         strcat(bookMove, bookHit);
13564         savedMessage = bookMove; // args for deferred call
13565         savedState = onmove;
13566         ScheduleDelayedEvent(DeferredBookMove, 1);
13567     }
13568 }
13569
13570 void
13571 TrainingEvent ()
13572 {
13573     if (gameMode == Training) {
13574       SetTrainingModeOff();
13575       gameMode = PlayFromGameFile;
13576       DisplayMessage("", _("Training mode off"));
13577     } else {
13578       gameMode = Training;
13579       animateTraining = appData.animate;
13580
13581       /* make sure we are not already at the end of the game */
13582       if (currentMove < forwardMostMove) {
13583         SetTrainingModeOn();
13584         DisplayMessage("", _("Training mode on"));
13585       } else {
13586         gameMode = PlayFromGameFile;
13587         DisplayError(_("Already at end of game"), 0);
13588       }
13589     }
13590     ModeHighlight();
13591 }
13592
13593 void
13594 IcsClientEvent ()
13595 {
13596     if (!appData.icsActive) return;
13597     switch (gameMode) {
13598       case IcsPlayingWhite:
13599       case IcsPlayingBlack:
13600       case IcsObserving:
13601       case IcsIdle:
13602       case BeginningOfGame:
13603       case IcsExamining:
13604         return;
13605
13606       case EditGame:
13607         break;
13608
13609       case EditPosition:
13610         EditPositionDone(TRUE);
13611         break;
13612
13613       case AnalyzeMode:
13614       case AnalyzeFile:
13615         ExitAnalyzeMode();
13616         break;
13617
13618       default:
13619         EditGameEvent();
13620         break;
13621     }
13622
13623     gameMode = IcsIdle;
13624     ModeHighlight();
13625     return;
13626 }
13627
13628 void
13629 EditGameEvent ()
13630 {
13631     int i;
13632
13633     switch (gameMode) {
13634       case Training:
13635         SetTrainingModeOff();
13636         break;
13637       case MachinePlaysWhite:
13638       case MachinePlaysBlack:
13639       case BeginningOfGame:
13640         SendToProgram("force\n", &first);
13641         SetUserThinkingEnables();
13642         break;
13643       case PlayFromGameFile:
13644         (void) StopLoadGameTimer();
13645         if (gameFileFP != NULL) {
13646             gameFileFP = NULL;
13647         }
13648         break;
13649       case EditPosition:
13650         EditPositionDone(TRUE);
13651         break;
13652       case AnalyzeMode:
13653       case AnalyzeFile:
13654         ExitAnalyzeMode();
13655         SendToProgram("force\n", &first);
13656         break;
13657       case TwoMachinesPlay:
13658         GameEnds(EndOfFile, NULL, GE_PLAYER);
13659         ResurrectChessProgram();
13660         SetUserThinkingEnables();
13661         break;
13662       case EndOfGame:
13663         ResurrectChessProgram();
13664         break;
13665       case IcsPlayingBlack:
13666       case IcsPlayingWhite:
13667         DisplayError(_("Warning: You are still playing a game"), 0);
13668         break;
13669       case IcsObserving:
13670         DisplayError(_("Warning: You are still observing a game"), 0);
13671         break;
13672       case IcsExamining:
13673         DisplayError(_("Warning: You are still examining a game"), 0);
13674         break;
13675       case IcsIdle:
13676         break;
13677       case EditGame:
13678       default:
13679         return;
13680     }
13681
13682     pausing = FALSE;
13683     StopClocks();
13684     first.offeredDraw = second.offeredDraw = 0;
13685
13686     if (gameMode == PlayFromGameFile) {
13687         whiteTimeRemaining = timeRemaining[0][currentMove];
13688         blackTimeRemaining = timeRemaining[1][currentMove];
13689         DisplayTitle("");
13690     }
13691
13692     if (gameMode == MachinePlaysWhite ||
13693         gameMode == MachinePlaysBlack ||
13694         gameMode == TwoMachinesPlay ||
13695         gameMode == EndOfGame) {
13696         i = forwardMostMove;
13697         while (i > currentMove) {
13698             SendToProgram("undo\n", &first);
13699             i--;
13700         }
13701         if(!adjustedClock) {
13702         whiteTimeRemaining = timeRemaining[0][currentMove];
13703         blackTimeRemaining = timeRemaining[1][currentMove];
13704         DisplayBothClocks();
13705         }
13706         if (whiteFlag || blackFlag) {
13707             whiteFlag = blackFlag = 0;
13708         }
13709         DisplayTitle("");
13710     }
13711
13712     gameMode = EditGame;
13713     ModeHighlight();
13714     SetGameInfo();
13715 }
13716
13717
13718 void
13719 EditPositionEvent ()
13720 {
13721     if (gameMode == EditPosition) {
13722         EditGameEvent();
13723         return;
13724     }
13725
13726     EditGameEvent();
13727     if (gameMode != EditGame) return;
13728
13729     gameMode = EditPosition;
13730     ModeHighlight();
13731     SetGameInfo();
13732     if (currentMove > 0)
13733       CopyBoard(boards[0], boards[currentMove]);
13734
13735     blackPlaysFirst = !WhiteOnMove(currentMove);
13736     ResetClocks();
13737     currentMove = forwardMostMove = backwardMostMove = 0;
13738     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13739     DisplayMove(-1);
13740     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13741 }
13742
13743 void
13744 ExitAnalyzeMode ()
13745 {
13746     /* [DM] icsEngineAnalyze - possible call from other functions */
13747     if (appData.icsEngineAnalyze) {
13748         appData.icsEngineAnalyze = FALSE;
13749
13750         DisplayMessage("",_("Close ICS engine analyze..."));
13751     }
13752     if (first.analysisSupport && first.analyzing) {
13753       SendToProgram("exit\n", &first);
13754       first.analyzing = FALSE;
13755     }
13756     thinkOutput[0] = NULLCHAR;
13757 }
13758
13759 void
13760 EditPositionDone (Boolean fakeRights)
13761 {
13762     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13763
13764     startedFromSetupPosition = TRUE;
13765     InitChessProgram(&first, FALSE);
13766     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13767       boards[0][EP_STATUS] = EP_NONE;
13768       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13769     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13770         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13771         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13772       } else boards[0][CASTLING][2] = NoRights;
13773     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13774         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13775         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13776       } else boards[0][CASTLING][5] = NoRights;
13777     }
13778     SendToProgram("force\n", &first);
13779     if (blackPlaysFirst) {
13780         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13781         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13782         currentMove = forwardMostMove = backwardMostMove = 1;
13783         CopyBoard(boards[1], boards[0]);
13784     } else {
13785         currentMove = forwardMostMove = backwardMostMove = 0;
13786     }
13787     SendBoard(&first, forwardMostMove);
13788     if (appData.debugMode) {
13789         fprintf(debugFP, "EditPosDone\n");
13790     }
13791     DisplayTitle("");
13792     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13793     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13794     gameMode = EditGame;
13795     ModeHighlight();
13796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13797     ClearHighlights(); /* [AS] */
13798 }
13799
13800 /* Pause for `ms' milliseconds */
13801 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13802 void
13803 TimeDelay (long ms)
13804 {
13805     TimeMark m1, m2;
13806
13807     GetTimeMark(&m1);
13808     do {
13809         GetTimeMark(&m2);
13810     } while (SubtractTimeMarks(&m2, &m1) < ms);
13811 }
13812
13813 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13814 void
13815 SendMultiLineToICS (char *buf)
13816 {
13817     char temp[MSG_SIZ+1], *p;
13818     int len;
13819
13820     len = strlen(buf);
13821     if (len > MSG_SIZ)
13822       len = MSG_SIZ;
13823
13824     strncpy(temp, buf, len);
13825     temp[len] = 0;
13826
13827     p = temp;
13828     while (*p) {
13829         if (*p == '\n' || *p == '\r')
13830           *p = ' ';
13831         ++p;
13832     }
13833
13834     strcat(temp, "\n");
13835     SendToICS(temp);
13836     SendToPlayer(temp, strlen(temp));
13837 }
13838
13839 void
13840 SetWhiteToPlayEvent ()
13841 {
13842     if (gameMode == EditPosition) {
13843         blackPlaysFirst = FALSE;
13844         DisplayBothClocks();    /* works because currentMove is 0 */
13845     } else if (gameMode == IcsExamining) {
13846         SendToICS(ics_prefix);
13847         SendToICS("tomove white\n");
13848     }
13849 }
13850
13851 void
13852 SetBlackToPlayEvent ()
13853 {
13854     if (gameMode == EditPosition) {
13855         blackPlaysFirst = TRUE;
13856         currentMove = 1;        /* kludge */
13857         DisplayBothClocks();
13858         currentMove = 0;
13859     } else if (gameMode == IcsExamining) {
13860         SendToICS(ics_prefix);
13861         SendToICS("tomove black\n");
13862     }
13863 }
13864
13865 void
13866 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13867 {
13868     char buf[MSG_SIZ];
13869     ChessSquare piece = boards[0][y][x];
13870
13871     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13872
13873     switch (selection) {
13874       case ClearBoard:
13875         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13876             SendToICS(ics_prefix);
13877             SendToICS("bsetup clear\n");
13878         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13879             SendToICS(ics_prefix);
13880             SendToICS("clearboard\n");
13881         } else {
13882             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13883                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13884                 for (y = 0; y < BOARD_HEIGHT; y++) {
13885                     if (gameMode == IcsExamining) {
13886                         if (boards[currentMove][y][x] != EmptySquare) {
13887                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13888                                     AAA + x, ONE + y);
13889                             SendToICS(buf);
13890                         }
13891                     } else {
13892                         boards[0][y][x] = p;
13893                     }
13894                 }
13895             }
13896         }
13897         if (gameMode == EditPosition) {
13898             DrawPosition(FALSE, boards[0]);
13899         }
13900         break;
13901
13902       case WhitePlay:
13903         SetWhiteToPlayEvent();
13904         break;
13905
13906       case BlackPlay:
13907         SetBlackToPlayEvent();
13908         break;
13909
13910       case EmptySquare:
13911         if (gameMode == IcsExamining) {
13912             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13913             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13914             SendToICS(buf);
13915         } else {
13916             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13917                 if(x == BOARD_LEFT-2) {
13918                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13919                     boards[0][y][1] = 0;
13920                 } else
13921                 if(x == BOARD_RGHT+1) {
13922                     if(y >= gameInfo.holdingsSize) break;
13923                     boards[0][y][BOARD_WIDTH-2] = 0;
13924                 } else break;
13925             }
13926             boards[0][y][x] = EmptySquare;
13927             DrawPosition(FALSE, boards[0]);
13928         }
13929         break;
13930
13931       case PromotePiece:
13932         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13933            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13934             selection = (ChessSquare) (PROMOTED piece);
13935         } else if(piece == EmptySquare) selection = WhiteSilver;
13936         else selection = (ChessSquare)((int)piece - 1);
13937         goto defaultlabel;
13938
13939       case DemotePiece:
13940         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13941            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13942             selection = (ChessSquare) (DEMOTED piece);
13943         } else if(piece == EmptySquare) selection = BlackSilver;
13944         else selection = (ChessSquare)((int)piece + 1);
13945         goto defaultlabel;
13946
13947       case WhiteQueen:
13948       case BlackQueen:
13949         if(gameInfo.variant == VariantShatranj ||
13950            gameInfo.variant == VariantXiangqi  ||
13951            gameInfo.variant == VariantCourier  ||
13952            gameInfo.variant == VariantMakruk     )
13953             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13954         goto defaultlabel;
13955
13956       case WhiteKing:
13957       case BlackKing:
13958         if(gameInfo.variant == VariantXiangqi)
13959             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13960         if(gameInfo.variant == VariantKnightmate)
13961             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13962       default:
13963         defaultlabel:
13964         if (gameMode == IcsExamining) {
13965             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13966             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13967                      PieceToChar(selection), AAA + x, ONE + y);
13968             SendToICS(buf);
13969         } else {
13970             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13971                 int n;
13972                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13973                     n = PieceToNumber(selection - BlackPawn);
13974                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13975                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13976                     boards[0][BOARD_HEIGHT-1-n][1]++;
13977                 } else
13978                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13979                     n = PieceToNumber(selection);
13980                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13981                     boards[0][n][BOARD_WIDTH-1] = selection;
13982                     boards[0][n][BOARD_WIDTH-2]++;
13983                 }
13984             } else
13985             boards[0][y][x] = selection;
13986             DrawPosition(TRUE, boards[0]);
13987             ClearHighlights();
13988             fromX = fromY = -1;
13989         }
13990         break;
13991     }
13992 }
13993
13994
13995 void
13996 DropMenuEvent (ChessSquare selection, int x, int y)
13997 {
13998     ChessMove moveType;
13999
14000     switch (gameMode) {
14001       case IcsPlayingWhite:
14002       case MachinePlaysBlack:
14003         if (!WhiteOnMove(currentMove)) {
14004             DisplayMoveError(_("It is Black's turn"));
14005             return;
14006         }
14007         moveType = WhiteDrop;
14008         break;
14009       case IcsPlayingBlack:
14010       case MachinePlaysWhite:
14011         if (WhiteOnMove(currentMove)) {
14012             DisplayMoveError(_("It is White's turn"));
14013             return;
14014         }
14015         moveType = BlackDrop;
14016         break;
14017       case EditGame:
14018         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14019         break;
14020       default:
14021         return;
14022     }
14023
14024     if (moveType == BlackDrop && selection < BlackPawn) {
14025       selection = (ChessSquare) ((int) selection
14026                                  + (int) BlackPawn - (int) WhitePawn);
14027     }
14028     if (boards[currentMove][y][x] != EmptySquare) {
14029         DisplayMoveError(_("That square is occupied"));
14030         return;
14031     }
14032
14033     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14034 }
14035
14036 void
14037 AcceptEvent ()
14038 {
14039     /* Accept a pending offer of any kind from opponent */
14040
14041     if (appData.icsActive) {
14042         SendToICS(ics_prefix);
14043         SendToICS("accept\n");
14044     } else if (cmailMsgLoaded) {
14045         if (currentMove == cmailOldMove &&
14046             commentList[cmailOldMove] != NULL &&
14047             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14048                    "Black offers a draw" : "White offers a draw")) {
14049             TruncateGame();
14050             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14051             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14052         } else {
14053             DisplayError(_("There is no pending offer on this move"), 0);
14054             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14055         }
14056     } else {
14057         /* Not used for offers from chess program */
14058     }
14059 }
14060
14061 void
14062 DeclineEvent ()
14063 {
14064     /* Decline a pending offer of any kind from opponent */
14065
14066     if (appData.icsActive) {
14067         SendToICS(ics_prefix);
14068         SendToICS("decline\n");
14069     } else if (cmailMsgLoaded) {
14070         if (currentMove == cmailOldMove &&
14071             commentList[cmailOldMove] != NULL &&
14072             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14073                    "Black offers a draw" : "White offers a draw")) {
14074 #ifdef NOTDEF
14075             AppendComment(cmailOldMove, "Draw declined", TRUE);
14076             DisplayComment(cmailOldMove - 1, "Draw declined");
14077 #endif /*NOTDEF*/
14078         } else {
14079             DisplayError(_("There is no pending offer on this move"), 0);
14080         }
14081     } else {
14082         /* Not used for offers from chess program */
14083     }
14084 }
14085
14086 void
14087 RematchEvent ()
14088 {
14089     /* Issue ICS rematch command */
14090     if (appData.icsActive) {
14091         SendToICS(ics_prefix);
14092         SendToICS("rematch\n");
14093     }
14094 }
14095
14096 void
14097 CallFlagEvent ()
14098 {
14099     /* Call your opponent's flag (claim a win on time) */
14100     if (appData.icsActive) {
14101         SendToICS(ics_prefix);
14102         SendToICS("flag\n");
14103     } else {
14104         switch (gameMode) {
14105           default:
14106             return;
14107           case MachinePlaysWhite:
14108             if (whiteFlag) {
14109                 if (blackFlag)
14110                   GameEnds(GameIsDrawn, "Both players ran out of time",
14111                            GE_PLAYER);
14112                 else
14113                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14114             } else {
14115                 DisplayError(_("Your opponent is not out of time"), 0);
14116             }
14117             break;
14118           case MachinePlaysBlack:
14119             if (blackFlag) {
14120                 if (whiteFlag)
14121                   GameEnds(GameIsDrawn, "Both players ran out of time",
14122                            GE_PLAYER);
14123                 else
14124                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14125             } else {
14126                 DisplayError(_("Your opponent is not out of time"), 0);
14127             }
14128             break;
14129         }
14130     }
14131 }
14132
14133 void
14134 ClockClick (int which)
14135 {       // [HGM] code moved to back-end from winboard.c
14136         if(which) { // black clock
14137           if (gameMode == EditPosition || gameMode == IcsExamining) {
14138             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14139             SetBlackToPlayEvent();
14140           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14141           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14142           } else if (shiftKey) {
14143             AdjustClock(which, -1);
14144           } else if (gameMode == IcsPlayingWhite ||
14145                      gameMode == MachinePlaysBlack) {
14146             CallFlagEvent();
14147           }
14148         } else { // white clock
14149           if (gameMode == EditPosition || gameMode == IcsExamining) {
14150             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14151             SetWhiteToPlayEvent();
14152           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14153           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14154           } else if (shiftKey) {
14155             AdjustClock(which, -1);
14156           } else if (gameMode == IcsPlayingBlack ||
14157                    gameMode == MachinePlaysWhite) {
14158             CallFlagEvent();
14159           }
14160         }
14161 }
14162
14163 void
14164 DrawEvent ()
14165 {
14166     /* Offer draw or accept pending draw offer from opponent */
14167
14168     if (appData.icsActive) {
14169         /* Note: tournament rules require draw offers to be
14170            made after you make your move but before you punch
14171            your clock.  Currently ICS doesn't let you do that;
14172            instead, you immediately punch your clock after making
14173            a move, but you can offer a draw at any time. */
14174
14175         SendToICS(ics_prefix);
14176         SendToICS("draw\n");
14177         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14178     } else if (cmailMsgLoaded) {
14179         if (currentMove == cmailOldMove &&
14180             commentList[cmailOldMove] != NULL &&
14181             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14182                    "Black offers a draw" : "White offers a draw")) {
14183             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14184             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14185         } else if (currentMove == cmailOldMove + 1) {
14186             char *offer = WhiteOnMove(cmailOldMove) ?
14187               "White offers a draw" : "Black offers a draw";
14188             AppendComment(currentMove, offer, TRUE);
14189             DisplayComment(currentMove - 1, offer);
14190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14191         } else {
14192             DisplayError(_("You must make your move before offering a draw"), 0);
14193             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14194         }
14195     } else if (first.offeredDraw) {
14196         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14197     } else {
14198         if (first.sendDrawOffers) {
14199             SendToProgram("draw\n", &first);
14200             userOfferedDraw = TRUE;
14201         }
14202     }
14203 }
14204
14205 void
14206 AdjournEvent ()
14207 {
14208     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14209
14210     if (appData.icsActive) {
14211         SendToICS(ics_prefix);
14212         SendToICS("adjourn\n");
14213     } else {
14214         /* Currently GNU Chess doesn't offer or accept Adjourns */
14215     }
14216 }
14217
14218
14219 void
14220 AbortEvent ()
14221 {
14222     /* Offer Abort or accept pending Abort offer from opponent */
14223
14224     if (appData.icsActive) {
14225         SendToICS(ics_prefix);
14226         SendToICS("abort\n");
14227     } else {
14228         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14229     }
14230 }
14231
14232 void
14233 ResignEvent ()
14234 {
14235     /* Resign.  You can do this even if it's not your turn. */
14236
14237     if (appData.icsActive) {
14238         SendToICS(ics_prefix);
14239         SendToICS("resign\n");
14240     } else {
14241         switch (gameMode) {
14242           case MachinePlaysWhite:
14243             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14244             break;
14245           case MachinePlaysBlack:
14246             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14247             break;
14248           case EditGame:
14249             if (cmailMsgLoaded) {
14250                 TruncateGame();
14251                 if (WhiteOnMove(cmailOldMove)) {
14252                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14253                 } else {
14254                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14255                 }
14256                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14257             }
14258             break;
14259           default:
14260             break;
14261         }
14262     }
14263 }
14264
14265
14266 void
14267 StopObservingEvent ()
14268 {
14269     /* Stop observing current games */
14270     SendToICS(ics_prefix);
14271     SendToICS("unobserve\n");
14272 }
14273
14274 void
14275 StopExaminingEvent ()
14276 {
14277     /* Stop observing current game */
14278     SendToICS(ics_prefix);
14279     SendToICS("unexamine\n");
14280 }
14281
14282 void
14283 ForwardInner (int target)
14284 {
14285     int limit; int oldSeekGraphUp = seekGraphUp;
14286
14287     if (appData.debugMode)
14288         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14289                 target, currentMove, forwardMostMove);
14290
14291     if (gameMode == EditPosition)
14292       return;
14293
14294     seekGraphUp = FALSE;
14295     MarkTargetSquares(1);
14296
14297     if (gameMode == PlayFromGameFile && !pausing)
14298       PauseEvent();
14299
14300     if (gameMode == IcsExamining && pausing)
14301       limit = pauseExamForwardMostMove;
14302     else
14303       limit = forwardMostMove;
14304
14305     if (target > limit) target = limit;
14306
14307     if (target > 0 && moveList[target - 1][0]) {
14308         int fromX, fromY, toX, toY;
14309         toX = moveList[target - 1][2] - AAA;
14310         toY = moveList[target - 1][3] - ONE;
14311         if (moveList[target - 1][1] == '@') {
14312             if (appData.highlightLastMove) {
14313                 SetHighlights(-1, -1, toX, toY);
14314             }
14315         } else {
14316             fromX = moveList[target - 1][0] - AAA;
14317             fromY = moveList[target - 1][1] - ONE;
14318             if (target == currentMove + 1) {
14319                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14320             }
14321             if (appData.highlightLastMove) {
14322                 SetHighlights(fromX, fromY, toX, toY);
14323             }
14324         }
14325     }
14326     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14327         gameMode == Training || gameMode == PlayFromGameFile ||
14328         gameMode == AnalyzeFile) {
14329         while (currentMove < target) {
14330             SendMoveToProgram(currentMove++, &first);
14331         }
14332     } else {
14333         currentMove = target;
14334     }
14335
14336     if (gameMode == EditGame || gameMode == EndOfGame) {
14337         whiteTimeRemaining = timeRemaining[0][currentMove];
14338         blackTimeRemaining = timeRemaining[1][currentMove];
14339     }
14340     DisplayBothClocks();
14341     DisplayMove(currentMove - 1);
14342     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14343     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14344     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14345         DisplayComment(currentMove - 1, commentList[currentMove]);
14346     }
14347     mappedMove = -1; // [HGM] exclude: invalidate map
14348 }
14349
14350
14351 void
14352 ForwardEvent ()
14353 {
14354     if (gameMode == IcsExamining && !pausing) {
14355         SendToICS(ics_prefix);
14356         SendToICS("forward\n");
14357     } else {
14358         ForwardInner(currentMove + 1);
14359     }
14360 }
14361
14362 void
14363 ToEndEvent ()
14364 {
14365     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14366         /* to optimze, we temporarily turn off analysis mode while we feed
14367          * the remaining moves to the engine. Otherwise we get analysis output
14368          * after each move.
14369          */
14370         if (first.analysisSupport) {
14371           SendToProgram("exit\nforce\n", &first);
14372           first.analyzing = FALSE;
14373         }
14374     }
14375
14376     if (gameMode == IcsExamining && !pausing) {
14377         SendToICS(ics_prefix);
14378         SendToICS("forward 999999\n");
14379     } else {
14380         ForwardInner(forwardMostMove);
14381     }
14382
14383     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14384         /* we have fed all the moves, so reactivate analysis mode */
14385         SendToProgram("analyze\n", &first);
14386         first.analyzing = TRUE;
14387         /*first.maybeThinking = TRUE;*/
14388         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14389     }
14390 }
14391
14392 void
14393 BackwardInner (int target)
14394 {
14395     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14396
14397     if (appData.debugMode)
14398         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14399                 target, currentMove, forwardMostMove);
14400
14401     if (gameMode == EditPosition) return;
14402     seekGraphUp = FALSE;
14403     MarkTargetSquares(1);
14404     if (currentMove <= backwardMostMove) {
14405         ClearHighlights();
14406         DrawPosition(full_redraw, boards[currentMove]);
14407         return;
14408     }
14409     if (gameMode == PlayFromGameFile && !pausing)
14410       PauseEvent();
14411
14412     if (moveList[target][0]) {
14413         int fromX, fromY, toX, toY;
14414         toX = moveList[target][2] - AAA;
14415         toY = moveList[target][3] - ONE;
14416         if (moveList[target][1] == '@') {
14417             if (appData.highlightLastMove) {
14418                 SetHighlights(-1, -1, toX, toY);
14419             }
14420         } else {
14421             fromX = moveList[target][0] - AAA;
14422             fromY = moveList[target][1] - ONE;
14423             if (target == currentMove - 1) {
14424                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14425             }
14426             if (appData.highlightLastMove) {
14427                 SetHighlights(fromX, fromY, toX, toY);
14428             }
14429         }
14430     }
14431     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14432         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14433         while (currentMove > target) {
14434             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14435                 // null move cannot be undone. Reload program with move history before it.
14436                 int i;
14437                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14438                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14439                 }
14440                 SendBoard(&first, i); 
14441                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14442                 break;
14443             }
14444             SendToProgram("undo\n", &first);
14445             currentMove--;
14446         }
14447     } else {
14448         currentMove = target;
14449     }
14450
14451     if (gameMode == EditGame || gameMode == EndOfGame) {
14452         whiteTimeRemaining = timeRemaining[0][currentMove];
14453         blackTimeRemaining = timeRemaining[1][currentMove];
14454     }
14455     DisplayBothClocks();
14456     DisplayMove(currentMove - 1);
14457     DrawPosition(full_redraw, boards[currentMove]);
14458     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14459     // [HGM] PV info: routine tests if comment empty
14460     DisplayComment(currentMove - 1, commentList[currentMove]);
14461     mappedMove = -1; // [HGM] exclude: invalidate map
14462 }
14463
14464 void
14465 BackwardEvent ()
14466 {
14467     if (gameMode == IcsExamining && !pausing) {
14468         SendToICS(ics_prefix);
14469         SendToICS("backward\n");
14470     } else {
14471         BackwardInner(currentMove - 1);
14472     }
14473 }
14474
14475 void
14476 ToStartEvent ()
14477 {
14478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14479         /* to optimize, we temporarily turn off analysis mode while we undo
14480          * all the moves. Otherwise we get analysis output after each undo.
14481          */
14482         if (first.analysisSupport) {
14483           SendToProgram("exit\nforce\n", &first);
14484           first.analyzing = FALSE;
14485         }
14486     }
14487
14488     if (gameMode == IcsExamining && !pausing) {
14489         SendToICS(ics_prefix);
14490         SendToICS("backward 999999\n");
14491     } else {
14492         BackwardInner(backwardMostMove);
14493     }
14494
14495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14496         /* we have fed all the moves, so reactivate analysis mode */
14497         SendToProgram("analyze\n", &first);
14498         first.analyzing = TRUE;
14499         /*first.maybeThinking = TRUE;*/
14500         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14501     }
14502 }
14503
14504 void
14505 ToNrEvent (int to)
14506 {
14507   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14508   if (to >= forwardMostMove) to = forwardMostMove;
14509   if (to <= backwardMostMove) to = backwardMostMove;
14510   if (to < currentMove) {
14511     BackwardInner(to);
14512   } else {
14513     ForwardInner(to);
14514   }
14515 }
14516
14517 void
14518 RevertEvent (Boolean annotate)
14519 {
14520     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14521         return;
14522     }
14523     if (gameMode != IcsExamining) {
14524         DisplayError(_("You are not examining a game"), 0);
14525         return;
14526     }
14527     if (pausing) {
14528         DisplayError(_("You can't revert while pausing"), 0);
14529         return;
14530     }
14531     SendToICS(ics_prefix);
14532     SendToICS("revert\n");
14533 }
14534
14535 void
14536 RetractMoveEvent ()
14537 {
14538     switch (gameMode) {
14539       case MachinePlaysWhite:
14540       case MachinePlaysBlack:
14541         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14542             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14543             return;
14544         }
14545         if (forwardMostMove < 2) return;
14546         currentMove = forwardMostMove = forwardMostMove - 2;
14547         whiteTimeRemaining = timeRemaining[0][currentMove];
14548         blackTimeRemaining = timeRemaining[1][currentMove];
14549         DisplayBothClocks();
14550         DisplayMove(currentMove - 1);
14551         ClearHighlights();/*!! could figure this out*/
14552         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14553         SendToProgram("remove\n", &first);
14554         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14555         break;
14556
14557       case BeginningOfGame:
14558       default:
14559         break;
14560
14561       case IcsPlayingWhite:
14562       case IcsPlayingBlack:
14563         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14564             SendToICS(ics_prefix);
14565             SendToICS("takeback 2\n");
14566         } else {
14567             SendToICS(ics_prefix);
14568             SendToICS("takeback 1\n");
14569         }
14570         break;
14571     }
14572 }
14573
14574 void
14575 MoveNowEvent ()
14576 {
14577     ChessProgramState *cps;
14578
14579     switch (gameMode) {
14580       case MachinePlaysWhite:
14581         if (!WhiteOnMove(forwardMostMove)) {
14582             DisplayError(_("It is your turn"), 0);
14583             return;
14584         }
14585         cps = &first;
14586         break;
14587       case MachinePlaysBlack:
14588         if (WhiteOnMove(forwardMostMove)) {
14589             DisplayError(_("It is your turn"), 0);
14590             return;
14591         }
14592         cps = &first;
14593         break;
14594       case TwoMachinesPlay:
14595         if (WhiteOnMove(forwardMostMove) ==
14596             (first.twoMachinesColor[0] == 'w')) {
14597             cps = &first;
14598         } else {
14599             cps = &second;
14600         }
14601         break;
14602       case BeginningOfGame:
14603       default:
14604         return;
14605     }
14606     SendToProgram("?\n", cps);
14607 }
14608
14609 void
14610 TruncateGameEvent ()
14611 {
14612     EditGameEvent();
14613     if (gameMode != EditGame) return;
14614     TruncateGame();
14615 }
14616
14617 void
14618 TruncateGame ()
14619 {
14620     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14621     if (forwardMostMove > currentMove) {
14622         if (gameInfo.resultDetails != NULL) {
14623             free(gameInfo.resultDetails);
14624             gameInfo.resultDetails = NULL;
14625             gameInfo.result = GameUnfinished;
14626         }
14627         forwardMostMove = currentMove;
14628         HistorySet(parseList, backwardMostMove, forwardMostMove,
14629                    currentMove-1);
14630     }
14631 }
14632
14633 void
14634 HintEvent ()
14635 {
14636     if (appData.noChessProgram) return;
14637     switch (gameMode) {
14638       case MachinePlaysWhite:
14639         if (WhiteOnMove(forwardMostMove)) {
14640             DisplayError(_("Wait until your turn"), 0);
14641             return;
14642         }
14643         break;
14644       case BeginningOfGame:
14645       case MachinePlaysBlack:
14646         if (!WhiteOnMove(forwardMostMove)) {
14647             DisplayError(_("Wait until your turn"), 0);
14648             return;
14649         }
14650         break;
14651       default:
14652         DisplayError(_("No hint available"), 0);
14653         return;
14654     }
14655     SendToProgram("hint\n", &first);
14656     hintRequested = TRUE;
14657 }
14658
14659 void
14660 BookEvent ()
14661 {
14662     if (appData.noChessProgram) return;
14663     switch (gameMode) {
14664       case MachinePlaysWhite:
14665         if (WhiteOnMove(forwardMostMove)) {
14666             DisplayError(_("Wait until your turn"), 0);
14667             return;
14668         }
14669         break;
14670       case BeginningOfGame:
14671       case MachinePlaysBlack:
14672         if (!WhiteOnMove(forwardMostMove)) {
14673             DisplayError(_("Wait until your turn"), 0);
14674             return;
14675         }
14676         break;
14677       case EditPosition:
14678         EditPositionDone(TRUE);
14679         break;
14680       case TwoMachinesPlay:
14681         return;
14682       default:
14683         break;
14684     }
14685     SendToProgram("bk\n", &first);
14686     bookOutput[0] = NULLCHAR;
14687     bookRequested = TRUE;
14688 }
14689
14690 void
14691 AboutGameEvent ()
14692 {
14693     char *tags = PGNTags(&gameInfo);
14694     TagsPopUp(tags, CmailMsg());
14695     free(tags);
14696 }
14697
14698 /* end button procedures */
14699
14700 void
14701 PrintPosition (FILE *fp, int move)
14702 {
14703     int i, j;
14704
14705     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14706         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14707             char c = PieceToChar(boards[move][i][j]);
14708             fputc(c == 'x' ? '.' : c, fp);
14709             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14710         }
14711     }
14712     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14713       fprintf(fp, "white to play\n");
14714     else
14715       fprintf(fp, "black to play\n");
14716 }
14717
14718 void
14719 PrintOpponents (FILE *fp)
14720 {
14721     if (gameInfo.white != NULL) {
14722         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14723     } else {
14724         fprintf(fp, "\n");
14725     }
14726 }
14727
14728 /* Find last component of program's own name, using some heuristics */
14729 void
14730 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14731 {
14732     char *p, *q, c;
14733     int local = (strcmp(host, "localhost") == 0);
14734     while (!local && (p = strchr(prog, ';')) != NULL) {
14735         p++;
14736         while (*p == ' ') p++;
14737         prog = p;
14738     }
14739     if (*prog == '"' || *prog == '\'') {
14740         q = strchr(prog + 1, *prog);
14741     } else {
14742         q = strchr(prog, ' ');
14743     }
14744     if (q == NULL) q = prog + strlen(prog);
14745     p = q;
14746     while (p >= prog && *p != '/' && *p != '\\') p--;
14747     p++;
14748     if(p == prog && *p == '"') p++;
14749     c = *q; *q = 0;
14750     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14751     memcpy(buf, p, q - p);
14752     buf[q - p] = NULLCHAR;
14753     if (!local) {
14754         strcat(buf, "@");
14755         strcat(buf, host);
14756     }
14757 }
14758
14759 char *
14760 TimeControlTagValue ()
14761 {
14762     char buf[MSG_SIZ];
14763     if (!appData.clockMode) {
14764       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14765     } else if (movesPerSession > 0) {
14766       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14767     } else if (timeIncrement == 0) {
14768       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14769     } else {
14770       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14771     }
14772     return StrSave(buf);
14773 }
14774
14775 void
14776 SetGameInfo ()
14777 {
14778     /* This routine is used only for certain modes */
14779     VariantClass v = gameInfo.variant;
14780     ChessMove r = GameUnfinished;
14781     char *p = NULL;
14782
14783     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14784         r = gameInfo.result;
14785         p = gameInfo.resultDetails;
14786         gameInfo.resultDetails = NULL;
14787     }
14788     ClearGameInfo(&gameInfo);
14789     gameInfo.variant = v;
14790
14791     switch (gameMode) {
14792       case MachinePlaysWhite:
14793         gameInfo.event = StrSave( appData.pgnEventHeader );
14794         gameInfo.site = StrSave(HostName());
14795         gameInfo.date = PGNDate();
14796         gameInfo.round = StrSave("-");
14797         gameInfo.white = StrSave(first.tidy);
14798         gameInfo.black = StrSave(UserName());
14799         gameInfo.timeControl = TimeControlTagValue();
14800         break;
14801
14802       case MachinePlaysBlack:
14803         gameInfo.event = StrSave( appData.pgnEventHeader );
14804         gameInfo.site = StrSave(HostName());
14805         gameInfo.date = PGNDate();
14806         gameInfo.round = StrSave("-");
14807         gameInfo.white = StrSave(UserName());
14808         gameInfo.black = StrSave(first.tidy);
14809         gameInfo.timeControl = TimeControlTagValue();
14810         break;
14811
14812       case TwoMachinesPlay:
14813         gameInfo.event = StrSave( appData.pgnEventHeader );
14814         gameInfo.site = StrSave(HostName());
14815         gameInfo.date = PGNDate();
14816         if (roundNr > 0) {
14817             char buf[MSG_SIZ];
14818             snprintf(buf, MSG_SIZ, "%d", roundNr);
14819             gameInfo.round = StrSave(buf);
14820         } else {
14821             gameInfo.round = StrSave("-");
14822         }
14823         if (first.twoMachinesColor[0] == 'w') {
14824             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14825             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14826         } else {
14827             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14828             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14829         }
14830         gameInfo.timeControl = TimeControlTagValue();
14831         break;
14832
14833       case EditGame:
14834         gameInfo.event = StrSave("Edited game");
14835         gameInfo.site = StrSave(HostName());
14836         gameInfo.date = PGNDate();
14837         gameInfo.round = StrSave("-");
14838         gameInfo.white = StrSave("-");
14839         gameInfo.black = StrSave("-");
14840         gameInfo.result = r;
14841         gameInfo.resultDetails = p;
14842         break;
14843
14844       case EditPosition:
14845         gameInfo.event = StrSave("Edited position");
14846         gameInfo.site = StrSave(HostName());
14847         gameInfo.date = PGNDate();
14848         gameInfo.round = StrSave("-");
14849         gameInfo.white = StrSave("-");
14850         gameInfo.black = StrSave("-");
14851         break;
14852
14853       case IcsPlayingWhite:
14854       case IcsPlayingBlack:
14855       case IcsObserving:
14856       case IcsExamining:
14857         break;
14858
14859       case PlayFromGameFile:
14860         gameInfo.event = StrSave("Game from non-PGN file");
14861         gameInfo.site = StrSave(HostName());
14862         gameInfo.date = PGNDate();
14863         gameInfo.round = StrSave("-");
14864         gameInfo.white = StrSave("?");
14865         gameInfo.black = StrSave("?");
14866         break;
14867
14868       default:
14869         break;
14870     }
14871 }
14872
14873 void
14874 ReplaceComment (int index, char *text)
14875 {
14876     int len;
14877     char *p;
14878     float score;
14879
14880     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14881        pvInfoList[index-1].depth == len &&
14882        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14883        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14884     while (*text == '\n') text++;
14885     len = strlen(text);
14886     while (len > 0 && text[len - 1] == '\n') len--;
14887
14888     if (commentList[index] != NULL)
14889       free(commentList[index]);
14890
14891     if (len == 0) {
14892         commentList[index] = NULL;
14893         return;
14894     }
14895   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14896       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14897       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14898     commentList[index] = (char *) malloc(len + 2);
14899     strncpy(commentList[index], text, len);
14900     commentList[index][len] = '\n';
14901     commentList[index][len + 1] = NULLCHAR;
14902   } else {
14903     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14904     char *p;
14905     commentList[index] = (char *) malloc(len + 7);
14906     safeStrCpy(commentList[index], "{\n", 3);
14907     safeStrCpy(commentList[index]+2, text, len+1);
14908     commentList[index][len+2] = NULLCHAR;
14909     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14910     strcat(commentList[index], "\n}\n");
14911   }
14912 }
14913
14914 void
14915 CrushCRs (char *text)
14916 {
14917   char *p = text;
14918   char *q = text;
14919   char ch;
14920
14921   do {
14922     ch = *p++;
14923     if (ch == '\r') continue;
14924     *q++ = ch;
14925   } while (ch != '\0');
14926 }
14927
14928 void
14929 AppendComment (int index, char *text, Boolean addBraces)
14930 /* addBraces  tells if we should add {} */
14931 {
14932     int oldlen, len;
14933     char *old;
14934
14935 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14936     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14937
14938     CrushCRs(text);
14939     while (*text == '\n') text++;
14940     len = strlen(text);
14941     while (len > 0 && text[len - 1] == '\n') len--;
14942     text[len] = NULLCHAR;
14943
14944     if (len == 0) return;
14945
14946     if (commentList[index] != NULL) {
14947       Boolean addClosingBrace = addBraces;
14948         old = commentList[index];
14949         oldlen = strlen(old);
14950         while(commentList[index][oldlen-1] ==  '\n')
14951           commentList[index][--oldlen] = NULLCHAR;
14952         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14953         safeStrCpy(commentList[index], old, oldlen + len + 6);
14954         free(old);
14955         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14956         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14957           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14958           while (*text == '\n') { text++; len--; }
14959           commentList[index][--oldlen] = NULLCHAR;
14960       }
14961         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14962         else          strcat(commentList[index], "\n");
14963         strcat(commentList[index], text);
14964         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14965         else          strcat(commentList[index], "\n");
14966     } else {
14967         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14968         if(addBraces)
14969           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14970         else commentList[index][0] = NULLCHAR;
14971         strcat(commentList[index], text);
14972         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14973         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14974     }
14975 }
14976
14977 static char *
14978 FindStr (char * text, char * sub_text)
14979 {
14980     char * result = strstr( text, sub_text );
14981
14982     if( result != NULL ) {
14983         result += strlen( sub_text );
14984     }
14985
14986     return result;
14987 }
14988
14989 /* [AS] Try to extract PV info from PGN comment */
14990 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14991 char *
14992 GetInfoFromComment (int index, char * text)
14993 {
14994     char * sep = text, *p;
14995
14996     if( text != NULL && index > 0 ) {
14997         int score = 0;
14998         int depth = 0;
14999         int time = -1, sec = 0, deci;
15000         char * s_eval = FindStr( text, "[%eval " );
15001         char * s_emt = FindStr( text, "[%emt " );
15002
15003         if( s_eval != NULL || s_emt != NULL ) {
15004             /* New style */
15005             char delim;
15006
15007             if( s_eval != NULL ) {
15008                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15009                     return text;
15010                 }
15011
15012                 if( delim != ']' ) {
15013                     return text;
15014                 }
15015             }
15016
15017             if( s_emt != NULL ) {
15018             }
15019                 return text;
15020         }
15021         else {
15022             /* We expect something like: [+|-]nnn.nn/dd */
15023             int score_lo = 0;
15024
15025             if(*text != '{') return text; // [HGM] braces: must be normal comment
15026
15027             sep = strchr( text, '/' );
15028             if( sep == NULL || sep < (text+4) ) {
15029                 return text;
15030             }
15031
15032             p = text;
15033             if(p[1] == '(') { // comment starts with PV
15034                p = strchr(p, ')'); // locate end of PV
15035                if(p == NULL || sep < p+5) return text;
15036                // at this point we have something like "{(.*) +0.23/6 ..."
15037                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15038                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15039                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15040             }
15041             time = -1; sec = -1; deci = -1;
15042             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15043                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15044                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15045                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15046                 return text;
15047             }
15048
15049             if( score_lo < 0 || score_lo >= 100 ) {
15050                 return text;
15051             }
15052
15053             if(sec >= 0) time = 600*time + 10*sec; else
15054             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15055
15056             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15057
15058             /* [HGM] PV time: now locate end of PV info */
15059             while( *++sep >= '0' && *sep <= '9'); // strip depth
15060             if(time >= 0)
15061             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15062             if(sec >= 0)
15063             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15064             if(deci >= 0)
15065             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15066             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15067         }
15068
15069         if( depth <= 0 ) {
15070             return text;
15071         }
15072
15073         if( time < 0 ) {
15074             time = -1;
15075         }
15076
15077         pvInfoList[index-1].depth = depth;
15078         pvInfoList[index-1].score = score;
15079         pvInfoList[index-1].time  = 10*time; // centi-sec
15080         if(*sep == '}') *sep = 0; else *--sep = '{';
15081         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15082     }
15083     return sep;
15084 }
15085
15086 void
15087 SendToProgram (char *message, ChessProgramState *cps)
15088 {
15089     int count, outCount, error;
15090     char buf[MSG_SIZ];
15091
15092     if (cps->pr == NoProc) return;
15093     Attention(cps);
15094
15095     if (appData.debugMode) {
15096         TimeMark now;
15097         GetTimeMark(&now);
15098         fprintf(debugFP, "%ld >%-6s: %s",
15099                 SubtractTimeMarks(&now, &programStartTime),
15100                 cps->which, message);
15101         if(serverFP)
15102             fprintf(serverFP, "%ld >%-6s: %s",
15103                 SubtractTimeMarks(&now, &programStartTime),
15104                 cps->which, message), fflush(serverFP);
15105     }
15106
15107     count = strlen(message);
15108     outCount = OutputToProcess(cps->pr, message, count, &error);
15109     if (outCount < count && !exiting
15110                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15111       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15112       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15113         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15114             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15115                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15116                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15117                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15118             } else {
15119                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15120                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15121                 gameInfo.result = res;
15122             }
15123             gameInfo.resultDetails = StrSave(buf);
15124         }
15125         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15126         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15127     }
15128 }
15129
15130 void
15131 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15132 {
15133     char *end_str;
15134     char buf[MSG_SIZ];
15135     ChessProgramState *cps = (ChessProgramState *)closure;
15136
15137     if (isr != cps->isr) return; /* Killed intentionally */
15138     if (count <= 0) {
15139         if (count == 0) {
15140             RemoveInputSource(cps->isr);
15141             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15142             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15143                     _(cps->which), cps->program);
15144         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15145                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15146                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15147                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15148                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15149                 } else {
15150                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15151                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15152                     gameInfo.result = res;
15153                 }
15154                 gameInfo.resultDetails = StrSave(buf);
15155             }
15156             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15157             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15158         } else {
15159             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15160                     _(cps->which), cps->program);
15161             RemoveInputSource(cps->isr);
15162
15163             /* [AS] Program is misbehaving badly... kill it */
15164             if( count == -2 ) {
15165                 DestroyChildProcess( cps->pr, 9 );
15166                 cps->pr = NoProc;
15167             }
15168
15169             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15170         }
15171         return;
15172     }
15173
15174     if ((end_str = strchr(message, '\r')) != NULL)
15175       *end_str = NULLCHAR;
15176     if ((end_str = strchr(message, '\n')) != NULL)
15177       *end_str = NULLCHAR;
15178
15179     if (appData.debugMode) {
15180         TimeMark now; int print = 1;
15181         char *quote = ""; char c; int i;
15182
15183         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15184                 char start = message[0];
15185                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15186                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15187                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15188                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15189                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15190                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15191                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15192                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15193                    sscanf(message, "hint: %c", &c)!=1 && 
15194                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15195                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15196                     print = (appData.engineComments >= 2);
15197                 }
15198                 message[0] = start; // restore original message
15199         }
15200         if(print) {
15201                 GetTimeMark(&now);
15202                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15203                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15204                         quote,
15205                         message);
15206                 if(serverFP)
15207                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15208                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15209                         quote,
15210                         message), fflush(serverFP);
15211         }
15212     }
15213
15214     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15215     if (appData.icsEngineAnalyze) {
15216         if (strstr(message, "whisper") != NULL ||
15217              strstr(message, "kibitz") != NULL ||
15218             strstr(message, "tellics") != NULL) return;
15219     }
15220
15221     HandleMachineMove(message, cps);
15222 }
15223
15224
15225 void
15226 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15227 {
15228     char buf[MSG_SIZ];
15229     int seconds;
15230
15231     if( timeControl_2 > 0 ) {
15232         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15233             tc = timeControl_2;
15234         }
15235     }
15236     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15237     inc /= cps->timeOdds;
15238     st  /= cps->timeOdds;
15239
15240     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15241
15242     if (st > 0) {
15243       /* Set exact time per move, normally using st command */
15244       if (cps->stKludge) {
15245         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15246         seconds = st % 60;
15247         if (seconds == 0) {
15248           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15249         } else {
15250           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15251         }
15252       } else {
15253         snprintf(buf, MSG_SIZ, "st %d\n", st);
15254       }
15255     } else {
15256       /* Set conventional or incremental time control, using level command */
15257       if (seconds == 0) {
15258         /* Note old gnuchess bug -- minutes:seconds used to not work.
15259            Fixed in later versions, but still avoid :seconds
15260            when seconds is 0. */
15261         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15262       } else {
15263         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15264                  seconds, inc/1000.);
15265       }
15266     }
15267     SendToProgram(buf, cps);
15268
15269     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15270     /* Orthogonally, limit search to given depth */
15271     if (sd > 0) {
15272       if (cps->sdKludge) {
15273         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15274       } else {
15275         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15276       }
15277       SendToProgram(buf, cps);
15278     }
15279
15280     if(cps->nps >= 0) { /* [HGM] nps */
15281         if(cps->supportsNPS == FALSE)
15282           cps->nps = -1; // don't use if engine explicitly says not supported!
15283         else {
15284           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15285           SendToProgram(buf, cps);
15286         }
15287     }
15288 }
15289
15290 ChessProgramState *
15291 WhitePlayer ()
15292 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15293 {
15294     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15295        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15296         return &second;
15297     return &first;
15298 }
15299
15300 void
15301 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15302 {
15303     char message[MSG_SIZ];
15304     long time, otime;
15305
15306     /* Note: this routine must be called when the clocks are stopped
15307        or when they have *just* been set or switched; otherwise
15308        it will be off by the time since the current tick started.
15309     */
15310     if (machineWhite) {
15311         time = whiteTimeRemaining / 10;
15312         otime = blackTimeRemaining / 10;
15313     } else {
15314         time = blackTimeRemaining / 10;
15315         otime = whiteTimeRemaining / 10;
15316     }
15317     /* [HGM] translate opponent's time by time-odds factor */
15318     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15319
15320     if (time <= 0) time = 1;
15321     if (otime <= 0) otime = 1;
15322
15323     snprintf(message, MSG_SIZ, "time %ld\n", time);
15324     SendToProgram(message, cps);
15325
15326     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15327     SendToProgram(message, cps);
15328 }
15329
15330 int
15331 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15332 {
15333   char buf[MSG_SIZ];
15334   int len = strlen(name);
15335   int val;
15336
15337   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15338     (*p) += len + 1;
15339     sscanf(*p, "%d", &val);
15340     *loc = (val != 0);
15341     while (**p && **p != ' ')
15342       (*p)++;
15343     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15344     SendToProgram(buf, cps);
15345     return TRUE;
15346   }
15347   return FALSE;
15348 }
15349
15350 int
15351 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15352 {
15353   char buf[MSG_SIZ];
15354   int len = strlen(name);
15355   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15356     (*p) += len + 1;
15357     sscanf(*p, "%d", loc);
15358     while (**p && **p != ' ') (*p)++;
15359     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15360     SendToProgram(buf, cps);
15361     return TRUE;
15362   }
15363   return FALSE;
15364 }
15365
15366 int
15367 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15368 {
15369   char buf[MSG_SIZ];
15370   int len = strlen(name);
15371   if (strncmp((*p), name, len) == 0
15372       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15373     (*p) += len + 2;
15374     sscanf(*p, "%[^\"]", loc);
15375     while (**p && **p != '\"') (*p)++;
15376     if (**p == '\"') (*p)++;
15377     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15378     SendToProgram(buf, cps);
15379     return TRUE;
15380   }
15381   return FALSE;
15382 }
15383
15384 int
15385 ParseOption (Option *opt, ChessProgramState *cps)
15386 // [HGM] options: process the string that defines an engine option, and determine
15387 // name, type, default value, and allowed value range
15388 {
15389         char *p, *q, buf[MSG_SIZ];
15390         int n, min = (-1)<<31, max = 1<<31, def;
15391
15392         if(p = strstr(opt->name, " -spin ")) {
15393             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15394             if(max < min) max = min; // enforce consistency
15395             if(def < min) def = min;
15396             if(def > max) def = max;
15397             opt->value = def;
15398             opt->min = min;
15399             opt->max = max;
15400             opt->type = Spin;
15401         } else if((p = strstr(opt->name, " -slider "))) {
15402             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15403             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15404             if(max < min) max = min; // enforce consistency
15405             if(def < min) def = min;
15406             if(def > max) def = max;
15407             opt->value = def;
15408             opt->min = min;
15409             opt->max = max;
15410             opt->type = Spin; // Slider;
15411         } else if((p = strstr(opt->name, " -string "))) {
15412             opt->textValue = p+9;
15413             opt->type = TextBox;
15414         } else if((p = strstr(opt->name, " -file "))) {
15415             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15416             opt->textValue = p+7;
15417             opt->type = FileName; // FileName;
15418         } else if((p = strstr(opt->name, " -path "))) {
15419             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15420             opt->textValue = p+7;
15421             opt->type = PathName; // PathName;
15422         } else if(p = strstr(opt->name, " -check ")) {
15423             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15424             opt->value = (def != 0);
15425             opt->type = CheckBox;
15426         } else if(p = strstr(opt->name, " -combo ")) {
15427             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15428             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15429             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15430             opt->value = n = 0;
15431             while(q = StrStr(q, " /// ")) {
15432                 n++; *q = 0;    // count choices, and null-terminate each of them
15433                 q += 5;
15434                 if(*q == '*') { // remember default, which is marked with * prefix
15435                     q++;
15436                     opt->value = n;
15437                 }
15438                 cps->comboList[cps->comboCnt++] = q;
15439             }
15440             cps->comboList[cps->comboCnt++] = NULL;
15441             opt->max = n + 1;
15442             opt->type = ComboBox;
15443         } else if(p = strstr(opt->name, " -button")) {
15444             opt->type = Button;
15445         } else if(p = strstr(opt->name, " -save")) {
15446             opt->type = SaveButton;
15447         } else return FALSE;
15448         *p = 0; // terminate option name
15449         // now look if the command-line options define a setting for this engine option.
15450         if(cps->optionSettings && cps->optionSettings[0])
15451             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15452         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15453           snprintf(buf, MSG_SIZ, "option %s", p);
15454                 if(p = strstr(buf, ",")) *p = 0;
15455                 if(q = strchr(buf, '=')) switch(opt->type) {
15456                     case ComboBox:
15457                         for(n=0; n<opt->max; n++)
15458                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15459                         break;
15460                     case TextBox:
15461                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15462                         break;
15463                     case Spin:
15464                     case CheckBox:
15465                         opt->value = atoi(q+1);
15466                     default:
15467                         break;
15468                 }
15469                 strcat(buf, "\n");
15470                 SendToProgram(buf, cps);
15471         }
15472         return TRUE;
15473 }
15474
15475 void
15476 FeatureDone (ChessProgramState *cps, int val)
15477 {
15478   DelayedEventCallback cb = GetDelayedEvent();
15479   if ((cb == InitBackEnd3 && cps == &first) ||
15480       (cb == SettingsMenuIfReady && cps == &second) ||
15481       (cb == LoadEngine) ||
15482       (cb == TwoMachinesEventIfReady)) {
15483     CancelDelayedEvent();
15484     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15485   }
15486   cps->initDone = val;
15487 }
15488
15489 /* Parse feature command from engine */
15490 void
15491 ParseFeatures (char *args, ChessProgramState *cps)
15492 {
15493   char *p = args;
15494   char *q;
15495   int val;
15496   char buf[MSG_SIZ];
15497
15498   for (;;) {
15499     while (*p == ' ') p++;
15500     if (*p == NULLCHAR) return;
15501
15502     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15503     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15504     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15505     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15506     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15507     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15508     if (BoolFeature(&p, "reuse", &val, cps)) {
15509       /* Engine can disable reuse, but can't enable it if user said no */
15510       if (!val) cps->reuse = FALSE;
15511       continue;
15512     }
15513     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15514     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15515       if (gameMode == TwoMachinesPlay) {
15516         DisplayTwoMachinesTitle();
15517       } else {
15518         DisplayTitle("");
15519       }
15520       continue;
15521     }
15522     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15523     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15524     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15525     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15526     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15527     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15528     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15529     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15530     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15531     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15532     if (IntFeature(&p, "done", &val, cps)) {
15533       FeatureDone(cps, val);
15534       continue;
15535     }
15536     /* Added by Tord: */
15537     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15538     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15539     /* End of additions by Tord */
15540
15541     /* [HGM] added features: */
15542     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15543     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15544     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15545     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15546     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15547     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15548     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15549         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15550           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15551             SendToProgram(buf, cps);
15552             continue;
15553         }
15554         if(cps->nrOptions >= MAX_OPTIONS) {
15555             cps->nrOptions--;
15556             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15557             DisplayError(buf, 0);
15558         }
15559         continue;
15560     }
15561     /* End of additions by HGM */
15562
15563     /* unknown feature: complain and skip */
15564     q = p;
15565     while (*q && *q != '=') q++;
15566     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15567     SendToProgram(buf, cps);
15568     p = q;
15569     if (*p == '=') {
15570       p++;
15571       if (*p == '\"') {
15572         p++;
15573         while (*p && *p != '\"') p++;
15574         if (*p == '\"') p++;
15575       } else {
15576         while (*p && *p != ' ') p++;
15577       }
15578     }
15579   }
15580
15581 }
15582
15583 void
15584 PeriodicUpdatesEvent (int newState)
15585 {
15586     if (newState == appData.periodicUpdates)
15587       return;
15588
15589     appData.periodicUpdates=newState;
15590
15591     /* Display type changes, so update it now */
15592 //    DisplayAnalysis();
15593
15594     /* Get the ball rolling again... */
15595     if (newState) {
15596         AnalysisPeriodicEvent(1);
15597         StartAnalysisClock();
15598     }
15599 }
15600
15601 void
15602 PonderNextMoveEvent (int newState)
15603 {
15604     if (newState == appData.ponderNextMove) return;
15605     if (gameMode == EditPosition) EditPositionDone(TRUE);
15606     if (newState) {
15607         SendToProgram("hard\n", &first);
15608         if (gameMode == TwoMachinesPlay) {
15609             SendToProgram("hard\n", &second);
15610         }
15611     } else {
15612         SendToProgram("easy\n", &first);
15613         thinkOutput[0] = NULLCHAR;
15614         if (gameMode == TwoMachinesPlay) {
15615             SendToProgram("easy\n", &second);
15616         }
15617     }
15618     appData.ponderNextMove = newState;
15619 }
15620
15621 void
15622 NewSettingEvent (int option, int *feature, char *command, int value)
15623 {
15624     char buf[MSG_SIZ];
15625
15626     if (gameMode == EditPosition) EditPositionDone(TRUE);
15627     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15628     if(feature == NULL || *feature) SendToProgram(buf, &first);
15629     if (gameMode == TwoMachinesPlay) {
15630         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15631     }
15632 }
15633
15634 void
15635 ShowThinkingEvent ()
15636 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15637 {
15638     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15639     int newState = appData.showThinking
15640         // [HGM] thinking: other features now need thinking output as well
15641         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15642
15643     if (oldState == newState) return;
15644     oldState = newState;
15645     if (gameMode == EditPosition) EditPositionDone(TRUE);
15646     if (oldState) {
15647         SendToProgram("post\n", &first);
15648         if (gameMode == TwoMachinesPlay) {
15649             SendToProgram("post\n", &second);
15650         }
15651     } else {
15652         SendToProgram("nopost\n", &first);
15653         thinkOutput[0] = NULLCHAR;
15654         if (gameMode == TwoMachinesPlay) {
15655             SendToProgram("nopost\n", &second);
15656         }
15657     }
15658 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15659 }
15660
15661 void
15662 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15663 {
15664   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15665   if (pr == NoProc) return;
15666   AskQuestion(title, question, replyPrefix, pr);
15667 }
15668
15669 void
15670 TypeInEvent (char firstChar)
15671 {
15672     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15673         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15674         gameMode == AnalyzeMode || gameMode == EditGame || 
15675         gameMode == EditPosition || gameMode == IcsExamining ||
15676         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15677         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15678                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15679                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15680         gameMode == Training) PopUpMoveDialog(firstChar);
15681 }
15682
15683 void
15684 TypeInDoneEvent (char *move)
15685 {
15686         Board board;
15687         int n, fromX, fromY, toX, toY;
15688         char promoChar;
15689         ChessMove moveType;
15690
15691         // [HGM] FENedit
15692         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15693                 EditPositionPasteFEN(move);
15694                 return;
15695         }
15696         // [HGM] movenum: allow move number to be typed in any mode
15697         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15698           ToNrEvent(2*n-1);
15699           return;
15700         }
15701         // undocumented kludge: allow command-line option to be typed in!
15702         // (potentially fatal, and does not implement the effect of the option.)
15703         // should only be used for options that are values on which future decisions will be made,
15704         // and definitely not on options that would be used during initialization.
15705         if(strstr(move, "!!! -") == move) {
15706             ParseArgsFromString(move+4);
15707             return;
15708         }
15709
15710       if (gameMode != EditGame && currentMove != forwardMostMove && 
15711         gameMode != Training) {
15712         DisplayMoveError(_("Displayed move is not current"));
15713       } else {
15714         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15715           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15716         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15717         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15718           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15719           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15720         } else {
15721           DisplayMoveError(_("Could not parse move"));
15722         }
15723       }
15724 }
15725
15726 void
15727 DisplayMove (int moveNumber)
15728 {
15729     char message[MSG_SIZ];
15730     char res[MSG_SIZ];
15731     char cpThinkOutput[MSG_SIZ];
15732
15733     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15734
15735     if (moveNumber == forwardMostMove - 1 ||
15736         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15737
15738         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15739
15740         if (strchr(cpThinkOutput, '\n')) {
15741             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15742         }
15743     } else {
15744         *cpThinkOutput = NULLCHAR;
15745     }
15746
15747     /* [AS] Hide thinking from human user */
15748     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15749         *cpThinkOutput = NULLCHAR;
15750         if( thinkOutput[0] != NULLCHAR ) {
15751             int i;
15752
15753             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15754                 cpThinkOutput[i] = '.';
15755             }
15756             cpThinkOutput[i] = NULLCHAR;
15757             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15758         }
15759     }
15760
15761     if (moveNumber == forwardMostMove - 1 &&
15762         gameInfo.resultDetails != NULL) {
15763         if (gameInfo.resultDetails[0] == NULLCHAR) {
15764           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15765         } else {
15766           snprintf(res, MSG_SIZ, " {%s} %s",
15767                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15768         }
15769     } else {
15770         res[0] = NULLCHAR;
15771     }
15772
15773     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15774         DisplayMessage(res, cpThinkOutput);
15775     } else {
15776       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15777                 WhiteOnMove(moveNumber) ? " " : ".. ",
15778                 parseList[moveNumber], res);
15779         DisplayMessage(message, cpThinkOutput);
15780     }
15781 }
15782
15783 void
15784 DisplayComment (int moveNumber, char *text)
15785 {
15786     char title[MSG_SIZ];
15787
15788     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15789       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15790     } else {
15791       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15792               WhiteOnMove(moveNumber) ? " " : ".. ",
15793               parseList[moveNumber]);
15794     }
15795     if (text != NULL && (appData.autoDisplayComment || commentUp))
15796         CommentPopUp(title, text);
15797 }
15798
15799 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15800  * might be busy thinking or pondering.  It can be omitted if your
15801  * gnuchess is configured to stop thinking immediately on any user
15802  * input.  However, that gnuchess feature depends on the FIONREAD
15803  * ioctl, which does not work properly on some flavors of Unix.
15804  */
15805 void
15806 Attention (ChessProgramState *cps)
15807 {
15808 #if ATTENTION
15809     if (!cps->useSigint) return;
15810     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15811     switch (gameMode) {
15812       case MachinePlaysWhite:
15813       case MachinePlaysBlack:
15814       case TwoMachinesPlay:
15815       case IcsPlayingWhite:
15816       case IcsPlayingBlack:
15817       case AnalyzeMode:
15818       case AnalyzeFile:
15819         /* Skip if we know it isn't thinking */
15820         if (!cps->maybeThinking) return;
15821         if (appData.debugMode)
15822           fprintf(debugFP, "Interrupting %s\n", cps->which);
15823         InterruptChildProcess(cps->pr);
15824         cps->maybeThinking = FALSE;
15825         break;
15826       default:
15827         break;
15828     }
15829 #endif /*ATTENTION*/
15830 }
15831
15832 int
15833 CheckFlags ()
15834 {
15835     if (whiteTimeRemaining <= 0) {
15836         if (!whiteFlag) {
15837             whiteFlag = TRUE;
15838             if (appData.icsActive) {
15839                 if (appData.autoCallFlag &&
15840                     gameMode == IcsPlayingBlack && !blackFlag) {
15841                   SendToICS(ics_prefix);
15842                   SendToICS("flag\n");
15843                 }
15844             } else {
15845                 if (blackFlag) {
15846                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15847                 } else {
15848                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15849                     if (appData.autoCallFlag) {
15850                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15851                         return TRUE;
15852                     }
15853                 }
15854             }
15855         }
15856     }
15857     if (blackTimeRemaining <= 0) {
15858         if (!blackFlag) {
15859             blackFlag = TRUE;
15860             if (appData.icsActive) {
15861                 if (appData.autoCallFlag &&
15862                     gameMode == IcsPlayingWhite && !whiteFlag) {
15863                   SendToICS(ics_prefix);
15864                   SendToICS("flag\n");
15865                 }
15866             } else {
15867                 if (whiteFlag) {
15868                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15869                 } else {
15870                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15871                     if (appData.autoCallFlag) {
15872                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15873                         return TRUE;
15874                     }
15875                 }
15876             }
15877         }
15878     }
15879     return FALSE;
15880 }
15881
15882 void
15883 CheckTimeControl ()
15884 {
15885     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15886         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15887
15888     /*
15889      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15890      */
15891     if ( !WhiteOnMove(forwardMostMove) ) {
15892         /* White made time control */
15893         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15894         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15895         /* [HGM] time odds: correct new time quota for time odds! */
15896                                             / WhitePlayer()->timeOdds;
15897         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15898     } else {
15899         lastBlack -= blackTimeRemaining;
15900         /* Black made time control */
15901         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15902                                             / WhitePlayer()->other->timeOdds;
15903         lastWhite = whiteTimeRemaining;
15904     }
15905 }
15906
15907 void
15908 DisplayBothClocks ()
15909 {
15910     int wom = gameMode == EditPosition ?
15911       !blackPlaysFirst : WhiteOnMove(currentMove);
15912     DisplayWhiteClock(whiteTimeRemaining, wom);
15913     DisplayBlackClock(blackTimeRemaining, !wom);
15914 }
15915
15916
15917 /* Timekeeping seems to be a portability nightmare.  I think everyone
15918    has ftime(), but I'm really not sure, so I'm including some ifdefs
15919    to use other calls if you don't.  Clocks will be less accurate if
15920    you have neither ftime nor gettimeofday.
15921 */
15922
15923 /* VS 2008 requires the #include outside of the function */
15924 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15925 #include <sys/timeb.h>
15926 #endif
15927
15928 /* Get the current time as a TimeMark */
15929 void
15930 GetTimeMark (TimeMark *tm)
15931 {
15932 #if HAVE_GETTIMEOFDAY
15933
15934     struct timeval timeVal;
15935     struct timezone timeZone;
15936
15937     gettimeofday(&timeVal, &timeZone);
15938     tm->sec = (long) timeVal.tv_sec;
15939     tm->ms = (int) (timeVal.tv_usec / 1000L);
15940
15941 #else /*!HAVE_GETTIMEOFDAY*/
15942 #if HAVE_FTIME
15943
15944 // include <sys/timeb.h> / moved to just above start of function
15945     struct timeb timeB;
15946
15947     ftime(&timeB);
15948     tm->sec = (long) timeB.time;
15949     tm->ms = (int) timeB.millitm;
15950
15951 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15952     tm->sec = (long) time(NULL);
15953     tm->ms = 0;
15954 #endif
15955 #endif
15956 }
15957
15958 /* Return the difference in milliseconds between two
15959    time marks.  We assume the difference will fit in a long!
15960 */
15961 long
15962 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15963 {
15964     return 1000L*(tm2->sec - tm1->sec) +
15965            (long) (tm2->ms - tm1->ms);
15966 }
15967
15968
15969 /*
15970  * Code to manage the game clocks.
15971  *
15972  * In tournament play, black starts the clock and then white makes a move.
15973  * We give the human user a slight advantage if he is playing white---the
15974  * clocks don't run until he makes his first move, so it takes zero time.
15975  * Also, we don't account for network lag, so we could get out of sync
15976  * with GNU Chess's clock -- but then, referees are always right.
15977  */
15978
15979 static TimeMark tickStartTM;
15980 static long intendedTickLength;
15981
15982 long
15983 NextTickLength (long timeRemaining)
15984 {
15985     long nominalTickLength, nextTickLength;
15986
15987     if (timeRemaining > 0L && timeRemaining <= 10000L)
15988       nominalTickLength = 100L;
15989     else
15990       nominalTickLength = 1000L;
15991     nextTickLength = timeRemaining % nominalTickLength;
15992     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15993
15994     return nextTickLength;
15995 }
15996
15997 /* Adjust clock one minute up or down */
15998 void
15999 AdjustClock (Boolean which, int dir)
16000 {
16001     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16002     if(which) blackTimeRemaining += 60000*dir;
16003     else      whiteTimeRemaining += 60000*dir;
16004     DisplayBothClocks();
16005     adjustedClock = TRUE;
16006 }
16007
16008 /* Stop clocks and reset to a fresh time control */
16009 void
16010 ResetClocks ()
16011 {
16012     (void) StopClockTimer();
16013     if (appData.icsActive) {
16014         whiteTimeRemaining = blackTimeRemaining = 0;
16015     } else if (searchTime) {
16016         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16017         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16018     } else { /* [HGM] correct new time quote for time odds */
16019         whiteTC = blackTC = fullTimeControlString;
16020         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16021         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16022     }
16023     if (whiteFlag || blackFlag) {
16024         DisplayTitle("");
16025         whiteFlag = blackFlag = FALSE;
16026     }
16027     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16028     DisplayBothClocks();
16029     adjustedClock = FALSE;
16030 }
16031
16032 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16033
16034 /* Decrement running clock by amount of time that has passed */
16035 void
16036 DecrementClocks ()
16037 {
16038     long timeRemaining;
16039     long lastTickLength, fudge;
16040     TimeMark now;
16041
16042     if (!appData.clockMode) return;
16043     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16044
16045     GetTimeMark(&now);
16046
16047     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16048
16049     /* Fudge if we woke up a little too soon */
16050     fudge = intendedTickLength - lastTickLength;
16051     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16052
16053     if (WhiteOnMove(forwardMostMove)) {
16054         if(whiteNPS >= 0) lastTickLength = 0;
16055         timeRemaining = whiteTimeRemaining -= lastTickLength;
16056         if(timeRemaining < 0 && !appData.icsActive) {
16057             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16058             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16059                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16060                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16061             }
16062         }
16063         DisplayWhiteClock(whiteTimeRemaining - fudge,
16064                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16065     } else {
16066         if(blackNPS >= 0) lastTickLength = 0;
16067         timeRemaining = blackTimeRemaining -= lastTickLength;
16068         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16069             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16070             if(suddenDeath) {
16071                 blackStartMove = forwardMostMove;
16072                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16073             }
16074         }
16075         DisplayBlackClock(blackTimeRemaining - fudge,
16076                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16077     }
16078     if (CheckFlags()) return;
16079
16080     tickStartTM = now;
16081     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16082     StartClockTimer(intendedTickLength);
16083
16084     /* if the time remaining has fallen below the alarm threshold, sound the
16085      * alarm. if the alarm has sounded and (due to a takeback or time control
16086      * with increment) the time remaining has increased to a level above the
16087      * threshold, reset the alarm so it can sound again.
16088      */
16089
16090     if (appData.icsActive && appData.icsAlarm) {
16091
16092         /* make sure we are dealing with the user's clock */
16093         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16094                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16095            )) return;
16096
16097         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16098             alarmSounded = FALSE;
16099         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16100             PlayAlarmSound();
16101             alarmSounded = TRUE;
16102         }
16103     }
16104 }
16105
16106
16107 /* A player has just moved, so stop the previously running
16108    clock and (if in clock mode) start the other one.
16109    We redisplay both clocks in case we're in ICS mode, because
16110    ICS gives us an update to both clocks after every move.
16111    Note that this routine is called *after* forwardMostMove
16112    is updated, so the last fractional tick must be subtracted
16113    from the color that is *not* on move now.
16114 */
16115 void
16116 SwitchClocks (int newMoveNr)
16117 {
16118     long lastTickLength;
16119     TimeMark now;
16120     int flagged = FALSE;
16121
16122     GetTimeMark(&now);
16123
16124     if (StopClockTimer() && appData.clockMode) {
16125         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16126         if (!WhiteOnMove(forwardMostMove)) {
16127             if(blackNPS >= 0) lastTickLength = 0;
16128             blackTimeRemaining -= lastTickLength;
16129            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16130 //         if(pvInfoList[forwardMostMove].time == -1)
16131                  pvInfoList[forwardMostMove].time =               // use GUI time
16132                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16133         } else {
16134            if(whiteNPS >= 0) lastTickLength = 0;
16135            whiteTimeRemaining -= lastTickLength;
16136            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16137 //         if(pvInfoList[forwardMostMove].time == -1)
16138                  pvInfoList[forwardMostMove].time =
16139                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16140         }
16141         flagged = CheckFlags();
16142     }
16143     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16144     CheckTimeControl();
16145
16146     if (flagged || !appData.clockMode) return;
16147
16148     switch (gameMode) {
16149       case MachinePlaysBlack:
16150       case MachinePlaysWhite:
16151       case BeginningOfGame:
16152         if (pausing) return;
16153         break;
16154
16155       case EditGame:
16156       case PlayFromGameFile:
16157       case IcsExamining:
16158         return;
16159
16160       default:
16161         break;
16162     }
16163
16164     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16165         if(WhiteOnMove(forwardMostMove))
16166              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16167         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16168     }
16169
16170     tickStartTM = now;
16171     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16172       whiteTimeRemaining : blackTimeRemaining);
16173     StartClockTimer(intendedTickLength);
16174 }
16175
16176
16177 /* Stop both clocks */
16178 void
16179 StopClocks ()
16180 {
16181     long lastTickLength;
16182     TimeMark now;
16183
16184     if (!StopClockTimer()) return;
16185     if (!appData.clockMode) return;
16186
16187     GetTimeMark(&now);
16188
16189     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16190     if (WhiteOnMove(forwardMostMove)) {
16191         if(whiteNPS >= 0) lastTickLength = 0;
16192         whiteTimeRemaining -= lastTickLength;
16193         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16194     } else {
16195         if(blackNPS >= 0) lastTickLength = 0;
16196         blackTimeRemaining -= lastTickLength;
16197         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16198     }
16199     CheckFlags();
16200 }
16201
16202 /* Start clock of player on move.  Time may have been reset, so
16203    if clock is already running, stop and restart it. */
16204 void
16205 StartClocks ()
16206 {
16207     (void) StopClockTimer(); /* in case it was running already */
16208     DisplayBothClocks();
16209     if (CheckFlags()) return;
16210
16211     if (!appData.clockMode) return;
16212     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16213
16214     GetTimeMark(&tickStartTM);
16215     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16216       whiteTimeRemaining : blackTimeRemaining);
16217
16218    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16219     whiteNPS = blackNPS = -1;
16220     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16221        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16222         whiteNPS = first.nps;
16223     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16224        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16225         blackNPS = first.nps;
16226     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16227         whiteNPS = second.nps;
16228     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16229         blackNPS = second.nps;
16230     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16231
16232     StartClockTimer(intendedTickLength);
16233 }
16234
16235 char *
16236 TimeString (long ms)
16237 {
16238     long second, minute, hour, day;
16239     char *sign = "";
16240     static char buf[32];
16241
16242     if (ms > 0 && ms <= 9900) {
16243       /* convert milliseconds to tenths, rounding up */
16244       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16245
16246       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16247       return buf;
16248     }
16249
16250     /* convert milliseconds to seconds, rounding up */
16251     /* use floating point to avoid strangeness of integer division
16252        with negative dividends on many machines */
16253     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16254
16255     if (second < 0) {
16256         sign = "-";
16257         second = -second;
16258     }
16259
16260     day = second / (60 * 60 * 24);
16261     second = second % (60 * 60 * 24);
16262     hour = second / (60 * 60);
16263     second = second % (60 * 60);
16264     minute = second / 60;
16265     second = second % 60;
16266
16267     if (day > 0)
16268       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16269               sign, day, hour, minute, second);
16270     else if (hour > 0)
16271       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16272     else
16273       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16274
16275     return buf;
16276 }
16277
16278
16279 /*
16280  * This is necessary because some C libraries aren't ANSI C compliant yet.
16281  */
16282 char *
16283 StrStr (char *string, char *match)
16284 {
16285     int i, length;
16286
16287     length = strlen(match);
16288
16289     for (i = strlen(string) - length; i >= 0; i--, string++)
16290       if (!strncmp(match, string, length))
16291         return string;
16292
16293     return NULL;
16294 }
16295
16296 char *
16297 StrCaseStr (char *string, char *match)
16298 {
16299     int i, j, length;
16300
16301     length = strlen(match);
16302
16303     for (i = strlen(string) - length; i >= 0; i--, string++) {
16304         for (j = 0; j < length; j++) {
16305             if (ToLower(match[j]) != ToLower(string[j]))
16306               break;
16307         }
16308         if (j == length) return string;
16309     }
16310
16311     return NULL;
16312 }
16313
16314 #ifndef _amigados
16315 int
16316 StrCaseCmp (char *s1, char *s2)
16317 {
16318     char c1, c2;
16319
16320     for (;;) {
16321         c1 = ToLower(*s1++);
16322         c2 = ToLower(*s2++);
16323         if (c1 > c2) return 1;
16324         if (c1 < c2) return -1;
16325         if (c1 == NULLCHAR) return 0;
16326     }
16327 }
16328
16329
16330 int
16331 ToLower (int c)
16332 {
16333     return isupper(c) ? tolower(c) : c;
16334 }
16335
16336
16337 int
16338 ToUpper (int c)
16339 {
16340     return islower(c) ? toupper(c) : c;
16341 }
16342 #endif /* !_amigados    */
16343
16344 char *
16345 StrSave (char *s)
16346 {
16347   char *ret;
16348
16349   if ((ret = (char *) malloc(strlen(s) + 1)))
16350     {
16351       safeStrCpy(ret, s, strlen(s)+1);
16352     }
16353   return ret;
16354 }
16355
16356 char *
16357 StrSavePtr (char *s, char **savePtr)
16358 {
16359     if (*savePtr) {
16360         free(*savePtr);
16361     }
16362     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16363       safeStrCpy(*savePtr, s, strlen(s)+1);
16364     }
16365     return(*savePtr);
16366 }
16367
16368 char *
16369 PGNDate ()
16370 {
16371     time_t clock;
16372     struct tm *tm;
16373     char buf[MSG_SIZ];
16374
16375     clock = time((time_t *)NULL);
16376     tm = localtime(&clock);
16377     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16378             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16379     return StrSave(buf);
16380 }
16381
16382
16383 char *
16384 PositionToFEN (int move, char *overrideCastling)
16385 {
16386     int i, j, fromX, fromY, toX, toY;
16387     int whiteToPlay;
16388     char buf[MSG_SIZ];
16389     char *p, *q;
16390     int emptycount;
16391     ChessSquare piece;
16392
16393     whiteToPlay = (gameMode == EditPosition) ?
16394       !blackPlaysFirst : (move % 2 == 0);
16395     p = buf;
16396
16397     /* Piece placement data */
16398     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16399         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16400         emptycount = 0;
16401         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16402             if (boards[move][i][j] == EmptySquare) {
16403                 emptycount++;
16404             } else { ChessSquare piece = boards[move][i][j];
16405                 if (emptycount > 0) {
16406                     if(emptycount<10) /* [HGM] can be >= 10 */
16407                         *p++ = '0' + emptycount;
16408                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16409                     emptycount = 0;
16410                 }
16411                 if(PieceToChar(piece) == '+') {
16412                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16413                     *p++ = '+';
16414                     piece = (ChessSquare)(DEMOTED piece);
16415                 }
16416                 *p++ = PieceToChar(piece);
16417                 if(p[-1] == '~') {
16418                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16419                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16420                     *p++ = '~';
16421                 }
16422             }
16423         }
16424         if (emptycount > 0) {
16425             if(emptycount<10) /* [HGM] can be >= 10 */
16426                 *p++ = '0' + emptycount;
16427             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16428             emptycount = 0;
16429         }
16430         *p++ = '/';
16431     }
16432     *(p - 1) = ' ';
16433
16434     /* [HGM] print Crazyhouse or Shogi holdings */
16435     if( gameInfo.holdingsWidth ) {
16436         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16437         q = p;
16438         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16439             piece = boards[move][i][BOARD_WIDTH-1];
16440             if( piece != EmptySquare )
16441               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16442                   *p++ = PieceToChar(piece);
16443         }
16444         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16445             piece = boards[move][BOARD_HEIGHT-i-1][0];
16446             if( piece != EmptySquare )
16447               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16448                   *p++ = PieceToChar(piece);
16449         }
16450
16451         if( q == p ) *p++ = '-';
16452         *p++ = ']';
16453         *p++ = ' ';
16454     }
16455
16456     /* Active color */
16457     *p++ = whiteToPlay ? 'w' : 'b';
16458     *p++ = ' ';
16459
16460   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16461     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16462   } else {
16463   if(nrCastlingRights) {
16464      q = p;
16465      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16466        /* [HGM] write directly from rights */
16467            if(boards[move][CASTLING][2] != NoRights &&
16468               boards[move][CASTLING][0] != NoRights   )
16469                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16470            if(boards[move][CASTLING][2] != NoRights &&
16471               boards[move][CASTLING][1] != NoRights   )
16472                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16473            if(boards[move][CASTLING][5] != NoRights &&
16474               boards[move][CASTLING][3] != NoRights   )
16475                 *p++ = boards[move][CASTLING][3] + AAA;
16476            if(boards[move][CASTLING][5] != NoRights &&
16477               boards[move][CASTLING][4] != NoRights   )
16478                 *p++ = boards[move][CASTLING][4] + AAA;
16479      } else {
16480
16481         /* [HGM] write true castling rights */
16482         if( nrCastlingRights == 6 ) {
16483             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16484                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16485             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16486                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16487             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16488                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16489             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16490                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16491         }
16492      }
16493      if (q == p) *p++ = '-'; /* No castling rights */
16494      *p++ = ' ';
16495   }
16496
16497   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16498      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16499     /* En passant target square */
16500     if (move > backwardMostMove) {
16501         fromX = moveList[move - 1][0] - AAA;
16502         fromY = moveList[move - 1][1] - ONE;
16503         toX = moveList[move - 1][2] - AAA;
16504         toY = moveList[move - 1][3] - ONE;
16505         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16506             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16507             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16508             fromX == toX) {
16509             /* 2-square pawn move just happened */
16510             *p++ = toX + AAA;
16511             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16512         } else {
16513             *p++ = '-';
16514         }
16515     } else if(move == backwardMostMove) {
16516         // [HGM] perhaps we should always do it like this, and forget the above?
16517         if((signed char)boards[move][EP_STATUS] >= 0) {
16518             *p++ = boards[move][EP_STATUS] + AAA;
16519             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16520         } else {
16521             *p++ = '-';
16522         }
16523     } else {
16524         *p++ = '-';
16525     }
16526     *p++ = ' ';
16527   }
16528   }
16529
16530     /* [HGM] find reversible plies */
16531     {   int i = 0, j=move;
16532
16533         if (appData.debugMode) { int k;
16534             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16535             for(k=backwardMostMove; k<=forwardMostMove; k++)
16536                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16537
16538         }
16539
16540         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16541         if( j == backwardMostMove ) i += initialRulePlies;
16542         sprintf(p, "%d ", i);
16543         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16544     }
16545     /* Fullmove number */
16546     sprintf(p, "%d", (move / 2) + 1);
16547
16548     return StrSave(buf);
16549 }
16550
16551 Boolean
16552 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16553 {
16554     int i, j;
16555     char *p, c;
16556     int emptycount;
16557     ChessSquare piece;
16558
16559     p = fen;
16560
16561     /* [HGM] by default clear Crazyhouse holdings, if present */
16562     if(gameInfo.holdingsWidth) {
16563        for(i=0; i<BOARD_HEIGHT; i++) {
16564            board[i][0]             = EmptySquare; /* black holdings */
16565            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16566            board[i][1]             = (ChessSquare) 0; /* black counts */
16567            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16568        }
16569     }
16570
16571     /* Piece placement data */
16572     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16573         j = 0;
16574         for (;;) {
16575             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16576                 if (*p == '/') p++;
16577                 emptycount = gameInfo.boardWidth - j;
16578                 while (emptycount--)
16579                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16580                 break;
16581 #if(BOARD_FILES >= 10)
16582             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16583                 p++; emptycount=10;
16584                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16585                 while (emptycount--)
16586                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16587 #endif
16588             } else if (isdigit(*p)) {
16589                 emptycount = *p++ - '0';
16590                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16591                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16592                 while (emptycount--)
16593                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16594             } else if (*p == '+' || isalpha(*p)) {
16595                 if (j >= gameInfo.boardWidth) return FALSE;
16596                 if(*p=='+') {
16597                     piece = CharToPiece(*++p);
16598                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16599                     piece = (ChessSquare) (PROMOTED piece ); p++;
16600                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16601                 } else piece = CharToPiece(*p++);
16602
16603                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16604                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16605                     piece = (ChessSquare) (PROMOTED piece);
16606                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16607                     p++;
16608                 }
16609                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16610             } else {
16611                 return FALSE;
16612             }
16613         }
16614     }
16615     while (*p == '/' || *p == ' ') p++;
16616
16617     /* [HGM] look for Crazyhouse holdings here */
16618     while(*p==' ') p++;
16619     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16620         if(*p == '[') p++;
16621         if(*p == '-' ) p++; /* empty holdings */ else {
16622             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16623             /* if we would allow FEN reading to set board size, we would   */
16624             /* have to add holdings and shift the board read so far here   */
16625             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16626                 p++;
16627                 if((int) piece >= (int) BlackPawn ) {
16628                     i = (int)piece - (int)BlackPawn;
16629                     i = PieceToNumber((ChessSquare)i);
16630                     if( i >= gameInfo.holdingsSize ) return FALSE;
16631                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16632                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16633                 } else {
16634                     i = (int)piece - (int)WhitePawn;
16635                     i = PieceToNumber((ChessSquare)i);
16636                     if( i >= gameInfo.holdingsSize ) return FALSE;
16637                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16638                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16639                 }
16640             }
16641         }
16642         if(*p == ']') p++;
16643     }
16644
16645     while(*p == ' ') p++;
16646
16647     /* Active color */
16648     c = *p++;
16649     if(appData.colorNickNames) {
16650       if( c == appData.colorNickNames[0] ) c = 'w'; else
16651       if( c == appData.colorNickNames[1] ) c = 'b';
16652     }
16653     switch (c) {
16654       case 'w':
16655         *blackPlaysFirst = FALSE;
16656         break;
16657       case 'b':
16658         *blackPlaysFirst = TRUE;
16659         break;
16660       default:
16661         return FALSE;
16662     }
16663
16664     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16665     /* return the extra info in global variiables             */
16666
16667     /* set defaults in case FEN is incomplete */
16668     board[EP_STATUS] = EP_UNKNOWN;
16669     for(i=0; i<nrCastlingRights; i++ ) {
16670         board[CASTLING][i] =
16671             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16672     }   /* assume possible unless obviously impossible */
16673     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16674     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16675     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16676                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16677     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16678     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16679     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16680                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16681     FENrulePlies = 0;
16682
16683     while(*p==' ') p++;
16684     if(nrCastlingRights) {
16685       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16686           /* castling indicator present, so default becomes no castlings */
16687           for(i=0; i<nrCastlingRights; i++ ) {
16688                  board[CASTLING][i] = NoRights;
16689           }
16690       }
16691       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16692              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16693              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16694              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16695         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16696
16697         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16698             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16699             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16700         }
16701         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16702             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16703         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16704                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16705         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16706                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16707         switch(c) {
16708           case'K':
16709               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16710               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16711               board[CASTLING][2] = whiteKingFile;
16712               break;
16713           case'Q':
16714               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16715               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16716               board[CASTLING][2] = whiteKingFile;
16717               break;
16718           case'k':
16719               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16720               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16721               board[CASTLING][5] = blackKingFile;
16722               break;
16723           case'q':
16724               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16725               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16726               board[CASTLING][5] = blackKingFile;
16727           case '-':
16728               break;
16729           default: /* FRC castlings */
16730               if(c >= 'a') { /* black rights */
16731                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16732                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16733                   if(i == BOARD_RGHT) break;
16734                   board[CASTLING][5] = i;
16735                   c -= AAA;
16736                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16737                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16738                   if(c > i)
16739                       board[CASTLING][3] = c;
16740                   else
16741                       board[CASTLING][4] = c;
16742               } else { /* white rights */
16743                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16744                     if(board[0][i] == WhiteKing) break;
16745                   if(i == BOARD_RGHT) break;
16746                   board[CASTLING][2] = i;
16747                   c -= AAA - 'a' + 'A';
16748                   if(board[0][c] >= WhiteKing) break;
16749                   if(c > i)
16750                       board[CASTLING][0] = c;
16751                   else
16752                       board[CASTLING][1] = c;
16753               }
16754         }
16755       }
16756       for(i=0; i<nrCastlingRights; i++)
16757         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16758     if (appData.debugMode) {
16759         fprintf(debugFP, "FEN castling rights:");
16760         for(i=0; i<nrCastlingRights; i++)
16761         fprintf(debugFP, " %d", board[CASTLING][i]);
16762         fprintf(debugFP, "\n");
16763     }
16764
16765       while(*p==' ') p++;
16766     }
16767
16768     /* read e.p. field in games that know e.p. capture */
16769     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16770        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16771       if(*p=='-') {
16772         p++; board[EP_STATUS] = EP_NONE;
16773       } else {
16774          char c = *p++ - AAA;
16775
16776          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16777          if(*p >= '0' && *p <='9') p++;
16778          board[EP_STATUS] = c;
16779       }
16780     }
16781
16782
16783     if(sscanf(p, "%d", &i) == 1) {
16784         FENrulePlies = i; /* 50-move ply counter */
16785         /* (The move number is still ignored)    */
16786     }
16787
16788     return TRUE;
16789 }
16790
16791 void
16792 EditPositionPasteFEN (char *fen)
16793 {
16794   if (fen != NULL) {
16795     Board initial_position;
16796
16797     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16798       DisplayError(_("Bad FEN position in clipboard"), 0);
16799       return ;
16800     } else {
16801       int savedBlackPlaysFirst = blackPlaysFirst;
16802       EditPositionEvent();
16803       blackPlaysFirst = savedBlackPlaysFirst;
16804       CopyBoard(boards[0], initial_position);
16805       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16806       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16807       DisplayBothClocks();
16808       DrawPosition(FALSE, boards[currentMove]);
16809     }
16810   }
16811 }
16812
16813 static char cseq[12] = "\\   ";
16814
16815 Boolean
16816 set_cont_sequence (char *new_seq)
16817 {
16818     int len;
16819     Boolean ret;
16820
16821     // handle bad attempts to set the sequence
16822         if (!new_seq)
16823                 return 0; // acceptable error - no debug
16824
16825     len = strlen(new_seq);
16826     ret = (len > 0) && (len < sizeof(cseq));
16827     if (ret)
16828       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16829     else if (appData.debugMode)
16830       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16831     return ret;
16832 }
16833
16834 /*
16835     reformat a source message so words don't cross the width boundary.  internal
16836     newlines are not removed.  returns the wrapped size (no null character unless
16837     included in source message).  If dest is NULL, only calculate the size required
16838     for the dest buffer.  lp argument indicats line position upon entry, and it's
16839     passed back upon exit.
16840 */
16841 int
16842 wrap (char *dest, char *src, int count, int width, int *lp)
16843 {
16844     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16845
16846     cseq_len = strlen(cseq);
16847     old_line = line = *lp;
16848     ansi = len = clen = 0;
16849
16850     for (i=0; i < count; i++)
16851     {
16852         if (src[i] == '\033')
16853             ansi = 1;
16854
16855         // if we hit the width, back up
16856         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16857         {
16858             // store i & len in case the word is too long
16859             old_i = i, old_len = len;
16860
16861             // find the end of the last word
16862             while (i && src[i] != ' ' && src[i] != '\n')
16863             {
16864                 i--;
16865                 len--;
16866             }
16867
16868             // word too long?  restore i & len before splitting it
16869             if ((old_i-i+clen) >= width)
16870             {
16871                 i = old_i;
16872                 len = old_len;
16873             }
16874
16875             // extra space?
16876             if (i && src[i-1] == ' ')
16877                 len--;
16878
16879             if (src[i] != ' ' && src[i] != '\n')
16880             {
16881                 i--;
16882                 if (len)
16883                     len--;
16884             }
16885
16886             // now append the newline and continuation sequence
16887             if (dest)
16888                 dest[len] = '\n';
16889             len++;
16890             if (dest)
16891                 strncpy(dest+len, cseq, cseq_len);
16892             len += cseq_len;
16893             line = cseq_len;
16894             clen = cseq_len;
16895             continue;
16896         }
16897
16898         if (dest)
16899             dest[len] = src[i];
16900         len++;
16901         if (!ansi)
16902             line++;
16903         if (src[i] == '\n')
16904             line = 0;
16905         if (src[i] == 'm')
16906             ansi = 0;
16907     }
16908     if (dest && appData.debugMode)
16909     {
16910         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16911             count, width, line, len, *lp);
16912         show_bytes(debugFP, src, count);
16913         fprintf(debugFP, "\ndest: ");
16914         show_bytes(debugFP, dest, len);
16915         fprintf(debugFP, "\n");
16916     }
16917     *lp = dest ? line : old_line;
16918
16919     return len;
16920 }
16921
16922 // [HGM] vari: routines for shelving variations
16923 Boolean modeRestore = FALSE;
16924
16925 void
16926 PushInner (int firstMove, int lastMove)
16927 {
16928         int i, j, nrMoves = lastMove - firstMove;
16929
16930         // push current tail of game on stack
16931         savedResult[storedGames] = gameInfo.result;
16932         savedDetails[storedGames] = gameInfo.resultDetails;
16933         gameInfo.resultDetails = NULL;
16934         savedFirst[storedGames] = firstMove;
16935         savedLast [storedGames] = lastMove;
16936         savedFramePtr[storedGames] = framePtr;
16937         framePtr -= nrMoves; // reserve space for the boards
16938         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16939             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16940             for(j=0; j<MOVE_LEN; j++)
16941                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16942             for(j=0; j<2*MOVE_LEN; j++)
16943                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16944             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16945             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16946             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16947             pvInfoList[firstMove+i-1].depth = 0;
16948             commentList[framePtr+i] = commentList[firstMove+i];
16949             commentList[firstMove+i] = NULL;
16950         }
16951
16952         storedGames++;
16953         forwardMostMove = firstMove; // truncate game so we can start variation
16954 }
16955
16956 void
16957 PushTail (int firstMove, int lastMove)
16958 {
16959         if(appData.icsActive) { // only in local mode
16960                 forwardMostMove = currentMove; // mimic old ICS behavior
16961                 return;
16962         }
16963         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16964
16965         PushInner(firstMove, lastMove);
16966         if(storedGames == 1) GreyRevert(FALSE);
16967         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16968 }
16969
16970 void
16971 PopInner (Boolean annotate)
16972 {
16973         int i, j, nrMoves;
16974         char buf[8000], moveBuf[20];
16975
16976         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16977         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16978         nrMoves = savedLast[storedGames] - currentMove;
16979         if(annotate) {
16980                 int cnt = 10;
16981                 if(!WhiteOnMove(currentMove))
16982                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16983                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16984                 for(i=currentMove; i<forwardMostMove; i++) {
16985                         if(WhiteOnMove(i))
16986                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16987                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16988                         strcat(buf, moveBuf);
16989                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16990                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16991                 }
16992                 strcat(buf, ")");
16993         }
16994         for(i=1; i<=nrMoves; i++) { // copy last variation back
16995             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16996             for(j=0; j<MOVE_LEN; j++)
16997                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16998             for(j=0; j<2*MOVE_LEN; j++)
16999                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17000             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17001             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17002             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17003             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17004             commentList[currentMove+i] = commentList[framePtr+i];
17005             commentList[framePtr+i] = NULL;
17006         }
17007         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17008         framePtr = savedFramePtr[storedGames];
17009         gameInfo.result = savedResult[storedGames];
17010         if(gameInfo.resultDetails != NULL) {
17011             free(gameInfo.resultDetails);
17012       }
17013         gameInfo.resultDetails = savedDetails[storedGames];
17014         forwardMostMove = currentMove + nrMoves;
17015 }
17016
17017 Boolean
17018 PopTail (Boolean annotate)
17019 {
17020         if(appData.icsActive) return FALSE; // only in local mode
17021         if(!storedGames) return FALSE; // sanity
17022         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17023
17024         PopInner(annotate);
17025         if(currentMove < forwardMostMove) ForwardEvent(); else
17026         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17027
17028         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17029         return TRUE;
17030 }
17031
17032 void
17033 CleanupTail ()
17034 {       // remove all shelved variations
17035         int i;
17036         for(i=0; i<storedGames; i++) {
17037             if(savedDetails[i])
17038                 free(savedDetails[i]);
17039             savedDetails[i] = NULL;
17040         }
17041         for(i=framePtr; i<MAX_MOVES; i++) {
17042                 if(commentList[i]) free(commentList[i]);
17043                 commentList[i] = NULL;
17044         }
17045         framePtr = MAX_MOVES-1;
17046         storedGames = 0;
17047 }
17048
17049 void
17050 LoadVariation (int index, char *text)
17051 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17052         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17053         int level = 0, move;
17054
17055         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17056         // first find outermost bracketing variation
17057         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17058             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17059                 if(*p == '{') wait = '}'; else
17060                 if(*p == '[') wait = ']'; else
17061                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17062                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17063             }
17064             if(*p == wait) wait = NULLCHAR; // closing ]} found
17065             p++;
17066         }
17067         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17068         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17069         end[1] = NULLCHAR; // clip off comment beyond variation
17070         ToNrEvent(currentMove-1);
17071         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17072         // kludge: use ParsePV() to append variation to game
17073         move = currentMove;
17074         ParsePV(start, TRUE, TRUE);
17075         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17076         ClearPremoveHighlights();
17077         CommentPopDown();
17078         ToNrEvent(currentMove+1);
17079 }
17080