Limit premove cancelling to click on from square
[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     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091         if( (len >= MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1489
1490 void
1491 InitBackEnd3 P((void))
1492 {
1493     GameMode initialMode;
1494     char buf[MSG_SIZ];
1495     int err, len;
1496
1497     InitChessProgram(&first, startedFromSetupPosition);
1498
1499     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1500         free(programVersion);
1501         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1502         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1503         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1504     }
1505
1506     if (appData.icsActive) {
1507 #ifdef WIN32
1508         /* [DM] Make a console window if needed [HGM] merged ifs */
1509         ConsoleCreate();
1510 #endif
1511         err = establish();
1512         if (err != 0)
1513           {
1514             if (*appData.icsCommPort != NULLCHAR)
1515               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1516                              appData.icsCommPort);
1517             else
1518               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1519                         appData.icsHost, appData.icsPort);
1520
1521             if( (len >= MSG_SIZ) && appData.debugMode )
1522               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1523
1524             DisplayFatalError(buf, err, 1);
1525             return;
1526         }
1527         SetICSMode();
1528         telnetISR =
1529           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1530         fromUserISR =
1531           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1532         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1533             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1534     } else if (appData.noChessProgram) {
1535         SetNCPMode();
1536     } else {
1537         SetGNUMode();
1538     }
1539
1540     if (*appData.cmailGameName != NULLCHAR) {
1541         SetCmailMode();
1542         OpenLoopback(&cmailPR);
1543         cmailISR =
1544           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1545     }
1546
1547     ThawUI();
1548     DisplayMessage("", "");
1549     if (StrCaseCmp(appData.initialMode, "") == 0) {
1550       initialMode = BeginningOfGame;
1551       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1552         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1553         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1554         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1555         ModeHighlight();
1556       }
1557     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1558       initialMode = TwoMachinesPlay;
1559     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1560       initialMode = AnalyzeFile;
1561     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1562       initialMode = AnalyzeMode;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1564       initialMode = MachinePlaysWhite;
1565     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1566       initialMode = MachinePlaysBlack;
1567     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1568       initialMode = EditGame;
1569     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1570       initialMode = EditPosition;
1571     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1572       initialMode = Training;
1573     } else {
1574       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1575       if( (len >= MSG_SIZ) && appData.debugMode )
1576         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1577
1578       DisplayFatalError(buf, 0, 2);
1579       return;
1580     }
1581
1582     if (appData.matchMode) {
1583         if(appData.tourneyFile[0]) { // start tourney from command line
1584             FILE *f;
1585             if(f = fopen(appData.tourneyFile, "r")) {
1586                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1587                 fclose(f);
1588                 appData.clockMode = TRUE;
1589                 SetGNUMode();
1590             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1591         }
1592         MatchEvent(TRUE);
1593     } else if (*appData.cmailGameName != NULLCHAR) {
1594         /* Set up cmail mode */
1595         ReloadCmailMsgEvent(TRUE);
1596     } else {
1597         /* Set up other modes */
1598         if (initialMode == AnalyzeFile) {
1599           if (*appData.loadGameFile == NULLCHAR) {
1600             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1601             return;
1602           }
1603         }
1604         if (*appData.loadGameFile != NULLCHAR) {
1605             (void) LoadGameFromFile(appData.loadGameFile,
1606                                     appData.loadGameIndex,
1607                                     appData.loadGameFile, TRUE);
1608         } else if (*appData.loadPositionFile != NULLCHAR) {
1609             (void) LoadPositionFromFile(appData.loadPositionFile,
1610                                         appData.loadPositionIndex,
1611                                         appData.loadPositionFile);
1612             /* [HGM] try to make self-starting even after FEN load */
1613             /* to allow automatic setup of fairy variants with wtm */
1614             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1615                 gameMode = BeginningOfGame;
1616                 setboardSpoiledMachineBlack = 1;
1617             }
1618             /* [HGM] loadPos: make that every new game uses the setup */
1619             /* from file as long as we do not switch variant          */
1620             if(!blackPlaysFirst) {
1621                 startedFromPositionFile = TRUE;
1622                 CopyBoard(filePosition, boards[0]);
1623             }
1624         }
1625         if (initialMode == AnalyzeMode) {
1626           if (appData.noChessProgram) {
1627             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1632             return;
1633           }
1634           AnalyzeModeEvent();
1635         } else if (initialMode == AnalyzeFile) {
1636           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1637           ShowThinkingEvent();
1638           AnalyzeFileEvent();
1639           AnalysisPeriodicEvent(1);
1640         } else if (initialMode == MachinePlaysWhite) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           MachineWhiteEvent();
1652         } else if (initialMode == MachinePlaysBlack) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineBlackEvent();
1664         } else if (initialMode == TwoMachinesPlay) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           TwoMachinesEvent();
1676         } else if (initialMode == EditGame) {
1677           EditGameEvent();
1678         } else if (initialMode == EditPosition) {
1679           EditPositionEvent();
1680         } else if (initialMode == Training) {
1681           if (*appData.loadGameFile == NULLCHAR) {
1682             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1683             return;
1684           }
1685           TrainingEvent();
1686         }
1687     }
1688 }
1689
1690 void
1691 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1692 {
1693     DisplayBook(current+1);
1694
1695     MoveHistorySet( movelist, first, last, current, pvInfoList );
1696
1697     EvalGraphSet( first, last, current, pvInfoList );
1698
1699     MakeEngineOutputTitle();
1700 }
1701
1702 /*
1703  * Establish will establish a contact to a remote host.port.
1704  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1705  *  used to talk to the host.
1706  * Returns 0 if okay, error code if not.
1707  */
1708 int
1709 establish ()
1710 {
1711     char buf[MSG_SIZ];
1712
1713     if (*appData.icsCommPort != NULLCHAR) {
1714         /* Talk to the host through a serial comm port */
1715         return OpenCommPort(appData.icsCommPort, &icsPR);
1716
1717     } else if (*appData.gateway != NULLCHAR) {
1718         if (*appData.remoteShell == NULLCHAR) {
1719             /* Use the rcmd protocol to run telnet program on a gateway host */
1720             snprintf(buf, sizeof(buf), "%s %s %s",
1721                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1722             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1723
1724         } else {
1725             /* Use the rsh program to run telnet program on a gateway host */
1726             if (*appData.remoteUser == NULLCHAR) {
1727                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1728                         appData.gateway, appData.telnetProgram,
1729                         appData.icsHost, appData.icsPort);
1730             } else {
1731                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1732                         appData.remoteShell, appData.gateway,
1733                         appData.remoteUser, appData.telnetProgram,
1734                         appData.icsHost, appData.icsPort);
1735             }
1736             return StartChildProcess(buf, "", &icsPR);
1737
1738         }
1739     } else if (appData.useTelnet) {
1740         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1741
1742     } else {
1743         /* TCP socket interface differs somewhat between
1744            Unix and NT; handle details in the front end.
1745            */
1746         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1747     }
1748 }
1749
1750 void
1751 EscapeExpand (char *p, char *q)
1752 {       // [HGM] initstring: routine to shape up string arguments
1753         while(*p++ = *q++) if(p[-1] == '\\')
1754             switch(*q++) {
1755                 case 'n': p[-1] = '\n'; break;
1756                 case 'r': p[-1] = '\r'; break;
1757                 case 't': p[-1] = '\t'; break;
1758                 case '\\': p[-1] = '\\'; break;
1759                 case 0: *p = 0; return;
1760                 default: p[-1] = q[-1]; break;
1761             }
1762 }
1763
1764 void
1765 show_bytes (FILE *fp, char *buf, int count)
1766 {
1767     while (count--) {
1768         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1769             fprintf(fp, "\\%03o", *buf & 0xff);
1770         } else {
1771             putc(*buf, fp);
1772         }
1773         buf++;
1774     }
1775     fflush(fp);
1776 }
1777
1778 /* Returns an errno value */
1779 int
1780 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1781 {
1782     char buf[8192], *p, *q, *buflim;
1783     int left, newcount, outcount;
1784
1785     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1786         *appData.gateway != NULLCHAR) {
1787         if (appData.debugMode) {
1788             fprintf(debugFP, ">ICS: ");
1789             show_bytes(debugFP, message, count);
1790             fprintf(debugFP, "\n");
1791         }
1792         return OutputToProcess(pr, message, count, outError);
1793     }
1794
1795     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1796     p = message;
1797     q = buf;
1798     left = count;
1799     newcount = 0;
1800     while (left) {
1801         if (q >= buflim) {
1802             if (appData.debugMode) {
1803                 fprintf(debugFP, ">ICS: ");
1804                 show_bytes(debugFP, buf, newcount);
1805                 fprintf(debugFP, "\n");
1806             }
1807             outcount = OutputToProcess(pr, buf, newcount, outError);
1808             if (outcount < newcount) return -1; /* to be sure */
1809             q = buf;
1810             newcount = 0;
1811         }
1812         if (*p == '\n') {
1813             *q++ = '\r';
1814             newcount++;
1815         } else if (((unsigned char) *p) == TN_IAC) {
1816             *q++ = (char) TN_IAC;
1817             newcount ++;
1818         }
1819         *q++ = *p++;
1820         newcount++;
1821         left--;
1822     }
1823     if (appData.debugMode) {
1824         fprintf(debugFP, ">ICS: ");
1825         show_bytes(debugFP, buf, newcount);
1826         fprintf(debugFP, "\n");
1827     }
1828     outcount = OutputToProcess(pr, buf, newcount, outError);
1829     if (outcount < newcount) return -1; /* to be sure */
1830     return count;
1831 }
1832
1833 void
1834 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1835 {
1836     int outError, outCount;
1837     static int gotEof = 0;
1838
1839     /* Pass data read from player on to ICS */
1840     if (count > 0) {
1841         gotEof = 0;
1842         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1843         if (outCount < count) {
1844             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1845         }
1846     } else if (count < 0) {
1847         RemoveInputSource(isr);
1848         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1849     } else if (gotEof++ > 0) {
1850         RemoveInputSource(isr);
1851         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1852     }
1853 }
1854
1855 void
1856 KeepAlive ()
1857 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1858     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1859     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1860     SendToICS("date\n");
1861     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1862 }
1863
1864 /* added routine for printf style output to ics */
1865 void
1866 ics_printf (char *format, ...)
1867 {
1868     char buffer[MSG_SIZ];
1869     va_list args;
1870
1871     va_start(args, format);
1872     vsnprintf(buffer, sizeof(buffer), format, args);
1873     buffer[sizeof(buffer)-1] = '\0';
1874     SendToICS(buffer);
1875     va_end(args);
1876 }
1877
1878 void
1879 SendToICS (char *s)
1880 {
1881     int count, outCount, outError;
1882
1883     if (icsPR == NoProc) return;
1884
1885     count = strlen(s);
1886     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892 /* This is used for sending logon scripts to the ICS. Sending
1893    without a delay causes problems when using timestamp on ICC
1894    (at least on my machine). */
1895 void
1896 SendToICSDelayed (char *s, long msdelay)
1897 {
1898     int count, outCount, outError;
1899
1900     if (icsPR == NoProc) return;
1901
1902     count = strlen(s);
1903     if (appData.debugMode) {
1904         fprintf(debugFP, ">ICS: ");
1905         show_bytes(debugFP, s, count);
1906         fprintf(debugFP, "\n");
1907     }
1908     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1909                                       msdelay);
1910     if (outCount < count) {
1911         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1912     }
1913 }
1914
1915
1916 /* Remove all highlighting escape sequences in s
1917    Also deletes any suffix starting with '('
1918    */
1919 char *
1920 StripHighlightAndTitle (char *s)
1921 {
1922     static char retbuf[MSG_SIZ];
1923     char *p = retbuf;
1924
1925     while (*s != NULLCHAR) {
1926         while (*s == '\033') {
1927             while (*s != NULLCHAR && !isalpha(*s)) s++;
1928             if (*s != NULLCHAR) s++;
1929         }
1930         while (*s != NULLCHAR && *s != '\033') {
1931             if (*s == '(' || *s == '[') {
1932                 *p = NULLCHAR;
1933                 return retbuf;
1934             }
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 /* Remove all highlighting escape sequences in s */
1943 char *
1944 StripHighlight (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             *p++ = *s++;
1956         }
1957     }
1958     *p = NULLCHAR;
1959     return retbuf;
1960 }
1961
1962 char *variantNames[] = VARIANT_NAMES;
1963 char *
1964 VariantName (VariantClass v)
1965 {
1966     return variantNames[v];
1967 }
1968
1969
1970 /* Identify a variant from the strings the chess servers use or the
1971    PGN Variant tag names we use. */
1972 VariantClass
1973 StringToVariant (char *e)
1974 {
1975     char *p;
1976     int wnum = -1;
1977     VariantClass v = VariantNormal;
1978     int i, found = FALSE;
1979     char buf[MSG_SIZ];
1980     int len;
1981
1982     if (!e) return v;
1983
1984     /* [HGM] skip over optional board-size prefixes */
1985     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1986         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1987         while( *e++ != '_');
1988     }
1989
1990     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1991         v = VariantNormal;
1992         found = TRUE;
1993     } else
1994     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1995       if (StrCaseStr(e, variantNames[i])) {
1996         v = (VariantClass) i;
1997         found = TRUE;
1998         break;
1999       }
2000     }
2001
2002     if (!found) {
2003       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2004           || StrCaseStr(e, "wild/fr")
2005           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2006         v = VariantFischeRandom;
2007       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2008                  (i = 1, p = StrCaseStr(e, "w"))) {
2009         p += i;
2010         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011         if (isdigit(*p)) {
2012           wnum = atoi(p);
2013         } else {
2014           wnum = -1;
2015         }
2016         switch (wnum) {
2017         case 0: /* FICS only, actually */
2018         case 1:
2019           /* Castling legal even if K starts on d-file */
2020           v = VariantWildCastle;
2021           break;
2022         case 2:
2023         case 3:
2024         case 4:
2025           /* Castling illegal even if K & R happen to start in
2026              normal positions. */
2027           v = VariantNoCastle;
2028           break;
2029         case 5:
2030         case 7:
2031         case 8:
2032         case 10:
2033         case 11:
2034         case 12:
2035         case 13:
2036         case 14:
2037         case 15:
2038         case 18:
2039         case 19:
2040           /* Castling legal iff K & R start in normal positions */
2041           v = VariantNormal;
2042           break;
2043         case 6:
2044         case 20:
2045         case 21:
2046           /* Special wilds for position setup; unclear what to do here */
2047           v = VariantLoadable;
2048           break;
2049         case 9:
2050           /* Bizarre ICC game */
2051           v = VariantTwoKings;
2052           break;
2053         case 16:
2054           v = VariantKriegspiel;
2055           break;
2056         case 17:
2057           v = VariantLosers;
2058           break;
2059         case 22:
2060           v = VariantFischeRandom;
2061           break;
2062         case 23:
2063           v = VariantCrazyhouse;
2064           break;
2065         case 24:
2066           v = VariantBughouse;
2067           break;
2068         case 25:
2069           v = Variant3Check;
2070           break;
2071         case 26:
2072           /* Not quite the same as FICS suicide! */
2073           v = VariantGiveaway;
2074           break;
2075         case 27:
2076           v = VariantAtomic;
2077           break;
2078         case 28:
2079           v = VariantShatranj;
2080           break;
2081
2082         /* Temporary names for future ICC types.  The name *will* change in
2083            the next xboard/WinBoard release after ICC defines it. */
2084         case 29:
2085           v = Variant29;
2086           break;
2087         case 30:
2088           v = Variant30;
2089           break;
2090         case 31:
2091           v = Variant31;
2092           break;
2093         case 32:
2094           v = Variant32;
2095           break;
2096         case 33:
2097           v = Variant33;
2098           break;
2099         case 34:
2100           v = Variant34;
2101           break;
2102         case 35:
2103           v = Variant35;
2104           break;
2105         case 36:
2106           v = Variant36;
2107           break;
2108         case 37:
2109           v = VariantShogi;
2110           break;
2111         case 38:
2112           v = VariantXiangqi;
2113           break;
2114         case 39:
2115           v = VariantCourier;
2116           break;
2117         case 40:
2118           v = VariantGothic;
2119           break;
2120         case 41:
2121           v = VariantCapablanca;
2122           break;
2123         case 42:
2124           v = VariantKnightmate;
2125           break;
2126         case 43:
2127           v = VariantFairy;
2128           break;
2129         case 44:
2130           v = VariantCylinder;
2131           break;
2132         case 45:
2133           v = VariantFalcon;
2134           break;
2135         case 46:
2136           v = VariantCapaRandom;
2137           break;
2138         case 47:
2139           v = VariantBerolina;
2140           break;
2141         case 48:
2142           v = VariantJanus;
2143           break;
2144         case 49:
2145           v = VariantSuper;
2146           break;
2147         case 50:
2148           v = VariantGreat;
2149           break;
2150         case -1:
2151           /* Found "wild" or "w" in the string but no number;
2152              must assume it's normal chess. */
2153           v = VariantNormal;
2154           break;
2155         default:
2156           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2157           if( (len >= MSG_SIZ) && appData.debugMode )
2158             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2159
2160           DisplayError(buf, 0);
2161           v = VariantUnknown;
2162           break;
2163         }
2164       }
2165     }
2166     if (appData.debugMode) {
2167       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2168               e, wnum, VariantName(v));
2169     }
2170     return v;
2171 }
2172
2173 static int leftover_start = 0, leftover_len = 0;
2174 char star_match[STAR_MATCH_N][MSG_SIZ];
2175
2176 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2177    advance *index beyond it, and set leftover_start to the new value of
2178    *index; else return FALSE.  If pattern contains the character '*', it
2179    matches any sequence of characters not containing '\r', '\n', or the
2180    character following the '*' (if any), and the matched sequence(s) are
2181    copied into star_match.
2182    */
2183 int
2184 looking_at ( char *buf, int *index, char *pattern)
2185 {
2186     char *bufp = &buf[*index], *patternp = pattern;
2187     int star_count = 0;
2188     char *matchp = star_match[0];
2189
2190     for (;;) {
2191         if (*patternp == NULLCHAR) {
2192             *index = leftover_start = bufp - buf;
2193             *matchp = NULLCHAR;
2194             return TRUE;
2195         }
2196         if (*bufp == NULLCHAR) return FALSE;
2197         if (*patternp == '*') {
2198             if (*bufp == *(patternp + 1)) {
2199                 *matchp = NULLCHAR;
2200                 matchp = star_match[++star_count];
2201                 patternp += 2;
2202                 bufp++;
2203                 continue;
2204             } else if (*bufp == '\n' || *bufp == '\r') {
2205                 patternp++;
2206                 if (*patternp == NULLCHAR)
2207                   continue;
2208                 else
2209                   return FALSE;
2210             } else {
2211                 *matchp++ = *bufp++;
2212                 continue;
2213             }
2214         }
2215         if (*patternp != *bufp) return FALSE;
2216         patternp++;
2217         bufp++;
2218     }
2219 }
2220
2221 void
2222 SendToPlayer (char *data, int length)
2223 {
2224     int error, outCount;
2225     outCount = OutputToProcess(NoProc, data, length, &error);
2226     if (outCount < length) {
2227         DisplayFatalError(_("Error writing to display"), error, 1);
2228     }
2229 }
2230
2231 void
2232 PackHolding (char packed[], char *holding)
2233 {
2234     char *p = holding;
2235     char *q = packed;
2236     int runlength = 0;
2237     int curr = 9999;
2238     do {
2239         if (*p == curr) {
2240             runlength++;
2241         } else {
2242             switch (runlength) {
2243               case 0:
2244                 break;
2245               case 1:
2246                 *q++ = curr;
2247                 break;
2248               case 2:
2249                 *q++ = curr;
2250                 *q++ = curr;
2251                 break;
2252               default:
2253                 sprintf(q, "%d", runlength);
2254                 while (*q) q++;
2255                 *q++ = curr;
2256                 break;
2257             }
2258             runlength = 1;
2259             curr = *p;
2260         }
2261     } while (*p++);
2262     *q = NULLCHAR;
2263 }
2264
2265 /* Telnet protocol requests from the front end */
2266 void
2267 TelnetRequest (unsigned char ddww, unsigned char option)
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho ()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch (Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd (int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot (int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd (int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine (char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph ()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int
2615 SeekGraphClick (ClickType click, int x, int y, int moving)
2616 {
2617     static int lastDown = 0, displayed = 0, lastSecond;
2618     if(y < 0) return FALSE;
2619     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2620         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2621         if(!seekGraphUp) return FALSE;
2622         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2623         DrawPosition(TRUE, NULL);
2624         return TRUE;
2625     }
2626     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2627         if(click == Release || moving) return FALSE;
2628         nrOfSeekAds = 0;
2629         soughtPending = TRUE;
2630         SendToICS(ics_prefix);
2631         SendToICS("sought\n"); // should this be "sought all"?
2632     } else { // issue challenge based on clicked ad
2633         int dist = 10000; int i, closest = 0, second = 0;
2634         for(i=0; i<nrOfSeekAds; i++) {
2635             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2636             if(d < dist) { dist = d; closest = i; }
2637             second += (d - zList[i] < 120); // count in-range ads
2638             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2639         }
2640         if(dist < 120) {
2641             char buf[MSG_SIZ];
2642             second = (second > 1);
2643             if(displayed != closest || second != lastSecond) {
2644                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2645                 lastSecond = second; displayed = closest;
2646             }
2647             if(click == Press) {
2648                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2649                 lastDown = closest;
2650                 return TRUE;
2651             } // on press 'hit', only show info
2652             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2653             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2654             SendToICS(ics_prefix);
2655             SendToICS(buf);
2656             return TRUE; // let incoming board of started game pop down the graph
2657         } else if(click == Release) { // release 'miss' is ignored
2658             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2659             if(moving == 2) { // right up-click
2660                 nrOfSeekAds = 0; // refresh graph
2661                 soughtPending = TRUE;
2662                 SendToICS(ics_prefix);
2663                 SendToICS("sought\n"); // should this be "sought all"?
2664             }
2665             return TRUE;
2666         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2667         // press miss or release hit 'pop down' seek graph
2668         seekGraphUp = FALSE;
2669         DrawPosition(TRUE, NULL);
2670     }
2671     return TRUE;
2672 }
2673
2674 void
2675 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2676 {
2677 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2678 #define STARTED_NONE 0
2679 #define STARTED_MOVES 1
2680 #define STARTED_BOARD 2
2681 #define STARTED_OBSERVE 3
2682 #define STARTED_HOLDINGS 4
2683 #define STARTED_CHATTER 5
2684 #define STARTED_COMMENT 6
2685 #define STARTED_MOVES_NOHIDE 7
2686
2687     static int started = STARTED_NONE;
2688     static char parse[20000];
2689     static int parse_pos = 0;
2690     static char buf[BUF_SIZE + 1];
2691     static int firstTime = TRUE, intfSet = FALSE;
2692     static ColorClass prevColor = ColorNormal;
2693     static int savingComment = FALSE;
2694     static int cmatch = 0; // continuation sequence match
2695     char *bp;
2696     char str[MSG_SIZ];
2697     int i, oldi;
2698     int buf_len;
2699     int next_out;
2700     int tkind;
2701     int backup;    /* [DM] For zippy color lines */
2702     char *p;
2703     char talker[MSG_SIZ]; // [HGM] chat
2704     int channel;
2705
2706     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2707
2708     if (appData.debugMode) {
2709       if (!error) {
2710         fprintf(debugFP, "<ICS: ");
2711         show_bytes(debugFP, data, count);
2712         fprintf(debugFP, "\n");
2713       }
2714     }
2715
2716     if (appData.debugMode) { int f = forwardMostMove;
2717         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2718                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2719                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2720     }
2721     if (count > 0) {
2722         /* If last read ended with a partial line that we couldn't parse,
2723            prepend it to the new read and try again. */
2724         if (leftover_len > 0) {
2725             for (i=0; i<leftover_len; i++)
2726               buf[i] = buf[leftover_start + i];
2727         }
2728
2729     /* copy new characters into the buffer */
2730     bp = buf + leftover_len;
2731     buf_len=leftover_len;
2732     for (i=0; i<count; i++)
2733     {
2734         // ignore these
2735         if (data[i] == '\r')
2736             continue;
2737
2738         // join lines split by ICS?
2739         if (!appData.noJoin)
2740         {
2741             /*
2742                 Joining just consists of finding matches against the
2743                 continuation sequence, and discarding that sequence
2744                 if found instead of copying it.  So, until a match
2745                 fails, there's nothing to do since it might be the
2746                 complete sequence, and thus, something we don't want
2747                 copied.
2748             */
2749             if (data[i] == cont_seq[cmatch])
2750             {
2751                 cmatch++;
2752                 if (cmatch == strlen(cont_seq))
2753                 {
2754                     cmatch = 0; // complete match.  just reset the counter
2755
2756                     /*
2757                         it's possible for the ICS to not include the space
2758                         at the end of the last word, making our [correct]
2759                         join operation fuse two separate words.  the server
2760                         does this when the space occurs at the width setting.
2761                     */
2762                     if (!buf_len || buf[buf_len-1] != ' ')
2763                     {
2764                         *bp++ = ' ';
2765                         buf_len++;
2766                     }
2767                 }
2768                 continue;
2769             }
2770             else if (cmatch)
2771             {
2772                 /*
2773                     match failed, so we have to copy what matched before
2774                     falling through and copying this character.  In reality,
2775                     this will only ever be just the newline character, but
2776                     it doesn't hurt to be precise.
2777                 */
2778                 strncpy(bp, cont_seq, cmatch);
2779                 bp += cmatch;
2780                 buf_len += cmatch;
2781                 cmatch = 0;
2782             }
2783         }
2784
2785         // copy this char
2786         *bp++ = data[i];
2787         buf_len++;
2788     }
2789
2790         buf[buf_len] = NULLCHAR;
2791 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2792         next_out = 0;
2793         leftover_start = 0;
2794
2795         i = 0;
2796         while (i < buf_len) {
2797             /* Deal with part of the TELNET option negotiation
2798                protocol.  We refuse to do anything beyond the
2799                defaults, except that we allow the WILL ECHO option,
2800                which ICS uses to turn off password echoing when we are
2801                directly connected to it.  We reject this option
2802                if localLineEditing mode is on (always on in xboard)
2803                and we are talking to port 23, which might be a real
2804                telnet server that will try to keep WILL ECHO on permanently.
2805              */
2806             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2807                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2808                 unsigned char option;
2809                 oldi = i;
2810                 switch ((unsigned char) buf[++i]) {
2811                   case TN_WILL:
2812                     if (appData.debugMode)
2813                       fprintf(debugFP, "\n<WILL ");
2814                     switch (option = (unsigned char) buf[++i]) {
2815                       case TN_ECHO:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "ECHO ");
2818                         /* Reply only if this is a change, according
2819                            to the protocol rules. */
2820                         if (remoteEchoOption) break;
2821                         if (appData.localLineEditing &&
2822                             atoi(appData.icsPort) == TN_PORT) {
2823                             TelnetRequest(TN_DONT, TN_ECHO);
2824                         } else {
2825                             EchoOff();
2826                             TelnetRequest(TN_DO, TN_ECHO);
2827                             remoteEchoOption = TRUE;
2828                         }
2829                         break;
2830                       default:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "%d ", option);
2833                         /* Whatever this is, we don't want it. */
2834                         TelnetRequest(TN_DONT, option);
2835                         break;
2836                     }
2837                     break;
2838                   case TN_WONT:
2839                     if (appData.debugMode)
2840                       fprintf(debugFP, "\n<WONT ");
2841                     switch (option = (unsigned char) buf[++i]) {
2842                       case TN_ECHO:
2843                         if (appData.debugMode)
2844                           fprintf(debugFP, "ECHO ");
2845                         /* Reply only if this is a change, according
2846                            to the protocol rules. */
2847                         if (!remoteEchoOption) break;
2848                         EchoOn();
2849                         TelnetRequest(TN_DONT, TN_ECHO);
2850                         remoteEchoOption = FALSE;
2851                         break;
2852                       default:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", (unsigned char) option);
2855                         /* Whatever this is, it must already be turned
2856                            off, because we never agree to turn on
2857                            anything non-default, so according to the
2858                            protocol rules, we don't reply. */
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DO:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DO ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         /* Whatever this is, we refuse to do it. */
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         TelnetRequest(TN_WONT, option);
2871                         break;
2872                     }
2873                     break;
2874                   case TN_DONT:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<DONT ");
2877                     switch (option = (unsigned char) buf[++i]) {
2878                       default:
2879                         if (appData.debugMode)
2880                           fprintf(debugFP, "%d ", option);
2881                         /* Whatever this is, we are already not doing
2882                            it, because we never agree to do anything
2883                            non-default, so according to the protocol
2884                            rules, we don't reply. */
2885                         break;
2886                     }
2887                     break;
2888                   case TN_IAC:
2889                     if (appData.debugMode)
2890                       fprintf(debugFP, "\n<IAC ");
2891                     /* Doubled IAC; pass it through */
2892                     i--;
2893                     break;
2894                   default:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2897                     /* Drop all other telnet commands on the floor */
2898                     break;
2899                 }
2900                 if (oldi > next_out)
2901                   SendToPlayer(&buf[next_out], oldi - next_out);
2902                 if (++i > next_out)
2903                   next_out = i;
2904                 continue;
2905             }
2906
2907             /* OK, this at least will *usually* work */
2908             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2909                 loggedOn = TRUE;
2910             }
2911
2912             if (loggedOn && !intfSet) {
2913                 if (ics_type == ICS_ICC) {
2914                   snprintf(str, MSG_SIZ,
2915                           "/set-quietly interface %s\n/set-quietly style 12\n",
2916                           programVersion);
2917                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2918                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2919                 } else if (ics_type == ICS_CHESSNET) {
2920                   snprintf(str, MSG_SIZ, "/style 12\n");
2921                 } else {
2922                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2923                   strcat(str, programVersion);
2924                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2927 #ifdef WIN32
2928                   strcat(str, "$iset nohighlight 1\n");
2929 #endif
2930                   strcat(str, "$iset lock 1\n$style 12\n");
2931                 }
2932                 SendToICS(str);
2933                 NotifyFrontendLogin();
2934                 intfSet = TRUE;
2935             }
2936
2937             if (started == STARTED_COMMENT) {
2938                 /* Accumulate characters in comment */
2939                 parse[parse_pos++] = buf[i];
2940                 if (buf[i] == '\n') {
2941                     parse[parse_pos] = NULLCHAR;
2942                     if(chattingPartner>=0) {
2943                         char mess[MSG_SIZ];
2944                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2945                         OutputChatMessage(chattingPartner, mess);
2946                         chattingPartner = -1;
2947                         next_out = i+1; // [HGM] suppress printing in ICS window
2948                     } else
2949                     if(!suppressKibitz) // [HGM] kibitz
2950                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2951                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2952                         int nrDigit = 0, nrAlph = 0, j;
2953                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2954                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2955                         parse[parse_pos] = NULLCHAR;
2956                         // try to be smart: if it does not look like search info, it should go to
2957                         // ICS interaction window after all, not to engine-output window.
2958                         for(j=0; j<parse_pos; j++) { // count letters and digits
2959                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2960                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2961                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2962                         }
2963                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2964                             int depth=0; float score;
2965                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2966                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2967                                 pvInfoList[forwardMostMove-1].depth = depth;
2968                                 pvInfoList[forwardMostMove-1].score = 100*score;
2969                             }
2970                             OutputKibitz(suppressKibitz, parse);
2971                         } else {
2972                             char tmp[MSG_SIZ];
2973                             if(gameMode == IcsObserving) // restore original ICS messages
2974                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2975                             else
2976                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2977                             SendToPlayer(tmp, strlen(tmp));
2978                         }
2979                         next_out = i+1; // [HGM] suppress printing in ICS window
2980                     }
2981                     started = STARTED_NONE;
2982                 } else {
2983                     /* Don't match patterns against characters in comment */
2984                     i++;
2985                     continue;
2986                 }
2987             }
2988             if (started == STARTED_CHATTER) {
2989                 if (buf[i] != '\n') {
2990                     /* Don't match patterns against characters in chatter */
2991                     i++;
2992                     continue;
2993                 }
2994                 started = STARTED_NONE;
2995                 if(suppressKibitz) next_out = i+1;
2996             }
2997
2998             /* Kludge to deal with rcmd protocol */
2999             if (firstTime && looking_at(buf, &i, "\001*")) {
3000                 DisplayFatalError(&buf[1], 0, 1);
3001                 continue;
3002             } else {
3003                 firstTime = FALSE;
3004             }
3005
3006             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3007                 ics_type = ICS_ICC;
3008                 ics_prefix = "/";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3014                 ics_type = ICS_FICS;
3015                 ics_prefix = "$";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3021                 ics_type = ICS_CHESSNET;
3022                 ics_prefix = "/";
3023                 if (appData.debugMode)
3024                   fprintf(debugFP, "ics_type %d\n", ics_type);
3025                 continue;
3026             }
3027
3028             if (!loggedOn &&
3029                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3030                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3031                  looking_at(buf, &i, "will be \"*\""))) {
3032               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3033               continue;
3034             }
3035
3036             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3037               char buf[MSG_SIZ];
3038               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3039               DisplayIcsInteractionTitle(buf);
3040               have_set_title = TRUE;
3041             }
3042
3043             /* skip finger notes */
3044             if (started == STARTED_NONE &&
3045                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3046                  (buf[i] == '1' && buf[i+1] == '0')) &&
3047                 buf[i+2] == ':' && buf[i+3] == ' ') {
3048               started = STARTED_CHATTER;
3049               i += 3;
3050               continue;
3051             }
3052
3053             oldi = i;
3054             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3055             if(appData.seekGraph) {
3056                 if(soughtPending && MatchSoughtLine(buf+i)) {
3057                     i = strstr(buf+i, "rated") - buf;
3058                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3059                     next_out = leftover_start = i;
3060                     started = STARTED_CHATTER;
3061                     suppressKibitz = TRUE;
3062                     continue;
3063                 }
3064                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3065                         && looking_at(buf, &i, "* ads displayed")) {
3066                     soughtPending = FALSE;
3067                     seekGraphUp = TRUE;
3068                     DrawSeekGraph();
3069                     continue;
3070                 }
3071                 if(appData.autoRefresh) {
3072                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3073                         int s = (ics_type == ICS_ICC); // ICC format differs
3074                         if(seekGraphUp)
3075                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3076                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3077                         looking_at(buf, &i, "*% "); // eat prompt
3078                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i; // suppress
3081                         continue;
3082                     }
3083                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3084                         char *p = star_match[0];
3085                         while(*p) {
3086                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3087                             while(*p && *p++ != ' '); // next
3088                         }
3089                         looking_at(buf, &i, "*% "); // eat prompt
3090                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3091                         next_out = i;
3092                         continue;
3093                     }
3094                 }
3095             }
3096
3097             /* skip formula vars */
3098             if (started == STARTED_NONE &&
3099                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3100               started = STARTED_CHATTER;
3101               i += 3;
3102               continue;
3103             }
3104
3105             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3106             if (appData.autoKibitz && started == STARTED_NONE &&
3107                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3108                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3109                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3110                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3111                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3112                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3113                         suppressKibitz = TRUE;
3114                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115                         next_out = i;
3116                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3117                                 && (gameMode == IcsPlayingWhite)) ||
3118                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3119                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3120                             started = STARTED_CHATTER; // own kibitz we simply discard
3121                         else {
3122                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3123                             parse_pos = 0; parse[0] = NULLCHAR;
3124                             savingComment = TRUE;
3125                             suppressKibitz = gameMode != IcsObserving ? 2 :
3126                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3127                         }
3128                         continue;
3129                 } else
3130                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3131                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3132                          && atoi(star_match[0])) {
3133                     // suppress the acknowledgements of our own autoKibitz
3134                     char *p;
3135                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3137                     SendToPlayer(star_match[0], strlen(star_match[0]));
3138                     if(looking_at(buf, &i, "*% ")) // eat prompt
3139                         suppressKibitz = FALSE;
3140                     next_out = i;
3141                     continue;
3142                 }
3143             } // [HGM] kibitz: end of patch
3144
3145             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3146
3147             // [HGM] chat: intercept tells by users for which we have an open chat window
3148             channel = -1;
3149             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3150                                            looking_at(buf, &i, "* whispers:") ||
3151                                            looking_at(buf, &i, "* kibitzes:") ||
3152                                            looking_at(buf, &i, "* shouts:") ||
3153                                            looking_at(buf, &i, "* c-shouts:") ||
3154                                            looking_at(buf, &i, "--> * ") ||
3155                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3156                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3157                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3159                 int p;
3160                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3161                 chattingPartner = -1;
3162
3163                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3164                 for(p=0; p<MAX_CHAT; p++) {
3165                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3166                     talker[0] = '['; strcat(talker, "] ");
3167                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3168                     chattingPartner = p; break;
3169                     }
3170                 } else
3171                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(!strcmp("kibitzes", chatPartner[p])) {
3174                         talker[0] = '['; strcat(talker, "] ");
3175                         chattingPartner = p; break;
3176                     }
3177                 } else
3178                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3179                 for(p=0; p<MAX_CHAT; p++) {
3180                     if(!strcmp("whispers", chatPartner[p])) {
3181                         talker[0] = '['; strcat(talker, "] ");
3182                         chattingPartner = p; break;
3183                     }
3184                 } else
3185                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3186                   if(buf[i-8] == '-' && buf[i-3] == 't')
3187                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3188                     if(!strcmp("c-shouts", chatPartner[p])) {
3189                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                   if(chattingPartner < 0)
3194                   for(p=0; p<MAX_CHAT; p++) {
3195                     if(!strcmp("shouts", chatPartner[p])) {
3196                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3197                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3198                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                 }
3203                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3204                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3205                     talker[0] = 0; Colorize(ColorTell, FALSE);
3206                     chattingPartner = p; break;
3207                 }
3208                 if(chattingPartner<0) i = oldi; else {
3209                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3210                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     started = STARTED_COMMENT;
3213                     parse_pos = 0; parse[0] = NULLCHAR;
3214                     savingComment = 3 + chattingPartner; // counts as TRUE
3215                     suppressKibitz = TRUE;
3216                     continue;
3217                 }
3218             } // [HGM] chat: end of patch
3219
3220           backup = i;
3221             if (appData.zippyTalk || appData.zippyPlay) {
3222                 /* [DM] Backup address for color zippy lines */
3223 #if ZIPPY
3224                if (loggedOn == TRUE)
3225                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3226                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3227 #endif
3228             } // [DM] 'else { ' deleted
3229                 if (
3230                     /* Regular tells and says */
3231                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3232                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3233                     looking_at(buf, &i, "* says: ") ||
3234                     /* Don't color "message" or "messages" output */
3235                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3236                     looking_at(buf, &i, "*. * at *:*: ") ||
3237                     looking_at(buf, &i, "--* (*:*): ") ||
3238                     /* Message notifications (same color as tells) */
3239                     looking_at(buf, &i, "* has left a message ") ||
3240                     looking_at(buf, &i, "* just sent you a message:\n") ||
3241                     /* Whispers and kibitzes */
3242                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3243                     looking_at(buf, &i, "* kibitzes: ") ||
3244                     /* Channel tells */
3245                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3246
3247                   if (tkind == 1 && strchr(star_match[0], ':')) {
3248                       /* Avoid "tells you:" spoofs in channels */
3249                      tkind = 3;
3250                   }
3251                   if (star_match[0][0] == NULLCHAR ||
3252                       strchr(star_match[0], ' ') ||
3253                       (tkind == 3 && strchr(star_match[1], ' '))) {
3254                     /* Reject bogus matches */
3255                     i = oldi;
3256                   } else {
3257                     if (appData.colorize) {
3258                       if (oldi > next_out) {
3259                         SendToPlayer(&buf[next_out], oldi - next_out);
3260                         next_out = oldi;
3261                       }
3262                       switch (tkind) {
3263                       case 1:
3264                         Colorize(ColorTell, FALSE);
3265                         curColor = ColorTell;
3266                         break;
3267                       case 2:
3268                         Colorize(ColorKibitz, FALSE);
3269                         curColor = ColorKibitz;
3270                         break;
3271                       case 3:
3272                         p = strrchr(star_match[1], '(');
3273                         if (p == NULL) {
3274                           p = star_match[1];
3275                         } else {
3276                           p++;
3277                         }
3278                         if (atoi(p) == 1) {
3279                           Colorize(ColorChannel1, FALSE);
3280                           curColor = ColorChannel1;
3281                         } else {
3282                           Colorize(ColorChannel, FALSE);
3283                           curColor = ColorChannel;
3284                         }
3285                         break;
3286                       case 5:
3287                         curColor = ColorNormal;
3288                         break;
3289                       }
3290                     }
3291                     if (started == STARTED_NONE && appData.autoComment &&
3292                         (gameMode == IcsObserving ||
3293                          gameMode == IcsPlayingWhite ||
3294                          gameMode == IcsPlayingBlack)) {
3295                       parse_pos = i - oldi;
3296                       memcpy(parse, &buf[oldi], parse_pos);
3297                       parse[parse_pos] = NULLCHAR;
3298                       started = STARTED_COMMENT;
3299                       savingComment = TRUE;
3300                     } else {
3301                       started = STARTED_CHATTER;
3302                       savingComment = FALSE;
3303                     }
3304                     loggedOn = TRUE;
3305                     continue;
3306                   }
3307                 }
3308
3309                 if (looking_at(buf, &i, "* s-shouts: ") ||
3310                     looking_at(buf, &i, "* c-shouts: ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorSShout, FALSE);
3317                         curColor = ColorSShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at(buf, &i, "--->")) {
3325                     loggedOn = TRUE;
3326                     continue;
3327                 }
3328
3329                 if (looking_at(buf, &i, "* shouts: ") ||
3330                     looking_at(buf, &i, "--> ")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorShout, FALSE);
3337                         curColor = ColorShout;
3338                     }
3339                     loggedOn = TRUE;
3340                     started = STARTED_CHATTER;
3341                     continue;
3342                 }
3343
3344                 if (looking_at( buf, &i, "Challenge:")) {
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorChallenge, FALSE);
3351                         curColor = ColorChallenge;
3352                     }
3353                     loggedOn = TRUE;
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* offers you") ||
3358                     looking_at(buf, &i, "* offers to be") ||
3359                     looking_at(buf, &i, "* would like to") ||
3360                     looking_at(buf, &i, "* requests to") ||
3361                     looking_at(buf, &i, "Your opponent offers") ||
3362                     looking_at(buf, &i, "Your opponent requests")) {
3363
3364                     if (appData.colorize) {
3365                         if (oldi > next_out) {
3366                             SendToPlayer(&buf[next_out], oldi - next_out);
3367                             next_out = oldi;
3368                         }
3369                         Colorize(ColorRequest, FALSE);
3370                         curColor = ColorRequest;
3371                     }
3372                     continue;
3373                 }
3374
3375                 if (looking_at(buf, &i, "* (*) seeking")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorSeek, FALSE);
3382                         curColor = ColorSeek;
3383                     }
3384                     continue;
3385             }
3386
3387           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3388
3389             if (looking_at(buf, &i, "\\   ")) {
3390                 if (prevColor != ColorNormal) {
3391                     if (oldi > next_out) {
3392                         SendToPlayer(&buf[next_out], oldi - next_out);
3393                         next_out = oldi;
3394                     }
3395                     Colorize(prevColor, TRUE);
3396                     curColor = prevColor;
3397                 }
3398                 if (savingComment) {
3399                     parse_pos = i - oldi;
3400                     memcpy(parse, &buf[oldi], parse_pos);
3401                     parse[parse_pos] = NULLCHAR;
3402                     started = STARTED_COMMENT;
3403                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3404                         chattingPartner = savingComment - 3; // kludge to remember the box
3405                 } else {
3406                     started = STARTED_CHATTER;
3407                 }
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "Black Strength :") ||
3412                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3413                 looking_at(buf, &i, "<10>") ||
3414                 looking_at(buf, &i, "#@#")) {
3415                 /* Wrong board style */
3416                 loggedOn = TRUE;
3417                 SendToICS(ics_prefix);
3418                 SendToICS("set style 12\n");
3419                 SendToICS(ics_prefix);
3420                 SendToICS("refresh\n");
3421                 continue;
3422             }
3423
3424             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3425                 ICSInitScript();
3426                 have_sent_ICS_logon = 1;
3427                 continue;
3428             }
3429
3430             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3431                 (looking_at(buf, &i, "\n<12> ") ||
3432                  looking_at(buf, &i, "<12> "))) {
3433                 loggedOn = TRUE;
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_BOARD;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3444                 looking_at(buf, &i, "<b1> ")) {
3445                 if (oldi > next_out) {
3446                     SendToPlayer(&buf[next_out], oldi - next_out);
3447                 }
3448                 next_out = i;
3449                 started = STARTED_HOLDINGS;
3450                 parse_pos = 0;
3451                 continue;
3452             }
3453
3454             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3455                 loggedOn = TRUE;
3456                 /* Header for a move list -- first line */
3457
3458                 switch (ics_getting_history) {
3459                   case H_FALSE:
3460                     switch (gameMode) {
3461                       case IcsIdle:
3462                       case BeginningOfGame:
3463                         /* User typed "moves" or "oldmoves" while we
3464                            were idle.  Pretend we asked for these
3465                            moves and soak them up so user can step
3466                            through them and/or save them.
3467                            */
3468                         Reset(FALSE, TRUE);
3469                         gameMode = IcsObserving;
3470                         ModeHighlight();
3471                         ics_gamenum = -1;
3472                         ics_getting_history = H_GOT_UNREQ_HEADER;
3473                         break;
3474                       case EditGame: /*?*/
3475                       case EditPosition: /*?*/
3476                         /* Should above feature work in these modes too? */
3477                         /* For now it doesn't */
3478                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3479                         break;
3480                       default:
3481                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3482                         break;
3483                     }
3484                     break;
3485                   case H_REQUESTED:
3486                     /* Is this the right one? */
3487                     if (gameInfo.white && gameInfo.black &&
3488                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3489                         strcmp(gameInfo.black, star_match[2]) == 0) {
3490                         /* All is well */
3491                         ics_getting_history = H_GOT_REQ_HEADER;
3492                     }
3493                     break;
3494                   case H_GOT_REQ_HEADER:
3495                   case H_GOT_UNREQ_HEADER:
3496                   case H_GOT_UNWANTED_HEADER:
3497                   case H_GETTING_MOVES:
3498                     /* Should not happen */
3499                     DisplayError(_("Error gathering move list: two headers"), 0);
3500                     ics_getting_history = H_FALSE;
3501                     break;
3502                 }
3503
3504                 /* Save player ratings into gameInfo if needed */
3505                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3506                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3507                     (gameInfo.whiteRating == -1 ||
3508                      gameInfo.blackRating == -1)) {
3509
3510                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3511                     gameInfo.blackRating = string_to_rating(star_match[3]);
3512                     if (appData.debugMode)
3513                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3514                               gameInfo.whiteRating, gameInfo.blackRating);
3515                 }
3516                 continue;
3517             }
3518
3519             if (looking_at(buf, &i,
3520               "* * match, initial time: * minute*, increment: * second")) {
3521                 /* Header for a move list -- second line */
3522                 /* Initial board will follow if this is a wild game */
3523                 if (gameInfo.event != NULL) free(gameInfo.event);
3524                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3525                 gameInfo.event = StrSave(str);
3526                 /* [HGM] we switched variant. Translate boards if needed. */
3527                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3528                 continue;
3529             }
3530
3531             if (looking_at(buf, &i, "Move  ")) {
3532                 /* Beginning of a move list */
3533                 switch (ics_getting_history) {
3534                   case H_FALSE:
3535                     /* Normally should not happen */
3536                     /* Maybe user hit reset while we were parsing */
3537                     break;
3538                   case H_REQUESTED:
3539                     /* Happens if we are ignoring a move list that is not
3540                      * the one we just requested.  Common if the user
3541                      * tries to observe two games without turning off
3542                      * getMoveList */
3543                     break;
3544                   case H_GETTING_MOVES:
3545                     /* Should not happen */
3546                     DisplayError(_("Error gathering move list: nested"), 0);
3547                     ics_getting_history = H_FALSE;
3548                     break;
3549                   case H_GOT_REQ_HEADER:
3550                     ics_getting_history = H_GETTING_MOVES;
3551                     started = STARTED_MOVES;
3552                     parse_pos = 0;
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                     }
3556                     break;
3557                   case H_GOT_UNREQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES_NOHIDE;
3560                     parse_pos = 0;
3561                     break;
3562                   case H_GOT_UNWANTED_HEADER:
3563                     ics_getting_history = H_FALSE;
3564                     break;
3565                 }
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "% ") ||
3570                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3571                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3572                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3573                     soughtPending = FALSE;
3574                     seekGraphUp = TRUE;
3575                     DrawSeekGraph();
3576                 }
3577                 if(suppressKibitz) next_out = i;
3578                 savingComment = FALSE;
3579                 suppressKibitz = 0;
3580                 switch (started) {
3581                   case STARTED_MOVES:
3582                   case STARTED_MOVES_NOHIDE:
3583                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3584                     parse[parse_pos + i - oldi] = NULLCHAR;
3585                     ParseGameHistory(parse);
3586 #if ZIPPY
3587                     if (appData.zippyPlay && first.initDone) {
3588                         FeedMovesToProgram(&first, forwardMostMove);
3589                         if (gameMode == IcsPlayingWhite) {
3590                             if (WhiteOnMove(forwardMostMove)) {
3591                                 if (first.sendTime) {
3592                                   if (first.useColors) {
3593                                     SendToProgram("black\n", &first);
3594                                   }
3595                                   SendTimeRemaining(&first, TRUE);
3596                                 }
3597                                 if (first.useColors) {
3598                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3599                                 }
3600                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3601                                 first.maybeThinking = TRUE;
3602                             } else {
3603                                 if (first.usePlayother) {
3604                                   if (first.sendTime) {
3605                                     SendTimeRemaining(&first, TRUE);
3606                                   }
3607                                   SendToProgram("playother\n", &first);
3608                                   firstMove = FALSE;
3609                                 } else {
3610                                   firstMove = TRUE;
3611                                 }
3612                             }
3613                         } else if (gameMode == IcsPlayingBlack) {
3614                             if (!WhiteOnMove(forwardMostMove)) {
3615                                 if (first.sendTime) {
3616                                   if (first.useColors) {
3617                                     SendToProgram("white\n", &first);
3618                                   }
3619                                   SendTimeRemaining(&first, FALSE);
3620                                 }
3621                                 if (first.useColors) {
3622                                   SendToProgram("black\n", &first);
3623                                 }
3624                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3625                                 first.maybeThinking = TRUE;
3626                             } else {
3627                                 if (first.usePlayother) {
3628                                   if (first.sendTime) {
3629                                     SendTimeRemaining(&first, FALSE);
3630                                   }
3631                                   SendToProgram("playother\n", &first);
3632                                   firstMove = FALSE;
3633                                 } else {
3634                                   firstMove = TRUE;
3635                                 }
3636                             }
3637                         }
3638                     }
3639 #endif
3640                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3641                         /* Moves came from oldmoves or moves command
3642                            while we weren't doing anything else.
3643                            */
3644                         currentMove = forwardMostMove;
3645                         ClearHighlights();/*!!could figure this out*/
3646                         flipView = appData.flipView;
3647                         DrawPosition(TRUE, boards[currentMove]);
3648                         DisplayBothClocks();
3649                         snprintf(str, MSG_SIZ, "%s %s %s",
3650                                 gameInfo.white, _("vs."),  gameInfo.black);
3651                         DisplayTitle(str);
3652                         gameMode = IcsIdle;
3653                     } else {
3654                         /* Moves were history of an active game */
3655                         if (gameInfo.resultDetails != NULL) {
3656                             free(gameInfo.resultDetails);
3657                             gameInfo.resultDetails = NULL;
3658                         }
3659                     }
3660                     HistorySet(parseList, backwardMostMove,
3661                                forwardMostMove, currentMove-1);
3662                     DisplayMove(currentMove - 1);
3663                     if (started == STARTED_MOVES) next_out = i;
3664                     started = STARTED_NONE;
3665                     ics_getting_history = H_FALSE;
3666                     break;
3667
3668                   case STARTED_OBSERVE:
3669                     started = STARTED_NONE;
3670                     SendToICS(ics_prefix);
3671                     SendToICS("refresh\n");
3672                     break;
3673
3674                   default:
3675                     break;
3676                 }
3677                 if(bookHit) { // [HGM] book: simulate book reply
3678                     static char bookMove[MSG_SIZ]; // a bit generous?
3679
3680                     programStats.nodes = programStats.depth = programStats.time =
3681                     programStats.score = programStats.got_only_move = 0;
3682                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3683
3684                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3685                     strcat(bookMove, bookHit);
3686                     HandleMachineMove(bookMove, &first);
3687                 }
3688                 continue;
3689             }
3690
3691             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3692                  started == STARTED_HOLDINGS ||
3693                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3694                 /* Accumulate characters in move list or board */
3695                 parse[parse_pos++] = buf[i];
3696             }
3697
3698             /* Start of game messages.  Mostly we detect start of game
3699                when the first board image arrives.  On some versions
3700                of the ICS, though, we need to do a "refresh" after starting
3701                to observe in order to get the current board right away. */
3702             if (looking_at(buf, &i, "Adding game * to observation list")) {
3703                 started = STARTED_OBSERVE;
3704                 continue;
3705             }
3706
3707             /* Handle auto-observe */
3708             if (appData.autoObserve &&
3709                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3710                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3711                 char *player;
3712                 /* Choose the player that was highlighted, if any. */
3713                 if (star_match[0][0] == '\033' ||
3714                     star_match[1][0] != '\033') {
3715                     player = star_match[0];
3716                 } else {
3717                     player = star_match[2];
3718                 }
3719                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3720                         ics_prefix, StripHighlightAndTitle(player));
3721                 SendToICS(str);
3722
3723                 /* Save ratings from notify string */
3724                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3725                 player1Rating = string_to_rating(star_match[1]);
3726                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3727                 player2Rating = string_to_rating(star_match[3]);
3728
3729                 if (appData.debugMode)
3730                   fprintf(debugFP,
3731                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3732                           player1Name, player1Rating,
3733                           player2Name, player2Rating);
3734
3735                 continue;
3736             }
3737
3738             /* Deal with automatic examine mode after a game,
3739                and with IcsObserving -> IcsExamining transition */
3740             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3741                 looking_at(buf, &i, "has made you an examiner of game *")) {
3742
3743                 int gamenum = atoi(star_match[0]);
3744                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3745                     gamenum == ics_gamenum) {
3746                     /* We were already playing or observing this game;
3747                        no need to refetch history */
3748                     gameMode = IcsExamining;
3749                     if (pausing) {
3750                         pauseExamForwardMostMove = forwardMostMove;
3751                     } else if (currentMove < forwardMostMove) {
3752                         ForwardInner(forwardMostMove);
3753                     }
3754                 } else {
3755                     /* I don't think this case really can happen */
3756                     SendToICS(ics_prefix);
3757                     SendToICS("refresh\n");
3758                 }
3759                 continue;
3760             }
3761
3762             /* Error messages */
3763 //          if (ics_user_moved) {
3764             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3765                 if (looking_at(buf, &i, "Illegal move") ||
3766                     looking_at(buf, &i, "Not a legal move") ||
3767                     looking_at(buf, &i, "Your king is in check") ||
3768                     looking_at(buf, &i, "It isn't your turn") ||
3769                     looking_at(buf, &i, "It is not your move")) {
3770                     /* Illegal move */
3771                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3772                         currentMove = forwardMostMove-1;
3773                         DisplayMove(currentMove - 1); /* before DMError */
3774                         DrawPosition(FALSE, boards[currentMove]);
3775                         SwitchClocks(forwardMostMove-1); // [HGM] race
3776                         DisplayBothClocks();
3777                     }
3778                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3779                     ics_user_moved = 0;
3780                     continue;
3781                 }
3782             }
3783
3784             if (looking_at(buf, &i, "still have time") ||
3785                 looking_at(buf, &i, "not out of time") ||
3786                 looking_at(buf, &i, "either player is out of time") ||
3787                 looking_at(buf, &i, "has timeseal; checking")) {
3788                 /* We must have called his flag a little too soon */
3789                 whiteFlag = blackFlag = FALSE;
3790                 continue;
3791             }
3792
3793             if (looking_at(buf, &i, "added * seconds to") ||
3794                 looking_at(buf, &i, "seconds were added to")) {
3795                 /* Update the clocks */
3796                 SendToICS(ics_prefix);
3797                 SendToICS("refresh\n");
3798                 continue;
3799             }
3800
3801             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3802                 ics_clock_paused = TRUE;
3803                 StopClocks();
3804                 continue;
3805             }
3806
3807             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3808                 ics_clock_paused = FALSE;
3809                 StartClocks();
3810                 continue;
3811             }
3812
3813             /* Grab player ratings from the Creating: message.
3814                Note we have to check for the special case when
3815                the ICS inserts things like [white] or [black]. */
3816             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3817                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3818                 /* star_matches:
3819                    0    player 1 name (not necessarily white)
3820                    1    player 1 rating
3821                    2    empty, white, or black (IGNORED)
3822                    3    player 2 name (not necessarily black)
3823                    4    player 2 rating
3824
3825                    The names/ratings are sorted out when the game
3826                    actually starts (below).
3827                 */
3828                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3829                 player1Rating = string_to_rating(star_match[1]);
3830                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3831                 player2Rating = string_to_rating(star_match[4]);
3832
3833                 if (appData.debugMode)
3834                   fprintf(debugFP,
3835                           "Ratings from 'Creating:' %s %d, %s %d\n",
3836                           player1Name, player1Rating,
3837                           player2Name, player2Rating);
3838
3839                 continue;
3840             }
3841
3842             /* Improved generic start/end-of-game messages */
3843             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3844                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3845                 /* If tkind == 0: */
3846                 /* star_match[0] is the game number */
3847                 /*           [1] is the white player's name */
3848                 /*           [2] is the black player's name */
3849                 /* For end-of-game: */
3850                 /*           [3] is the reason for the game end */
3851                 /*           [4] is a PGN end game-token, preceded by " " */
3852                 /* For start-of-game: */
3853                 /*           [3] begins with "Creating" or "Continuing" */
3854                 /*           [4] is " *" or empty (don't care). */
3855                 int gamenum = atoi(star_match[0]);
3856                 char *whitename, *blackname, *why, *endtoken;
3857                 ChessMove endtype = EndOfFile;
3858
3859                 if (tkind == 0) {
3860                   whitename = star_match[1];
3861                   blackname = star_match[2];
3862                   why = star_match[3];
3863                   endtoken = star_match[4];
3864                 } else {
3865                   whitename = star_match[1];
3866                   blackname = star_match[3];
3867                   why = star_match[5];
3868                   endtoken = star_match[6];
3869                 }
3870
3871                 /* Game start messages */
3872                 if (strncmp(why, "Creating ", 9) == 0 ||
3873                     strncmp(why, "Continuing ", 11) == 0) {
3874                     gs_gamenum = gamenum;
3875                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3876                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3877                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3878 #if ZIPPY
3879                     if (appData.zippyPlay) {
3880                         ZippyGameStart(whitename, blackname);
3881                     }
3882 #endif /*ZIPPY*/
3883                     partnerBoardValid = FALSE; // [HGM] bughouse
3884                     continue;
3885                 }
3886
3887                 /* Game end messages */
3888                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3889                     ics_gamenum != gamenum) {
3890                     continue;
3891                 }
3892                 while (endtoken[0] == ' ') endtoken++;
3893                 switch (endtoken[0]) {
3894                   case '*':
3895                   default:
3896                     endtype = GameUnfinished;
3897                     break;
3898                   case '0':
3899                     endtype = BlackWins;
3900                     break;
3901                   case '1':
3902                     if (endtoken[1] == '/')
3903                       endtype = GameIsDrawn;
3904                     else
3905                       endtype = WhiteWins;
3906                     break;
3907                 }
3908                 GameEnds(endtype, why, GE_ICS);
3909 #if ZIPPY
3910                 if (appData.zippyPlay && first.initDone) {
3911                     ZippyGameEnd(endtype, why);
3912                     if (first.pr == NoProc) {
3913                       /* Start the next process early so that we'll
3914                          be ready for the next challenge */
3915                       StartChessProgram(&first);
3916                     }
3917                     /* Send "new" early, in case this command takes
3918                        a long time to finish, so that we'll be ready
3919                        for the next challenge. */
3920                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3921                     Reset(TRUE, TRUE);
3922                 }
3923 #endif /*ZIPPY*/
3924                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3925                 continue;
3926             }
3927
3928             if (looking_at(buf, &i, "Removing game * from observation") ||
3929                 looking_at(buf, &i, "no longer observing game *") ||
3930                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3931                 if (gameMode == IcsObserving &&
3932                     atoi(star_match[0]) == ics_gamenum)
3933                   {
3934                       /* icsEngineAnalyze */
3935                       if (appData.icsEngineAnalyze) {
3936                             ExitAnalyzeMode();
3937                             ModeHighlight();
3938                       }
3939                       StopClocks();
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             if (looking_at(buf, &i, "no longer examining game *")) {
3948                 if (gameMode == IcsExamining &&
3949                     atoi(star_match[0]) == ics_gamenum)
3950                   {
3951                       gameMode = IcsIdle;
3952                       ics_gamenum = -1;
3953                       ics_user_moved = FALSE;
3954                   }
3955                 continue;
3956             }
3957
3958             /* Advance leftover_start past any newlines we find,
3959                so only partial lines can get reparsed */
3960             if (looking_at(buf, &i, "\n")) {
3961                 prevColor = curColor;
3962                 if (curColor != ColorNormal) {
3963                     if (oldi > next_out) {
3964                         SendToPlayer(&buf[next_out], oldi - next_out);
3965                         next_out = oldi;
3966                     }
3967                     Colorize(ColorNormal, FALSE);
3968                     curColor = ColorNormal;
3969                 }
3970                 if (started == STARTED_BOARD) {
3971                     started = STARTED_NONE;
3972                     parse[parse_pos] = NULLCHAR;
3973                     ParseBoard12(parse);
3974                     ics_user_moved = 0;
3975
3976                     /* Send premove here */
3977                     if (appData.premove) {
3978                       char str[MSG_SIZ];
3979                       if (currentMove == 0 &&
3980                           gameMode == IcsPlayingWhite &&
3981                           appData.premoveWhite) {
3982                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                         SendToICS(str);
3986                       } else if (currentMove == 1 &&
3987                                  gameMode == IcsPlayingBlack &&
3988                                  appData.premoveBlack) {
3989                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3990                         if (appData.debugMode)
3991                           fprintf(debugFP, "Sending premove:\n");
3992                         SendToICS(str);
3993                       } else if (gotPremove) {
3994                         gotPremove = 0;
3995                         ClearPremoveHighlights();
3996                         if (appData.debugMode)
3997                           fprintf(debugFP, "Sending premove:\n");
3998                           UserMoveEvent(premoveFromX, premoveFromY,
3999                                         premoveToX, premoveToY,
4000                                         premovePromoChar);
4001                       }
4002                     }
4003
4004                     /* Usually suppress following prompt */
4005                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4006                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4007                         if (looking_at(buf, &i, "*% ")) {
4008                             savingComment = FALSE;
4009                             suppressKibitz = 0;
4010                         }
4011                     }
4012                     next_out = i;
4013                 } else if (started == STARTED_HOLDINGS) {
4014                     int gamenum;
4015                     char new_piece[MSG_SIZ];
4016                     started = STARTED_NONE;
4017                     parse[parse_pos] = NULLCHAR;
4018                     if (appData.debugMode)
4019                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4020                                                         parse, currentMove);
4021                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4022                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4023                         if (gameInfo.variant == VariantNormal) {
4024                           /* [HGM] We seem to switch variant during a game!
4025                            * Presumably no holdings were displayed, so we have
4026                            * to move the position two files to the right to
4027                            * create room for them!
4028                            */
4029                           VariantClass newVariant;
4030                           switch(gameInfo.boardWidth) { // base guess on board width
4031                                 case 9:  newVariant = VariantShogi; break;
4032                                 case 10: newVariant = VariantGreat; break;
4033                                 default: newVariant = VariantCrazyhouse; break;
4034                           }
4035                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4036                           /* Get a move list just to see the header, which
4037                              will tell us whether this is really bug or zh */
4038                           if (ics_getting_history == H_FALSE) {
4039                             ics_getting_history = H_REQUESTED;
4040                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4041                             SendToICS(str);
4042                           }
4043                         }
4044                         new_piece[0] = NULLCHAR;
4045                         sscanf(parse, "game %d white [%s black [%s <- %s",
4046                                &gamenum, white_holding, black_holding,
4047                                new_piece);
4048                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4049                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4050                         /* [HGM] copy holdings to board holdings area */
4051                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4052                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4053                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4054 #if ZIPPY
4055                         if (appData.zippyPlay && first.initDone) {
4056                             ZippyHoldings(white_holding, black_holding,
4057                                           new_piece);
4058                         }
4059 #endif /*ZIPPY*/
4060                         if (tinyLayout || smallLayout) {
4061                             char wh[16], bh[16];
4062                             PackHolding(wh, white_holding);
4063                             PackHolding(bh, black_holding);
4064                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4065                                     gameInfo.white, gameInfo.black);
4066                         } else {
4067                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4068                                     gameInfo.white, white_holding, _("vs."),
4069                                     gameInfo.black, black_holding);
4070                         }
4071                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4072                         DrawPosition(FALSE, boards[currentMove]);
4073                         DisplayTitle(str);
4074                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4075                         sscanf(parse, "game %d white [%s black [%s <- %s",
4076                                &gamenum, white_holding, black_holding,
4077                                new_piece);
4078                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4079                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4080                         /* [HGM] copy holdings to partner-board holdings area */
4081                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4082                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4083                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4084                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4085                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4086                       }
4087                     }
4088                     /* Suppress following prompt */
4089                     if (looking_at(buf, &i, "*% ")) {
4090                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4091                         savingComment = FALSE;
4092                         suppressKibitz = 0;
4093                     }
4094                     next_out = i;
4095                 }
4096                 continue;
4097             }
4098
4099             i++;                /* skip unparsed character and loop back */
4100         }
4101
4102         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4103 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4104 //          SendToPlayer(&buf[next_out], i - next_out);
4105             started != STARTED_HOLDINGS && leftover_start > next_out) {
4106             SendToPlayer(&buf[next_out], leftover_start - next_out);
4107             next_out = i;
4108         }
4109
4110         leftover_len = buf_len - leftover_start;
4111         /* if buffer ends with something we couldn't parse,
4112            reparse it after appending the next read */
4113
4114     } else if (count == 0) {
4115         RemoveInputSource(isr);
4116         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4117     } else {
4118         DisplayFatalError(_("Error reading from ICS"), error, 1);
4119     }
4120 }
4121
4122
4123 /* Board style 12 looks like this:
4124
4125    <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
4126
4127  * The "<12> " is stripped before it gets to this routine.  The two
4128  * trailing 0's (flip state and clock ticking) are later addition, and
4129  * some chess servers may not have them, or may have only the first.
4130  * Additional trailing fields may be added in the future.
4131  */
4132
4133 #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"
4134
4135 #define RELATION_OBSERVING_PLAYED    0
4136 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4137 #define RELATION_PLAYING_MYMOVE      1
4138 #define RELATION_PLAYING_NOTMYMOVE  -1
4139 #define RELATION_EXAMINING           2
4140 #define RELATION_ISOLATED_BOARD     -3
4141 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4142
4143 void
4144 ParseBoard12 (char *string)
4145 {
4146     GameMode newGameMode;
4147     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4148     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4149     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4150     char to_play, board_chars[200];
4151     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4152     char black[32], white[32];
4153     Board board;
4154     int prevMove = currentMove;
4155     int ticking = 2;
4156     ChessMove moveType;
4157     int fromX, fromY, toX, toY;
4158     char promoChar;
4159     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4160     char *bookHit = NULL; // [HGM] book
4161     Boolean weird = FALSE, reqFlag = FALSE;
4162
4163     fromX = fromY = toX = toY = -1;
4164
4165     newGame = FALSE;
4166
4167     if (appData.debugMode)
4168       fprintf(debugFP, _("Parsing board: %s\n"), string);
4169
4170     move_str[0] = NULLCHAR;
4171     elapsed_time[0] = NULLCHAR;
4172     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4173         int  i = 0, j;
4174         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4175             if(string[i] == ' ') { ranks++; files = 0; }
4176             else files++;
4177             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4178             i++;
4179         }
4180         for(j = 0; j <i; j++) board_chars[j] = string[j];
4181         board_chars[i] = '\0';
4182         string += i + 1;
4183     }
4184     n = sscanf(string, PATTERN, &to_play, &double_push,
4185                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4186                &gamenum, white, black, &relation, &basetime, &increment,
4187                &white_stren, &black_stren, &white_time, &black_time,
4188                &moveNum, str, elapsed_time, move_str, &ics_flip,
4189                &ticking);
4190
4191     if (n < 21) {
4192         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4193         DisplayError(str, 0);
4194         return;
4195     }
4196
4197     /* Convert the move number to internal form */
4198     moveNum = (moveNum - 1) * 2;
4199     if (to_play == 'B') moveNum++;
4200     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4201       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4202                         0, 1);
4203       return;
4204     }
4205
4206     switch (relation) {
4207       case RELATION_OBSERVING_PLAYED:
4208       case RELATION_OBSERVING_STATIC:
4209         if (gamenum == -1) {
4210             /* Old ICC buglet */
4211             relation = RELATION_OBSERVING_STATIC;
4212         }
4213         newGameMode = IcsObserving;
4214         break;
4215       case RELATION_PLAYING_MYMOVE:
4216       case RELATION_PLAYING_NOTMYMOVE:
4217         newGameMode =
4218           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4219             IcsPlayingWhite : IcsPlayingBlack;
4220         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4221         break;
4222       case RELATION_EXAMINING:
4223         newGameMode = IcsExamining;
4224         break;
4225       case RELATION_ISOLATED_BOARD:
4226       default:
4227         /* Just display this board.  If user was doing something else,
4228            we will forget about it until the next board comes. */
4229         newGameMode = IcsIdle;
4230         break;
4231       case RELATION_STARTING_POSITION:
4232         newGameMode = gameMode;
4233         break;
4234     }
4235
4236     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4237          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4238       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4239       char *toSqr;
4240       for (k = 0; k < ranks; k++) {
4241         for (j = 0; j < files; j++)
4242           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4243         if(gameInfo.holdingsWidth > 1) {
4244              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4245              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4246         }
4247       }
4248       CopyBoard(partnerBoard, board);
4249       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4250         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4251         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4252       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4253       if(toSqr = strchr(str, '-')) {
4254         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4255         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4256       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4257       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4258       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4259       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4260       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4261       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4262                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4263       DisplayMessage(partnerStatus, "");
4264         partnerBoardValid = TRUE;
4265       return;
4266     }
4267
4268     /* Modify behavior for initial board display on move listing
4269        of wild games.
4270        */
4271     switch (ics_getting_history) {
4272       case H_FALSE:
4273       case H_REQUESTED:
4274         break;
4275       case H_GOT_REQ_HEADER:
4276       case H_GOT_UNREQ_HEADER:
4277         /* This is the initial position of the current game */
4278         gamenum = ics_gamenum;
4279         moveNum = 0;            /* old ICS bug workaround */
4280         if (to_play == 'B') {
4281           startedFromSetupPosition = TRUE;
4282           blackPlaysFirst = TRUE;
4283           moveNum = 1;
4284           if (forwardMostMove == 0) forwardMostMove = 1;
4285           if (backwardMostMove == 0) backwardMostMove = 1;
4286           if (currentMove == 0) currentMove = 1;
4287         }
4288         newGameMode = gameMode;
4289         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4290         break;
4291       case H_GOT_UNWANTED_HEADER:
4292         /* This is an initial board that we don't want */
4293         return;
4294       case H_GETTING_MOVES:
4295         /* Should not happen */
4296         DisplayError(_("Error gathering move list: extra board"), 0);
4297         ics_getting_history = H_FALSE;
4298         return;
4299     }
4300
4301    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4302                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4303      /* [HGM] We seem to have switched variant unexpectedly
4304       * Try to guess new variant from board size
4305       */
4306           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4307           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4308           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4309           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4310           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4311           if(!weird) newVariant = VariantNormal;
4312           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4313           /* Get a move list just to see the header, which
4314              will tell us whether this is really bug or zh */
4315           if (ics_getting_history == H_FALSE) {
4316             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4317             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4318             SendToICS(str);
4319           }
4320     }
4321
4322     /* Take action if this is the first board of a new game, or of a
4323        different game than is currently being displayed.  */
4324     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4325         relation == RELATION_ISOLATED_BOARD) {
4326
4327         /* Forget the old game and get the history (if any) of the new one */
4328         if (gameMode != BeginningOfGame) {
4329           Reset(TRUE, TRUE);
4330         }
4331         newGame = TRUE;
4332         if (appData.autoRaiseBoard) BoardToTop();
4333         prevMove = -3;
4334         if (gamenum == -1) {
4335             newGameMode = IcsIdle;
4336         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4337                    appData.getMoveList && !reqFlag) {
4338             /* Need to get game history */
4339             ics_getting_history = H_REQUESTED;
4340             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4341             SendToICS(str);
4342         }
4343
4344         /* Initially flip the board to have black on the bottom if playing
4345            black or if the ICS flip flag is set, but let the user change
4346            it with the Flip View button. */
4347         flipView = appData.autoFlipView ?
4348           (newGameMode == IcsPlayingBlack) || ics_flip :
4349           appData.flipView;
4350
4351         /* Done with values from previous mode; copy in new ones */
4352         gameMode = newGameMode;
4353         ModeHighlight();
4354         ics_gamenum = gamenum;
4355         if (gamenum == gs_gamenum) {
4356             int klen = strlen(gs_kind);
4357             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4358             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4359             gameInfo.event = StrSave(str);
4360         } else {
4361             gameInfo.event = StrSave("ICS game");
4362         }
4363         gameInfo.site = StrSave(appData.icsHost);
4364         gameInfo.date = PGNDate();
4365         gameInfo.round = StrSave("-");
4366         gameInfo.white = StrSave(white);
4367         gameInfo.black = StrSave(black);
4368         timeControl = basetime * 60 * 1000;
4369         timeControl_2 = 0;
4370         timeIncrement = increment * 1000;
4371         movesPerSession = 0;
4372         gameInfo.timeControl = TimeControlTagValue();
4373         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4374   if (appData.debugMode) {
4375     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4376     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4377     setbuf(debugFP, NULL);
4378   }
4379
4380         gameInfo.outOfBook = NULL;
4381
4382         /* Do we have the ratings? */
4383         if (strcmp(player1Name, white) == 0 &&
4384             strcmp(player2Name, black) == 0) {
4385             if (appData.debugMode)
4386               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4387                       player1Rating, player2Rating);
4388             gameInfo.whiteRating = player1Rating;
4389             gameInfo.blackRating = player2Rating;
4390         } else if (strcmp(player2Name, white) == 0 &&
4391                    strcmp(player1Name, black) == 0) {
4392             if (appData.debugMode)
4393               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4394                       player2Rating, player1Rating);
4395             gameInfo.whiteRating = player2Rating;
4396             gameInfo.blackRating = player1Rating;
4397         }
4398         player1Name[0] = player2Name[0] = NULLCHAR;
4399
4400         /* Silence shouts if requested */
4401         if (appData.quietPlay &&
4402             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4403             SendToICS(ics_prefix);
4404             SendToICS("set shout 0\n");
4405         }
4406     }
4407
4408     /* Deal with midgame name changes */
4409     if (!newGame) {
4410         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4411             if (gameInfo.white) free(gameInfo.white);
4412             gameInfo.white = StrSave(white);
4413         }
4414         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4415             if (gameInfo.black) free(gameInfo.black);
4416             gameInfo.black = StrSave(black);
4417         }
4418     }
4419
4420     /* Throw away game result if anything actually changes in examine mode */
4421     if (gameMode == IcsExamining && !newGame) {
4422         gameInfo.result = GameUnfinished;
4423         if (gameInfo.resultDetails != NULL) {
4424             free(gameInfo.resultDetails);
4425             gameInfo.resultDetails = NULL;
4426         }
4427     }
4428
4429     /* In pausing && IcsExamining mode, we ignore boards coming
4430        in if they are in a different variation than we are. */
4431     if (pauseExamInvalid) return;
4432     if (pausing && gameMode == IcsExamining) {
4433         if (moveNum <= pauseExamForwardMostMove) {
4434             pauseExamInvalid = TRUE;
4435             forwardMostMove = pauseExamForwardMostMove;
4436             return;
4437         }
4438     }
4439
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4442   }
4443     /* Parse the board */
4444     for (k = 0; k < ranks; k++) {
4445       for (j = 0; j < files; j++)
4446         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4447       if(gameInfo.holdingsWidth > 1) {
4448            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4449            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4450       }
4451     }
4452     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4453       board[5][BOARD_RGHT+1] = WhiteAngel;
4454       board[6][BOARD_RGHT+1] = WhiteMarshall;
4455       board[1][0] = BlackMarshall;
4456       board[2][0] = BlackAngel;
4457       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4458     }
4459     CopyBoard(boards[moveNum], board);
4460     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4461     if (moveNum == 0) {
4462         startedFromSetupPosition =
4463           !CompareBoards(board, initialPosition);
4464         if(startedFromSetupPosition)
4465             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4466     }
4467
4468     /* [HGM] Set castling rights. Take the outermost Rooks,
4469        to make it also work for FRC opening positions. Note that board12
4470        is really defective for later FRC positions, as it has no way to
4471        indicate which Rook can castle if they are on the same side of King.
4472        For the initial position we grant rights to the outermost Rooks,
4473        and remember thos rights, and we then copy them on positions
4474        later in an FRC game. This means WB might not recognize castlings with
4475        Rooks that have moved back to their original position as illegal,
4476        but in ICS mode that is not its job anyway.
4477     */
4478     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4479     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4480
4481         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4482             if(board[0][i] == WhiteRook) j = i;
4483         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4484         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4485             if(board[0][i] == WhiteRook) j = i;
4486         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4487         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4488             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4489         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4490         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4491             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4492         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4493
4494         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4495         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4496         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4497             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4498         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4499             if(board[BOARD_HEIGHT-1][k] == bKing)
4500                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4501         if(gameInfo.variant == VariantTwoKings) {
4502             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4503             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4504             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4505         }
4506     } else { int r;
4507         r = boards[moveNum][CASTLING][0] = initialRights[0];
4508         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4509         r = boards[moveNum][CASTLING][1] = initialRights[1];
4510         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4511         r = boards[moveNum][CASTLING][3] = initialRights[3];
4512         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4513         r = boards[moveNum][CASTLING][4] = initialRights[4];
4514         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4515         /* wildcastle kludge: always assume King has rights */
4516         r = boards[moveNum][CASTLING][2] = initialRights[2];
4517         r = boards[moveNum][CASTLING][5] = initialRights[5];
4518     }
4519     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4520     boards[moveNum][EP_STATUS] = EP_NONE;
4521     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4522     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4523     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4524
4525
4526     if (ics_getting_history == H_GOT_REQ_HEADER ||
4527         ics_getting_history == H_GOT_UNREQ_HEADER) {
4528         /* This was an initial position from a move list, not
4529            the current position */
4530         return;
4531     }
4532
4533     /* Update currentMove and known move number limits */
4534     newMove = newGame || moveNum > forwardMostMove;
4535
4536     if (newGame) {
4537         forwardMostMove = backwardMostMove = currentMove = moveNum;
4538         if (gameMode == IcsExamining && moveNum == 0) {
4539           /* Workaround for ICS limitation: we are not told the wild
4540              type when starting to examine a game.  But if we ask for
4541              the move list, the move list header will tell us */
4542             ics_getting_history = H_REQUESTED;
4543             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4544             SendToICS(str);
4545         }
4546     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4547                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4548 #if ZIPPY
4549         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4550         /* [HGM] applied this also to an engine that is silently watching        */
4551         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4552             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4553             gameInfo.variant == currentlyInitializedVariant) {
4554           takeback = forwardMostMove - moveNum;
4555           for (i = 0; i < takeback; i++) {
4556             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4557             SendToProgram("undo\n", &first);
4558           }
4559         }
4560 #endif
4561
4562         forwardMostMove = moveNum;
4563         if (!pausing || currentMove > forwardMostMove)
4564           currentMove = forwardMostMove;
4565     } else {
4566         /* New part of history that is not contiguous with old part */
4567         if (pausing && gameMode == IcsExamining) {
4568             pauseExamInvalid = TRUE;
4569             forwardMostMove = pauseExamForwardMostMove;
4570             return;
4571         }
4572         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4573 #if ZIPPY
4574             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4575                 // [HGM] when we will receive the move list we now request, it will be
4576                 // fed to the engine from the first move on. So if the engine is not
4577                 // in the initial position now, bring it there.
4578                 InitChessProgram(&first, 0);
4579             }
4580 #endif
4581             ics_getting_history = H_REQUESTED;
4582             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4583             SendToICS(str);
4584         }
4585         forwardMostMove = backwardMostMove = currentMove = moveNum;
4586     }
4587
4588     /* Update the clocks */
4589     if (strchr(elapsed_time, '.')) {
4590       /* Time is in ms */
4591       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4592       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4593     } else {
4594       /* Time is in seconds */
4595       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4596       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4597     }
4598
4599
4600 #if ZIPPY
4601     if (appData.zippyPlay && newGame &&
4602         gameMode != IcsObserving && gameMode != IcsIdle &&
4603         gameMode != IcsExamining)
4604       ZippyFirstBoard(moveNum, basetime, increment);
4605 #endif
4606
4607     /* Put the move on the move list, first converting
4608        to canonical algebraic form. */
4609     if (moveNum > 0) {
4610   if (appData.debugMode) {
4611     if (appData.debugMode) { int f = forwardMostMove;
4612         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4613                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4614                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4615     }
4616     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4617     fprintf(debugFP, "moveNum = %d\n", moveNum);
4618     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4619     setbuf(debugFP, NULL);
4620   }
4621         if (moveNum <= backwardMostMove) {
4622             /* We don't know what the board looked like before
4623                this move.  Punt. */
4624           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4625             strcat(parseList[moveNum - 1], " ");
4626             strcat(parseList[moveNum - 1], elapsed_time);
4627             moveList[moveNum - 1][0] = NULLCHAR;
4628         } else if (strcmp(move_str, "none") == 0) {
4629             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4630             /* Again, we don't know what the board looked like;
4631                this is really the start of the game. */
4632             parseList[moveNum - 1][0] = NULLCHAR;
4633             moveList[moveNum - 1][0] = NULLCHAR;
4634             backwardMostMove = moveNum;
4635             startedFromSetupPosition = TRUE;
4636             fromX = fromY = toX = toY = -1;
4637         } else {
4638           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4639           //                 So we parse the long-algebraic move string in stead of the SAN move
4640           int valid; char buf[MSG_SIZ], *prom;
4641
4642           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4643                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4644           // str looks something like "Q/a1-a2"; kill the slash
4645           if(str[1] == '/')
4646             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4647           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4648           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4649                 strcat(buf, prom); // long move lacks promo specification!
4650           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4651                 if(appData.debugMode)
4652                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4653                 safeStrCpy(move_str, buf, MSG_SIZ);
4654           }
4655           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4656                                 &fromX, &fromY, &toX, &toY, &promoChar)
4657                || ParseOneMove(buf, moveNum - 1, &moveType,
4658                                 &fromX, &fromY, &toX, &toY, &promoChar);
4659           // end of long SAN patch
4660           if (valid) {
4661             (void) CoordsToAlgebraic(boards[moveNum - 1],
4662                                      PosFlags(moveNum - 1),
4663                                      fromY, fromX, toY, toX, promoChar,
4664                                      parseList[moveNum-1]);
4665             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4666               case MT_NONE:
4667               case MT_STALEMATE:
4668               default:
4669                 break;
4670               case MT_CHECK:
4671                 if(gameInfo.variant != VariantShogi)
4672                     strcat(parseList[moveNum - 1], "+");
4673                 break;
4674               case MT_CHECKMATE:
4675               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4676                 strcat(parseList[moveNum - 1], "#");
4677                 break;
4678             }
4679             strcat(parseList[moveNum - 1], " ");
4680             strcat(parseList[moveNum - 1], elapsed_time);
4681             /* currentMoveString is set as a side-effect of ParseOneMove */
4682             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4683             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4684             strcat(moveList[moveNum - 1], "\n");
4685
4686             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4687                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4688               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4689                 ChessSquare old, new = boards[moveNum][k][j];
4690                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4691                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4692                   if(old == new) continue;
4693                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4694                   else if(new == WhiteWazir || new == BlackWazir) {
4695                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4696                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4697                       else boards[moveNum][k][j] = old; // preserve type of Gold
4698                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4699                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4700               }
4701           } else {
4702             /* Move from ICS was illegal!?  Punt. */
4703             if (appData.debugMode) {
4704               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4705               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4706             }
4707             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4708             strcat(parseList[moveNum - 1], " ");
4709             strcat(parseList[moveNum - 1], elapsed_time);
4710             moveList[moveNum - 1][0] = NULLCHAR;
4711             fromX = fromY = toX = toY = -1;
4712           }
4713         }
4714   if (appData.debugMode) {
4715     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4716     setbuf(debugFP, NULL);
4717   }
4718
4719 #if ZIPPY
4720         /* Send move to chess program (BEFORE animating it). */
4721         if (appData.zippyPlay && !newGame && newMove &&
4722            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4723
4724             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4725                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4726                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4727                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4728                             move_str);
4729                     DisplayError(str, 0);
4730                 } else {
4731                     if (first.sendTime) {
4732                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4733                     }
4734                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4735                     if (firstMove && !bookHit) {
4736                         firstMove = FALSE;
4737                         if (first.useColors) {
4738                           SendToProgram(gameMode == IcsPlayingWhite ?
4739                                         "white\ngo\n" :
4740                                         "black\ngo\n", &first);
4741                         } else {
4742                           SendToProgram("go\n", &first);
4743                         }
4744                         first.maybeThinking = TRUE;
4745                     }
4746                 }
4747             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4748               if (moveList[moveNum - 1][0] == NULLCHAR) {
4749                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4750                 DisplayError(str, 0);
4751               } else {
4752                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4753                 SendMoveToProgram(moveNum - 1, &first);
4754               }
4755             }
4756         }
4757 #endif
4758     }
4759
4760     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4761         /* If move comes from a remote source, animate it.  If it
4762            isn't remote, it will have already been animated. */
4763         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4764             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4765         }
4766         if (!pausing && appData.highlightLastMove) {
4767             SetHighlights(fromX, fromY, toX, toY);
4768         }
4769     }
4770
4771     /* Start the clocks */
4772     whiteFlag = blackFlag = FALSE;
4773     appData.clockMode = !(basetime == 0 && increment == 0);
4774     if (ticking == 0) {
4775       ics_clock_paused = TRUE;
4776       StopClocks();
4777     } else if (ticking == 1) {
4778       ics_clock_paused = FALSE;
4779     }
4780     if (gameMode == IcsIdle ||
4781         relation == RELATION_OBSERVING_STATIC ||
4782         relation == RELATION_EXAMINING ||
4783         ics_clock_paused)
4784       DisplayBothClocks();
4785     else
4786       StartClocks();
4787
4788     /* Display opponents and material strengths */
4789     if (gameInfo.variant != VariantBughouse &&
4790         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4791         if (tinyLayout || smallLayout) {
4792             if(gameInfo.variant == VariantNormal)
4793               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4794                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4795                     basetime, increment);
4796             else
4797               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4798                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4799                     basetime, increment, (int) gameInfo.variant);
4800         } else {
4801             if(gameInfo.variant == VariantNormal)
4802               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4803                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4804                     basetime, increment);
4805             else
4806               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4807                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4808                     basetime, increment, VariantName(gameInfo.variant));
4809         }
4810         DisplayTitle(str);
4811   if (appData.debugMode) {
4812     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4813   }
4814     }
4815
4816
4817     /* Display the board */
4818     if (!pausing && !appData.noGUI) {
4819
4820       if (appData.premove)
4821           if (!gotPremove ||
4822              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4823              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4824               ClearPremoveHighlights();
4825
4826       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4827         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4828       DrawPosition(j, boards[currentMove]);
4829
4830       DisplayMove(moveNum - 1);
4831       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4832             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4833               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4834         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4835       }
4836     }
4837
4838     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4839 #if ZIPPY
4840     if(bookHit) { // [HGM] book: simulate book reply
4841         static char bookMove[MSG_SIZ]; // a bit generous?
4842
4843         programStats.nodes = programStats.depth = programStats.time =
4844         programStats.score = programStats.got_only_move = 0;
4845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4846
4847         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4848         strcat(bookMove, bookHit);
4849         HandleMachineMove(bookMove, &first);
4850     }
4851 #endif
4852 }
4853
4854 void
4855 GetMoveListEvent ()
4856 {
4857     char buf[MSG_SIZ];
4858     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4859         ics_getting_history = H_REQUESTED;
4860         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4861         SendToICS(buf);
4862     }
4863 }
4864
4865 void
4866 AnalysisPeriodicEvent (int force)
4867 {
4868     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4869          && !force) || !appData.periodicUpdates)
4870       return;
4871
4872     /* Send . command to Crafty to collect stats */
4873     SendToProgram(".\n", &first);
4874
4875     /* Don't send another until we get a response (this makes
4876        us stop sending to old Crafty's which don't understand
4877        the "." command (sending illegal cmds resets node count & time,
4878        which looks bad)) */
4879     programStats.ok_to_send = 0;
4880 }
4881
4882 void
4883 ics_update_width (int new_width)
4884 {
4885         ics_printf("set width %d\n", new_width);
4886 }
4887
4888 void
4889 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4890 {
4891     char buf[MSG_SIZ];
4892
4893     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4894         // null move in variant where engine does not understand it (for analysis purposes)
4895         SendBoard(cps, moveNum + 1); // send position after move in stead.
4896         return;
4897     }
4898     if (cps->useUsermove) {
4899       SendToProgram("usermove ", cps);
4900     }
4901     if (cps->useSAN) {
4902       char *space;
4903       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4904         int len = space - parseList[moveNum];
4905         memcpy(buf, parseList[moveNum], len);
4906         buf[len++] = '\n';
4907         buf[len] = NULLCHAR;
4908       } else {
4909         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4910       }
4911       SendToProgram(buf, cps);
4912     } else {
4913       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4914         AlphaRank(moveList[moveNum], 4);
4915         SendToProgram(moveList[moveNum], cps);
4916         AlphaRank(moveList[moveNum], 4); // and back
4917       } else
4918       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4919        * the engine. It would be nice to have a better way to identify castle
4920        * moves here. */
4921       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4922                                                                          && cps->useOOCastle) {
4923         int fromX = moveList[moveNum][0] - AAA;
4924         int fromY = moveList[moveNum][1] - ONE;
4925         int toX = moveList[moveNum][2] - AAA;
4926         int toY = moveList[moveNum][3] - ONE;
4927         if((boards[moveNum][fromY][fromX] == WhiteKing
4928             && boards[moveNum][toY][toX] == WhiteRook)
4929            || (boards[moveNum][fromY][fromX] == BlackKing
4930                && boards[moveNum][toY][toX] == BlackRook)) {
4931           if(toX > fromX) SendToProgram("O-O\n", cps);
4932           else SendToProgram("O-O-O\n", cps);
4933         }
4934         else SendToProgram(moveList[moveNum], cps);
4935       } else
4936       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4937         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4938           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4939           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4940                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4941         } else
4942           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4943                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4944         SendToProgram(buf, cps);
4945       }
4946       else SendToProgram(moveList[moveNum], cps);
4947       /* End of additions by Tord */
4948     }
4949
4950     /* [HGM] setting up the opening has brought engine in force mode! */
4951     /*       Send 'go' if we are in a mode where machine should play. */
4952     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4953         (gameMode == TwoMachinesPlay   ||
4954 #if ZIPPY
4955          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4956 #endif
4957          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4958         SendToProgram("go\n", cps);
4959   if (appData.debugMode) {
4960     fprintf(debugFP, "(extra)\n");
4961   }
4962     }
4963     setboardSpoiledMachineBlack = 0;
4964 }
4965
4966 void
4967 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4968 {
4969     char user_move[MSG_SIZ];
4970     char suffix[4];
4971
4972     if(gameInfo.variant == VariantSChess && promoChar) {
4973         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4974         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4975     } else suffix[0] = NULLCHAR;
4976
4977     switch (moveType) {
4978       default:
4979         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4980                 (int)moveType, fromX, fromY, toX, toY);
4981         DisplayError(user_move + strlen("say "), 0);
4982         break;
4983       case WhiteKingSideCastle:
4984       case BlackKingSideCastle:
4985       case WhiteQueenSideCastleWild:
4986       case BlackQueenSideCastleWild:
4987       /* PUSH Fabien */
4988       case WhiteHSideCastleFR:
4989       case BlackHSideCastleFR:
4990       /* POP Fabien */
4991         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4992         break;
4993       case WhiteQueenSideCastle:
4994       case BlackQueenSideCastle:
4995       case WhiteKingSideCastleWild:
4996       case BlackKingSideCastleWild:
4997       /* PUSH Fabien */
4998       case WhiteASideCastleFR:
4999       case BlackASideCastleFR:
5000       /* POP Fabien */
5001         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5002         break;
5003       case WhiteNonPromotion:
5004       case BlackNonPromotion:
5005         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5006         break;
5007       case WhitePromotion:
5008       case BlackPromotion:
5009         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5010           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5011                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5012                 PieceToChar(WhiteFerz));
5013         else if(gameInfo.variant == VariantGreat)
5014           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5015                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5016                 PieceToChar(WhiteMan));
5017         else
5018           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020                 promoChar);
5021         break;
5022       case WhiteDrop:
5023       case BlackDrop:
5024       drop:
5025         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5026                  ToUpper(PieceToChar((ChessSquare) fromX)),
5027                  AAA + toX, ONE + toY);
5028         break;
5029       case IllegalMove:  /* could be a variant we don't quite understand */
5030         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5031       case NormalMove:
5032       case WhiteCapturesEnPassant:
5033       case BlackCapturesEnPassant:
5034         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5035                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5036         break;
5037     }
5038     SendToICS(user_move);
5039     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5040         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5041 }
5042
5043 void
5044 UploadGameEvent ()
5045 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5046     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5047     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5048     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5049       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5050       return;
5051     }
5052     if(gameMode != IcsExamining) { // is this ever not the case?
5053         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5054
5055         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5056           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5057         } else { // on FICS we must first go to general examine mode
5058           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5059         }
5060         if(gameInfo.variant != VariantNormal) {
5061             // try figure out wild number, as xboard names are not always valid on ICS
5062             for(i=1; i<=36; i++) {
5063               snprintf(buf, MSG_SIZ, "wild/%d", i);
5064                 if(StringToVariant(buf) == gameInfo.variant) break;
5065             }
5066             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5067             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5068             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5069         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5070         SendToICS(ics_prefix);
5071         SendToICS(buf);
5072         if(startedFromSetupPosition || backwardMostMove != 0) {
5073           fen = PositionToFEN(backwardMostMove, NULL);
5074           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5075             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5076             SendToICS(buf);
5077           } else { // FICS: everything has to set by separate bsetup commands
5078             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5079             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5080             SendToICS(buf);
5081             if(!WhiteOnMove(backwardMostMove)) {
5082                 SendToICS("bsetup tomove black\n");
5083             }
5084             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5085             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5086             SendToICS(buf);
5087             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5088             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5089             SendToICS(buf);
5090             i = boards[backwardMostMove][EP_STATUS];
5091             if(i >= 0) { // set e.p.
5092               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5093                 SendToICS(buf);
5094             }
5095             bsetup++;
5096           }
5097         }
5098       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5099             SendToICS("bsetup done\n"); // switch to normal examining.
5100     }
5101     for(i = backwardMostMove; i<last; i++) {
5102         char buf[20];
5103         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5104         SendToICS(buf);
5105     }
5106     SendToICS(ics_prefix);
5107     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5108 }
5109
5110 void
5111 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5112 {
5113     if (rf == DROP_RANK) {
5114       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5115       sprintf(move, "%c@%c%c\n",
5116                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5117     } else {
5118         if (promoChar == 'x' || promoChar == NULLCHAR) {
5119           sprintf(move, "%c%c%c%c\n",
5120                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5121         } else {
5122             sprintf(move, "%c%c%c%c%c\n",
5123                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5124         }
5125     }
5126 }
5127
5128 void
5129 ProcessICSInitScript (FILE *f)
5130 {
5131     char buf[MSG_SIZ];
5132
5133     while (fgets(buf, MSG_SIZ, f)) {
5134         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5135     }
5136
5137     fclose(f);
5138 }
5139
5140
5141 static int lastX, lastY, selectFlag, dragging;
5142
5143 void
5144 Sweep (int step)
5145 {
5146     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5147     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5148     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5149     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5150     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5151     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5152     do {
5153         promoSweep -= step;
5154         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5155         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5156         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5157         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5158         if(!step) step = -1;
5159     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5160             appData.testLegality && (promoSweep == king ||
5161             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5162     ChangeDragPiece(promoSweep);
5163 }
5164
5165 int
5166 PromoScroll (int x, int y)
5167 {
5168   int step = 0;
5169
5170   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5171   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5172   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5173   if(!step) return FALSE;
5174   lastX = x; lastY = y;
5175   if((promoSweep < BlackPawn) == flipView) step = -step;
5176   if(step > 0) selectFlag = 1;
5177   if(!selectFlag) Sweep(step);
5178   return FALSE;
5179 }
5180
5181 void
5182 NextPiece (int step)
5183 {
5184     ChessSquare piece = boards[currentMove][toY][toX];
5185     do {
5186         pieceSweep -= step;
5187         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5188         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5189         if(!step) step = -1;
5190     } while(PieceToChar(pieceSweep) == '.');
5191     boards[currentMove][toY][toX] = pieceSweep;
5192     DrawPosition(FALSE, boards[currentMove]);
5193     boards[currentMove][toY][toX] = piece;
5194 }
5195 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5196 void
5197 AlphaRank (char *move, int n)
5198 {
5199 //    char *p = move, c; int x, y;
5200
5201     if (appData.debugMode) {
5202         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5203     }
5204
5205     if(move[1]=='*' &&
5206        move[2]>='0' && move[2]<='9' &&
5207        move[3]>='a' && move[3]<='x'    ) {
5208         move[1] = '@';
5209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5211     } else
5212     if(move[0]>='0' && move[0]<='9' &&
5213        move[1]>='a' && move[1]<='x' &&
5214        move[2]>='0' && move[2]<='9' &&
5215        move[3]>='a' && move[3]<='x'    ) {
5216         /* input move, Shogi -> normal */
5217         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5218         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5219         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5220         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5221     } else
5222     if(move[1]=='@' &&
5223        move[3]>='0' && move[3]<='9' &&
5224        move[2]>='a' && move[2]<='x'    ) {
5225         move[1] = '*';
5226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5228     } else
5229     if(
5230        move[0]>='a' && move[0]<='x' &&
5231        move[3]>='0' && move[3]<='9' &&
5232        move[2]>='a' && move[2]<='x'    ) {
5233          /* output move, normal -> Shogi */
5234         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5235         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5236         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5237         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5238         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5239     }
5240     if (appData.debugMode) {
5241         fprintf(debugFP, "   out = '%s'\n", move);
5242     }
5243 }
5244
5245 char yy_textstr[8000];
5246
5247 /* Parser for moves from gnuchess, ICS, or user typein box */
5248 Boolean
5249 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5250 {
5251     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5252
5253     switch (*moveType) {
5254       case WhitePromotion:
5255       case BlackPromotion:
5256       case WhiteNonPromotion:
5257       case BlackNonPromotion:
5258       case NormalMove:
5259       case WhiteCapturesEnPassant:
5260       case BlackCapturesEnPassant:
5261       case WhiteKingSideCastle:
5262       case WhiteQueenSideCastle:
5263       case BlackKingSideCastle:
5264       case BlackQueenSideCastle:
5265       case WhiteKingSideCastleWild:
5266       case WhiteQueenSideCastleWild:
5267       case BlackKingSideCastleWild:
5268       case BlackQueenSideCastleWild:
5269       /* Code added by Tord: */
5270       case WhiteHSideCastleFR:
5271       case WhiteASideCastleFR:
5272       case BlackHSideCastleFR:
5273       case BlackASideCastleFR:
5274       /* End of code added by Tord */
5275       case IllegalMove:         /* bug or odd chess variant */
5276         *fromX = currentMoveString[0] - AAA;
5277         *fromY = currentMoveString[1] - ONE;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = currentMoveString[4];
5281         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5282             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5283     if (appData.debugMode) {
5284         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5285     }
5286             *fromX = *fromY = *toX = *toY = 0;
5287             return FALSE;
5288         }
5289         if (appData.testLegality) {
5290           return (*moveType != IllegalMove);
5291         } else {
5292           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5293                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5294         }
5295
5296       case WhiteDrop:
5297       case BlackDrop:
5298         *fromX = *moveType == WhiteDrop ?
5299           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5300           (int) CharToPiece(ToLower(currentMoveString[0]));
5301         *fromY = DROP_RANK;
5302         *toX = currentMoveString[2] - AAA;
5303         *toY = currentMoveString[3] - ONE;
5304         *promoChar = NULLCHAR;
5305         return TRUE;
5306
5307       case AmbiguousMove:
5308       case ImpossibleMove:
5309       case EndOfFile:
5310       case ElapsedTime:
5311       case Comment:
5312       case PGNTag:
5313       case NAG:
5314       case WhiteWins:
5315       case BlackWins:
5316       case GameIsDrawn:
5317       default:
5318     if (appData.debugMode) {
5319         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5320     }
5321         /* bug? */
5322         *fromX = *fromY = *toX = *toY = 0;
5323         *promoChar = NULLCHAR;
5324         return FALSE;
5325     }
5326 }
5327
5328 Boolean pushed = FALSE;
5329 char *lastParseAttempt;
5330
5331 void
5332 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5333 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5334   int fromX, fromY, toX, toY; char promoChar;
5335   ChessMove moveType;
5336   Boolean valid;
5337   int nr = 0;
5338
5339   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5340     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5341     pushed = TRUE;
5342   }
5343   endPV = forwardMostMove;
5344   do {
5345     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5346     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5347     lastParseAttempt = pv;
5348     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5349     if(!valid && nr == 0 &&
5350        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5351         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5352         // Hande case where played move is different from leading PV move
5353         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5354         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5355         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5356         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5357           endPV += 2; // if position different, keep this
5358           moveList[endPV-1][0] = fromX + AAA;
5359           moveList[endPV-1][1] = fromY + ONE;
5360           moveList[endPV-1][2] = toX + AAA;
5361           moveList[endPV-1][3] = toY + ONE;
5362           parseList[endPV-1][0] = NULLCHAR;
5363           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5364         }
5365       }
5366     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5367     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5368     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5369     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5370         valid++; // allow comments in PV
5371         continue;
5372     }
5373     nr++;
5374     if(endPV+1 > framePtr) break; // no space, truncate
5375     if(!valid) break;
5376     endPV++;
5377     CopyBoard(boards[endPV], boards[endPV-1]);
5378     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5379     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5380     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5381     CoordsToAlgebraic(boards[endPV - 1],
5382                              PosFlags(endPV - 1),
5383                              fromY, fromX, toY, toX, promoChar,
5384                              parseList[endPV - 1]);
5385   } while(valid);
5386   if(atEnd == 2) return; // used hidden, for PV conversion
5387   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5388   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5389   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5390                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5391   DrawPosition(TRUE, boards[currentMove]);
5392 }
5393
5394 int
5395 MultiPV (ChessProgramState *cps)
5396 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5397         int i;
5398         for(i=0; i<cps->nrOptions; i++)
5399             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5400                 return i;
5401         return -1;
5402 }
5403
5404 Boolean
5405 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5406 {
5407         int startPV, multi, lineStart, origIndex = index;
5408         char *p, buf2[MSG_SIZ];
5409
5410         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5411         lastX = x; lastY = y;
5412         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5413         lineStart = startPV = index;
5414         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5415         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5416         index = startPV;
5417         do{ while(buf[index] && buf[index] != '\n') index++;
5418         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5419         buf[index] = 0;
5420         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5421                 int n = first.option[multi].value;
5422                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5423                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5424                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5425                 first.option[multi].value = n;
5426                 *start = *end = 0;
5427                 return FALSE;
5428         }
5429         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5430         *start = startPV; *end = index-1;
5431         return TRUE;
5432 }
5433
5434 char *
5435 PvToSAN (char *pv)
5436 {
5437         static char buf[10*MSG_SIZ];
5438         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5439         *buf = NULLCHAR;
5440         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5441         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5442         for(i = forwardMostMove; i<endPV; i++){
5443             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5444             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5445             k += strlen(buf+k);
5446         }
5447         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5448         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5449         endPV = savedEnd;
5450         return buf;
5451 }
5452
5453 Boolean
5454 LoadPV (int x, int y)
5455 { // called on right mouse click to load PV
5456   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5457   lastX = x; lastY = y;
5458   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5459   return TRUE;
5460 }
5461
5462 void
5463 UnLoadPV ()
5464 {
5465   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5466   if(endPV < 0) return;
5467   if(appData.autoCopyPV) CopyFENToClipboard();
5468   endPV = -1;
5469   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5470         Boolean saveAnimate = appData.animate;
5471         if(pushed) {
5472             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5473                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5474             } else storedGames--; // abandon shelved tail of original game
5475         }
5476         pushed = FALSE;
5477         forwardMostMove = currentMove;
5478         currentMove = oldFMM;
5479         appData.animate = FALSE;
5480         ToNrEvent(forwardMostMove);
5481         appData.animate = saveAnimate;
5482   }
5483   currentMove = forwardMostMove;
5484   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5485   ClearPremoveHighlights();
5486   DrawPosition(TRUE, boards[currentMove]);
5487 }
5488
5489 void
5490 MovePV (int x, int y, int h)
5491 { // step through PV based on mouse coordinates (called on mouse move)
5492   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5493
5494   // we must somehow check if right button is still down (might be released off board!)
5495   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5496   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5497   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5498   if(!step) return;
5499   lastX = x; lastY = y;
5500
5501   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5502   if(endPV < 0) return;
5503   if(y < margin) step = 1; else
5504   if(y > h - margin) step = -1;
5505   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5506   currentMove += step;
5507   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5508   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5509                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5510   DrawPosition(FALSE, boards[currentMove]);
5511 }
5512
5513
5514 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5515 // All positions will have equal probability, but the current method will not provide a unique
5516 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5517 #define DARK 1
5518 #define LITE 2
5519 #define ANY 3
5520
5521 int squaresLeft[4];
5522 int piecesLeft[(int)BlackPawn];
5523 int seed, nrOfShuffles;
5524
5525 void
5526 GetPositionNumber ()
5527 {       // sets global variable seed
5528         int i;
5529
5530         seed = appData.defaultFrcPosition;
5531         if(seed < 0) { // randomize based on time for negative FRC position numbers
5532                 for(i=0; i<50; i++) seed += random();
5533                 seed = random() ^ random() >> 8 ^ random() << 8;
5534                 if(seed<0) seed = -seed;
5535         }
5536 }
5537
5538 int
5539 put (Board board, int pieceType, int rank, int n, int shade)
5540 // put the piece on the (n-1)-th empty squares of the given shade
5541 {
5542         int i;
5543
5544         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5545                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5546                         board[rank][i] = (ChessSquare) pieceType;
5547                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5548                         squaresLeft[ANY]--;
5549                         piecesLeft[pieceType]--;
5550                         return i;
5551                 }
5552         }
5553         return -1;
5554 }
5555
5556
5557 void
5558 AddOnePiece (Board board, int pieceType, int rank, int shade)
5559 // calculate where the next piece goes, (any empty square), and put it there
5560 {
5561         int i;
5562
5563         i = seed % squaresLeft[shade];
5564         nrOfShuffles *= squaresLeft[shade];
5565         seed /= squaresLeft[shade];
5566         put(board, pieceType, rank, i, shade);
5567 }
5568
5569 void
5570 AddTwoPieces (Board board, int pieceType, int rank)
5571 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5572 {
5573         int i, n=squaresLeft[ANY], j=n-1, k;
5574
5575         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5576         i = seed % k;  // pick one
5577         nrOfShuffles *= k;
5578         seed /= k;
5579         while(i >= j) i -= j--;
5580         j = n - 1 - j; i += j;
5581         put(board, pieceType, rank, j, ANY);
5582         put(board, pieceType, rank, i, ANY);
5583 }
5584
5585 void
5586 SetUpShuffle (Board board, int number)
5587 {
5588         int i, p, first=1;
5589
5590         GetPositionNumber(); nrOfShuffles = 1;
5591
5592         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5593         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5594         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5595
5596         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5597
5598         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5599             p = (int) board[0][i];
5600             if(p < (int) BlackPawn) piecesLeft[p] ++;
5601             board[0][i] = EmptySquare;
5602         }
5603
5604         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5605             // shuffles restricted to allow normal castling put KRR first
5606             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5607                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5608             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5609                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5610             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5611                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5612             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5613                 put(board, WhiteRook, 0, 0, ANY);
5614             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5615         }
5616
5617         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5618             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5619             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5620                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5621                 while(piecesLeft[p] >= 2) {
5622                     AddOnePiece(board, p, 0, LITE);
5623                     AddOnePiece(board, p, 0, DARK);
5624                 }
5625                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5626             }
5627
5628         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5629             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5630             // but we leave King and Rooks for last, to possibly obey FRC restriction
5631             if(p == (int)WhiteRook) continue;
5632             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5633             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5634         }
5635
5636         // now everything is placed, except perhaps King (Unicorn) and Rooks
5637
5638         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5639             // Last King gets castling rights
5640             while(piecesLeft[(int)WhiteUnicorn]) {
5641                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5642                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5643             }
5644
5645             while(piecesLeft[(int)WhiteKing]) {
5646                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5647                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5648             }
5649
5650
5651         } else {
5652             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5653             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5654         }
5655
5656         // Only Rooks can be left; simply place them all
5657         while(piecesLeft[(int)WhiteRook]) {
5658                 i = put(board, WhiteRook, 0, 0, ANY);
5659                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5660                         if(first) {
5661                                 first=0;
5662                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5663                         }
5664                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5665                 }
5666         }
5667         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5668             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5669         }
5670
5671         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5672 }
5673
5674 int
5675 SetCharTable (char *table, const char * map)
5676 /* [HGM] moved here from winboard.c because of its general usefulness */
5677 /*       Basically a safe strcpy that uses the last character as King */
5678 {
5679     int result = FALSE; int NrPieces;
5680
5681     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5682                     && NrPieces >= 12 && !(NrPieces&1)) {
5683         int i; /* [HGM] Accept even length from 12 to 34 */
5684
5685         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5686         for( i=0; i<NrPieces/2-1; i++ ) {
5687             table[i] = map[i];
5688             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5689         }
5690         table[(int) WhiteKing]  = map[NrPieces/2-1];
5691         table[(int) BlackKing]  = map[NrPieces-1];
5692
5693         result = TRUE;
5694     }
5695
5696     return result;
5697 }
5698
5699 void
5700 Prelude (Board board)
5701 {       // [HGM] superchess: random selection of exo-pieces
5702         int i, j, k; ChessSquare p;
5703         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5704
5705         GetPositionNumber(); // use FRC position number
5706
5707         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5708             SetCharTable(pieceToChar, appData.pieceToCharTable);
5709             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5710                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5711         }
5712
5713         j = seed%4;                 seed /= 4;
5714         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5717         j = seed%3 + (seed%3 >= j); seed /= 3;
5718         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5719         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5720         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5721         j = seed%3;                 seed /= 3;
5722         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5723         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5724         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5725         j = seed%2 + (seed%2 >= j); seed /= 2;
5726         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5727         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5728         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5729         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5730         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5731         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5732         put(board, exoPieces[0],    0, 0, ANY);
5733         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5734 }
5735
5736 void
5737 InitPosition (int redraw)
5738 {
5739     ChessSquare (* pieces)[BOARD_FILES];
5740     int i, j, pawnRow, overrule,
5741     oldx = gameInfo.boardWidth,
5742     oldy = gameInfo.boardHeight,
5743     oldh = gameInfo.holdingsWidth;
5744     static int oldv;
5745
5746     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5747
5748     /* [AS] Initialize pv info list [HGM] and game status */
5749     {
5750         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5751             pvInfoList[i].depth = 0;
5752             boards[i][EP_STATUS] = EP_NONE;
5753             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5754         }
5755
5756         initialRulePlies = 0; /* 50-move counter start */
5757
5758         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5759         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5760     }
5761
5762
5763     /* [HGM] logic here is completely changed. In stead of full positions */
5764     /* the initialized data only consist of the two backranks. The switch */
5765     /* selects which one we will use, which is than copied to the Board   */
5766     /* initialPosition, which for the rest is initialized by Pawns and    */
5767     /* empty squares. This initial position is then copied to boards[0],  */
5768     /* possibly after shuffling, so that it remains available.            */
5769
5770     gameInfo.holdingsWidth = 0; /* default board sizes */
5771     gameInfo.boardWidth    = 8;
5772     gameInfo.boardHeight   = 8;
5773     gameInfo.holdingsSize  = 0;
5774     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5775     for(i=0; i<BOARD_FILES-2; i++)
5776       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5777     initialPosition[EP_STATUS] = EP_NONE;
5778     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5779     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5780          SetCharTable(pieceNickName, appData.pieceNickNames);
5781     else SetCharTable(pieceNickName, "............");
5782     pieces = FIDEArray;
5783
5784     switch (gameInfo.variant) {
5785     case VariantFischeRandom:
5786       shuffleOpenings = TRUE;
5787     default:
5788       break;
5789     case VariantShatranj:
5790       pieces = ShatranjArray;
5791       nrCastlingRights = 0;
5792       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5793       break;
5794     case VariantMakruk:
5795       pieces = makrukArray;
5796       nrCastlingRights = 0;
5797       startedFromSetupPosition = TRUE;
5798       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5799       break;
5800     case VariantTwoKings:
5801       pieces = twoKingsArray;
5802       break;
5803     case VariantGrand:
5804       pieces = GrandArray;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5807       gameInfo.boardWidth = 10;
5808       gameInfo.boardHeight = 10;
5809       gameInfo.holdingsSize = 7;
5810       break;
5811     case VariantCapaRandom:
5812       shuffleOpenings = TRUE;
5813     case VariantCapablanca:
5814       pieces = CapablancaArray;
5815       gameInfo.boardWidth = 10;
5816       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5817       break;
5818     case VariantGothic:
5819       pieces = GothicArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5822       break;
5823     case VariantSChess:
5824       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5825       gameInfo.holdingsSize = 7;
5826       break;
5827     case VariantJanus:
5828       pieces = JanusArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5831       nrCastlingRights = 6;
5832         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5833         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5834         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5835         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5836         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5837         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5838       break;
5839     case VariantFalcon:
5840       pieces = FalconArray;
5841       gameInfo.boardWidth = 10;
5842       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5843       break;
5844     case VariantXiangqi:
5845       pieces = XiangqiArray;
5846       gameInfo.boardWidth  = 9;
5847       gameInfo.boardHeight = 10;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5850       break;
5851     case VariantShogi:
5852       pieces = ShogiArray;
5853       gameInfo.boardWidth  = 9;
5854       gameInfo.boardHeight = 9;
5855       gameInfo.holdingsSize = 7;
5856       nrCastlingRights = 0;
5857       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5858       break;
5859     case VariantCourier:
5860       pieces = CourierArray;
5861       gameInfo.boardWidth  = 12;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5864       break;
5865     case VariantKnightmate:
5866       pieces = KnightmateArray;
5867       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5868       break;
5869     case VariantSpartan:
5870       pieces = SpartanArray;
5871       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5872       break;
5873     case VariantFairy:
5874       pieces = fairyArray;
5875       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5876       break;
5877     case VariantGreat:
5878       pieces = GreatArray;
5879       gameInfo.boardWidth = 10;
5880       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5881       gameInfo.holdingsSize = 8;
5882       break;
5883     case VariantSuper:
5884       pieces = FIDEArray;
5885       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5886       gameInfo.holdingsSize = 8;
5887       startedFromSetupPosition = TRUE;
5888       break;
5889     case VariantCrazyhouse:
5890     case VariantBughouse:
5891       pieces = FIDEArray;
5892       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5893       gameInfo.holdingsSize = 5;
5894       break;
5895     case VariantWildCastle:
5896       pieces = FIDEArray;
5897       /* !!?shuffle with kings guaranteed to be on d or e file */
5898       shuffleOpenings = 1;
5899       break;
5900     case VariantNoCastle:
5901       pieces = FIDEArray;
5902       nrCastlingRights = 0;
5903       /* !!?unconstrained back-rank shuffle */
5904       shuffleOpenings = 1;
5905       break;
5906     }
5907
5908     overrule = 0;
5909     if(appData.NrFiles >= 0) {
5910         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5911         gameInfo.boardWidth = appData.NrFiles;
5912     }
5913     if(appData.NrRanks >= 0) {
5914         gameInfo.boardHeight = appData.NrRanks;
5915     }
5916     if(appData.holdingsSize >= 0) {
5917         i = appData.holdingsSize;
5918         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5919         gameInfo.holdingsSize = i;
5920     }
5921     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5922     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5923         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5924
5925     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5926     if(pawnRow < 1) pawnRow = 1;
5927     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5928
5929     /* User pieceToChar list overrules defaults */
5930     if(appData.pieceToCharTable != NULL)
5931         SetCharTable(pieceToChar, appData.pieceToCharTable);
5932
5933     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5934
5935         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5936             s = (ChessSquare) 0; /* account holding counts in guard band */
5937         for( i=0; i<BOARD_HEIGHT; i++ )
5938             initialPosition[i][j] = s;
5939
5940         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5941         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5942         initialPosition[pawnRow][j] = WhitePawn;
5943         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5944         if(gameInfo.variant == VariantXiangqi) {
5945             if(j&1) {
5946                 initialPosition[pawnRow][j] =
5947                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5948                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5949                    initialPosition[2][j] = WhiteCannon;
5950                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5951                 }
5952             }
5953         }
5954         if(gameInfo.variant == VariantGrand) {
5955             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5956                initialPosition[0][j] = WhiteRook;
5957                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5958             }
5959         }
5960         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5961     }
5962     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5963
5964             j=BOARD_LEFT+1;
5965             initialPosition[1][j] = WhiteBishop;
5966             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5967             j=BOARD_RGHT-2;
5968             initialPosition[1][j] = WhiteRook;
5969             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5970     }
5971
5972     if( nrCastlingRights == -1) {
5973         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5974         /*       This sets default castling rights from none to normal corners   */
5975         /* Variants with other castling rights must set them themselves above    */
5976         nrCastlingRights = 6;
5977
5978         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5979         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5980         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5981         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5982         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5983         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5984      }
5985
5986      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5987      if(gameInfo.variant == VariantGreat) { // promotion commoners
5988         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5989         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5990         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5991         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5992      }
5993      if( gameInfo.variant == VariantSChess ) {
5994       initialPosition[1][0] = BlackMarshall;
5995       initialPosition[2][0] = BlackAngel;
5996       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5997       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5998       initialPosition[1][1] = initialPosition[2][1] = 
5999       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6000      }
6001   if (appData.debugMode) {
6002     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6003   }
6004     if(shuffleOpenings) {
6005         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6006         startedFromSetupPosition = TRUE;
6007     }
6008     if(startedFromPositionFile) {
6009       /* [HGM] loadPos: use PositionFile for every new game */
6010       CopyBoard(initialPosition, filePosition);
6011       for(i=0; i<nrCastlingRights; i++)
6012           initialRights[i] = filePosition[CASTLING][i];
6013       startedFromSetupPosition = TRUE;
6014     }
6015
6016     CopyBoard(boards[0], initialPosition);
6017
6018     if(oldx != gameInfo.boardWidth ||
6019        oldy != gameInfo.boardHeight ||
6020        oldv != gameInfo.variant ||
6021        oldh != gameInfo.holdingsWidth
6022                                          )
6023             InitDrawingSizes(-2 ,0);
6024
6025     oldv = gameInfo.variant;
6026     if (redraw)
6027       DrawPosition(TRUE, boards[currentMove]);
6028 }
6029
6030 void
6031 SendBoard (ChessProgramState *cps, int moveNum)
6032 {
6033     char message[MSG_SIZ];
6034
6035     if (cps->useSetboard) {
6036       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6037       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6038       SendToProgram(message, cps);
6039       free(fen);
6040
6041     } else {
6042       ChessSquare *bp;
6043       int i, j, left=0, right=BOARD_WIDTH;
6044       /* Kludge to set black to move, avoiding the troublesome and now
6045        * deprecated "black" command.
6046        */
6047       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6048         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6049
6050       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6051
6052       SendToProgram("edit\n", cps);
6053       SendToProgram("#\n", cps);
6054       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6055         bp = &boards[moveNum][i][left];
6056         for (j = left; j < right; j++, bp++) {
6057           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6058           if ((int) *bp < (int) BlackPawn) {
6059             if(j == BOARD_RGHT+1)
6060                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6061             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6062             if(message[0] == '+' || message[0] == '~') {
6063               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6064                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6065                         AAA + j, ONE + i);
6066             }
6067             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6068                 message[1] = BOARD_RGHT   - 1 - j + '1';
6069                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6070             }
6071             SendToProgram(message, cps);
6072           }
6073         }
6074       }
6075
6076       SendToProgram("c\n", cps);
6077       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6078         bp = &boards[moveNum][i][left];
6079         for (j = left; j < right; j++, bp++) {
6080           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6081           if (((int) *bp != (int) EmptySquare)
6082               && ((int) *bp >= (int) BlackPawn)) {
6083             if(j == BOARD_LEFT-2)
6084                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6085             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6086                     AAA + j, ONE + i);
6087             if(message[0] == '+' || message[0] == '~') {
6088               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6089                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6090                         AAA + j, ONE + i);
6091             }
6092             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6093                 message[1] = BOARD_RGHT   - 1 - j + '1';
6094                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6095             }
6096             SendToProgram(message, cps);
6097           }
6098         }
6099       }
6100
6101       SendToProgram(".\n", cps);
6102     }
6103     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6104 }
6105
6106 ChessSquare
6107 DefaultPromoChoice (int white)
6108 {
6109     ChessSquare result;
6110     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6111         result = WhiteFerz; // no choice
6112     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6113         result= WhiteKing; // in Suicide Q is the last thing we want
6114     else if(gameInfo.variant == VariantSpartan)
6115         result = white ? WhiteQueen : WhiteAngel;
6116     else result = WhiteQueen;
6117     if(!white) result = WHITE_TO_BLACK result;
6118     return result;
6119 }
6120
6121 static int autoQueen; // [HGM] oneclick
6122
6123 int
6124 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6125 {
6126     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6127     /* [HGM] add Shogi promotions */
6128     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6129     ChessSquare piece;
6130     ChessMove moveType;
6131     Boolean premove;
6132
6133     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6134     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6135
6136     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6137       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6138         return FALSE;
6139
6140     piece = boards[currentMove][fromY][fromX];
6141     if(gameInfo.variant == VariantShogi) {
6142         promotionZoneSize = BOARD_HEIGHT/3;
6143         highestPromotingPiece = (int)WhiteFerz;
6144     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6145         promotionZoneSize = 3;
6146     }
6147
6148     // Treat Lance as Pawn when it is not representing Amazon
6149     if(gameInfo.variant != VariantSuper) {
6150         if(piece == WhiteLance) piece = WhitePawn; else
6151         if(piece == BlackLance) piece = BlackPawn;
6152     }
6153
6154     // next weed out all moves that do not touch the promotion zone at all
6155     if((int)piece >= BlackPawn) {
6156         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6157              return FALSE;
6158         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6159     } else {
6160         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6161            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6162     }
6163
6164     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6165
6166     // weed out mandatory Shogi promotions
6167     if(gameInfo.variant == VariantShogi) {
6168         if(piece >= BlackPawn) {
6169             if(toY == 0 && piece == BlackPawn ||
6170                toY == 0 && piece == BlackQueen ||
6171                toY <= 1 && piece == BlackKnight) {
6172                 *promoChoice = '+';
6173                 return FALSE;
6174             }
6175         } else {
6176             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6177                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6178                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6179                 *promoChoice = '+';
6180                 return FALSE;
6181             }
6182         }
6183     }
6184
6185     // weed out obviously illegal Pawn moves
6186     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6187         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6188         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6189         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6190         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6191         // note we are not allowed to test for valid (non-)capture, due to premove
6192     }
6193
6194     // we either have a choice what to promote to, or (in Shogi) whether to promote
6195     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6196         *promoChoice = PieceToChar(BlackFerz);  // no choice
6197         return FALSE;
6198     }
6199     // no sense asking what we must promote to if it is going to explode...
6200     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6201         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6202         return FALSE;
6203     }
6204     // give caller the default choice even if we will not make it
6205     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6206     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6207     if(        sweepSelect && gameInfo.variant != VariantGreat
6208                            && gameInfo.variant != VariantGrand
6209                            && gameInfo.variant != VariantSuper) return FALSE;
6210     if(autoQueen) return FALSE; // predetermined
6211
6212     // suppress promotion popup on illegal moves that are not premoves
6213     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6214               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6215     if(appData.testLegality && !premove) {
6216         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6217                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6218         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6219             return FALSE;
6220     }
6221
6222     return TRUE;
6223 }
6224
6225 int
6226 InPalace (int row, int column)
6227 {   /* [HGM] for Xiangqi */
6228     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6229          column < (BOARD_WIDTH + 4)/2 &&
6230          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6231     return FALSE;
6232 }
6233
6234 int
6235 PieceForSquare (int x, int y)
6236 {
6237   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6238      return -1;
6239   else
6240      return boards[currentMove][y][x];
6241 }
6242
6243 int
6244 OKToStartUserMove (int x, int y)
6245 {
6246     ChessSquare from_piece;
6247     int white_piece;
6248
6249     if (matchMode) return FALSE;
6250     if (gameMode == EditPosition) return TRUE;
6251
6252     if (x >= 0 && y >= 0)
6253       from_piece = boards[currentMove][y][x];
6254     else
6255       from_piece = EmptySquare;
6256
6257     if (from_piece == EmptySquare) return FALSE;
6258
6259     white_piece = (int)from_piece >= (int)WhitePawn &&
6260       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6261
6262     switch (gameMode) {
6263       case AnalyzeFile:
6264       case TwoMachinesPlay:
6265       case EndOfGame:
6266         return FALSE;
6267
6268       case IcsObserving:
6269       case IcsIdle:
6270         return FALSE;
6271
6272       case MachinePlaysWhite:
6273       case IcsPlayingBlack:
6274         if (appData.zippyPlay) return FALSE;
6275         if (white_piece) {
6276             DisplayMoveError(_("You are playing Black"));
6277             return FALSE;
6278         }
6279         break;
6280
6281       case MachinePlaysBlack:
6282       case IcsPlayingWhite:
6283         if (appData.zippyPlay) return FALSE;
6284         if (!white_piece) {
6285             DisplayMoveError(_("You are playing White"));
6286             return FALSE;
6287         }
6288         break;
6289
6290       case PlayFromGameFile:
6291             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6292       case EditGame:
6293         if (!white_piece && WhiteOnMove(currentMove)) {
6294             DisplayMoveError(_("It is White's turn"));
6295             return FALSE;
6296         }
6297         if (white_piece && !WhiteOnMove(currentMove)) {
6298             DisplayMoveError(_("It is Black's turn"));
6299             return FALSE;
6300         }
6301         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6302             /* Editing correspondence game history */
6303             /* Could disallow this or prompt for confirmation */
6304             cmailOldMove = -1;
6305         }
6306         break;
6307
6308       case BeginningOfGame:
6309         if (appData.icsActive) return FALSE;
6310         if (!appData.noChessProgram) {
6311             if (!white_piece) {
6312                 DisplayMoveError(_("You are playing White"));
6313                 return FALSE;
6314             }
6315         }
6316         break;
6317
6318       case Training:
6319         if (!white_piece && WhiteOnMove(currentMove)) {
6320             DisplayMoveError(_("It is White's turn"));
6321             return FALSE;
6322         }
6323         if (white_piece && !WhiteOnMove(currentMove)) {
6324             DisplayMoveError(_("It is Black's turn"));
6325             return FALSE;
6326         }
6327         break;
6328
6329       default:
6330       case IcsExamining:
6331         break;
6332     }
6333     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6334         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6335         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6336         && gameMode != AnalyzeFile && gameMode != Training) {
6337         DisplayMoveError(_("Displayed position is not current"));
6338         return FALSE;
6339     }
6340     return TRUE;
6341 }
6342
6343 Boolean
6344 OnlyMove (int *x, int *y, Boolean captures) 
6345 {
6346     DisambiguateClosure cl;
6347     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6348     switch(gameMode) {
6349       case MachinePlaysBlack:
6350       case IcsPlayingWhite:
6351       case BeginningOfGame:
6352         if(!WhiteOnMove(currentMove)) return FALSE;
6353         break;
6354       case MachinePlaysWhite:
6355       case IcsPlayingBlack:
6356         if(WhiteOnMove(currentMove)) return FALSE;
6357         break;
6358       case EditGame:
6359         break;
6360       default:
6361         return FALSE;
6362     }
6363     cl.pieceIn = EmptySquare;
6364     cl.rfIn = *y;
6365     cl.ffIn = *x;
6366     cl.rtIn = -1;
6367     cl.ftIn = -1;
6368     cl.promoCharIn = NULLCHAR;
6369     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6370     if( cl.kind == NormalMove ||
6371         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6372         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6373         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6374       fromX = cl.ff;
6375       fromY = cl.rf;
6376       *x = cl.ft;
6377       *y = cl.rt;
6378       return TRUE;
6379     }
6380     if(cl.kind != ImpossibleMove) return FALSE;
6381     cl.pieceIn = EmptySquare;
6382     cl.rfIn = -1;
6383     cl.ffIn = -1;
6384     cl.rtIn = *y;
6385     cl.ftIn = *x;
6386     cl.promoCharIn = NULLCHAR;
6387     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6388     if( cl.kind == NormalMove ||
6389         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6390         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6391         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6392       fromX = cl.ff;
6393       fromY = cl.rf;
6394       *x = cl.ft;
6395       *y = cl.rt;
6396       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6397       return TRUE;
6398     }
6399     return FALSE;
6400 }
6401
6402 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6403 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6404 int lastLoadGameUseList = FALSE;
6405 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6406 ChessMove lastLoadGameStart = EndOfFile;
6407
6408 void
6409 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6410 {
6411     ChessMove moveType;
6412     ChessSquare pdown, pup;
6413
6414     /* Check if the user is playing in turn.  This is complicated because we
6415        let the user "pick up" a piece before it is his turn.  So the piece he
6416        tried to pick up may have been captured by the time he puts it down!
6417        Therefore we use the color the user is supposed to be playing in this
6418        test, not the color of the piece that is currently on the starting
6419        square---except in EditGame mode, where the user is playing both
6420        sides; fortunately there the capture race can't happen.  (It can
6421        now happen in IcsExamining mode, but that's just too bad.  The user
6422        will get a somewhat confusing message in that case.)
6423        */
6424
6425     switch (gameMode) {
6426       case AnalyzeFile:
6427       case TwoMachinesPlay:
6428       case EndOfGame:
6429       case IcsObserving:
6430       case IcsIdle:
6431         /* We switched into a game mode where moves are not accepted,
6432            perhaps while the mouse button was down. */
6433         return;
6434
6435       case MachinePlaysWhite:
6436         /* User is moving for Black */
6437         if (WhiteOnMove(currentMove)) {
6438             DisplayMoveError(_("It is White's turn"));
6439             return;
6440         }
6441         break;
6442
6443       case MachinePlaysBlack:
6444         /* User is moving for White */
6445         if (!WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is Black's turn"));
6447             return;
6448         }
6449         break;
6450
6451       case PlayFromGameFile:
6452             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6453       case EditGame:
6454       case IcsExamining:
6455       case BeginningOfGame:
6456       case AnalyzeMode:
6457       case Training:
6458         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6459         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6460             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6461             /* User is moving for Black */
6462             if (WhiteOnMove(currentMove)) {
6463                 DisplayMoveError(_("It is White's turn"));
6464                 return;
6465             }
6466         } else {
6467             /* User is moving for White */
6468             if (!WhiteOnMove(currentMove)) {
6469                 DisplayMoveError(_("It is Black's turn"));
6470                 return;
6471             }
6472         }
6473         break;
6474
6475       case IcsPlayingBlack:
6476         /* User is moving for Black */
6477         if (WhiteOnMove(currentMove)) {
6478             if (!appData.premove) {
6479                 DisplayMoveError(_("It is White's turn"));
6480             } else if (toX >= 0 && toY >= 0) {
6481                 premoveToX = toX;
6482                 premoveToY = toY;
6483                 premoveFromX = fromX;
6484                 premoveFromY = fromY;
6485                 premovePromoChar = promoChar;
6486                 gotPremove = 1;
6487                 if (appData.debugMode)
6488                     fprintf(debugFP, "Got premove: fromX %d,"
6489                             "fromY %d, toX %d, toY %d\n",
6490                             fromX, fromY, toX, toY);
6491             }
6492             return;
6493         }
6494         break;
6495
6496       case IcsPlayingWhite:
6497         /* User is moving for White */
6498         if (!WhiteOnMove(currentMove)) {
6499             if (!appData.premove) {
6500                 DisplayMoveError(_("It is Black's turn"));
6501             } else if (toX >= 0 && toY >= 0) {
6502                 premoveToX = toX;
6503                 premoveToY = toY;
6504                 premoveFromX = fromX;
6505                 premoveFromY = fromY;
6506                 premovePromoChar = promoChar;
6507                 gotPremove = 1;
6508                 if (appData.debugMode)
6509                     fprintf(debugFP, "Got premove: fromX %d,"
6510                             "fromY %d, toX %d, toY %d\n",
6511                             fromX, fromY, toX, toY);
6512             }
6513             return;
6514         }
6515         break;
6516
6517       default:
6518         break;
6519
6520       case EditPosition:
6521         /* EditPosition, empty square, or different color piece;
6522            click-click move is possible */
6523         if (toX == -2 || toY == -2) {
6524             boards[0][fromY][fromX] = EmptySquare;
6525             DrawPosition(FALSE, boards[currentMove]);
6526             return;
6527         } else if (toX >= 0 && toY >= 0) {
6528             boards[0][toY][toX] = boards[0][fromY][fromX];
6529             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6530                 if(boards[0][fromY][0] != EmptySquare) {
6531                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6532                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6533                 }
6534             } else
6535             if(fromX == BOARD_RGHT+1) {
6536                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6537                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6538                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6539                 }
6540             } else
6541             boards[0][fromY][fromX] = EmptySquare;
6542             DrawPosition(FALSE, boards[currentMove]);
6543             return;
6544         }
6545         return;
6546     }
6547
6548     if(toX < 0 || toY < 0) return;
6549     pdown = boards[currentMove][fromY][fromX];
6550     pup = boards[currentMove][toY][toX];
6551
6552     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6553     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6554          if( pup != EmptySquare ) return;
6555          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6556            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6557                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6558            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6559            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6560            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6561            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6562          fromY = DROP_RANK;
6563     }
6564
6565     /* [HGM] always test for legality, to get promotion info */
6566     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6567                                          fromY, fromX, toY, toX, promoChar);
6568
6569     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6570
6571     /* [HGM] but possibly ignore an IllegalMove result */
6572     if (appData.testLegality) {
6573         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6574             DisplayMoveError(_("Illegal move"));
6575             return;
6576         }
6577     }
6578
6579     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6580 }
6581
6582 /* Common tail of UserMoveEvent and DropMenuEvent */
6583 int
6584 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6585 {
6586     char *bookHit = 0;
6587
6588     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6589         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6590         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6591         if(WhiteOnMove(currentMove)) {
6592             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6593         } else {
6594             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6595         }
6596     }
6597
6598     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6599        move type in caller when we know the move is a legal promotion */
6600     if(moveType == NormalMove && promoChar)
6601         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6602
6603     /* [HGM] <popupFix> The following if has been moved here from
6604        UserMoveEvent(). Because it seemed to belong here (why not allow
6605        piece drops in training games?), and because it can only be
6606        performed after it is known to what we promote. */
6607     if (gameMode == Training) {
6608       /* compare the move played on the board to the next move in the
6609        * game. If they match, display the move and the opponent's response.
6610        * If they don't match, display an error message.
6611        */
6612       int saveAnimate;
6613       Board testBoard;
6614       CopyBoard(testBoard, boards[currentMove]);
6615       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6616
6617       if (CompareBoards(testBoard, boards[currentMove+1])) {
6618         ForwardInner(currentMove+1);
6619
6620         /* Autoplay the opponent's response.
6621          * if appData.animate was TRUE when Training mode was entered,
6622          * the response will be animated.
6623          */
6624         saveAnimate = appData.animate;
6625         appData.animate = animateTraining;
6626         ForwardInner(currentMove+1);
6627         appData.animate = saveAnimate;
6628
6629         /* check for the end of the game */
6630         if (currentMove >= forwardMostMove) {
6631           gameMode = PlayFromGameFile;
6632           ModeHighlight();
6633           SetTrainingModeOff();
6634           DisplayInformation(_("End of game"));
6635         }
6636       } else {
6637         DisplayError(_("Incorrect move"), 0);
6638       }
6639       return 1;
6640     }
6641
6642   /* Ok, now we know that the move is good, so we can kill
6643      the previous line in Analysis Mode */
6644   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6645                                 && currentMove < forwardMostMove) {
6646     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6647     else forwardMostMove = currentMove;
6648   }
6649
6650   /* If we need the chess program but it's dead, restart it */
6651   ResurrectChessProgram();
6652
6653   /* A user move restarts a paused game*/
6654   if (pausing)
6655     PauseEvent();
6656
6657   thinkOutput[0] = NULLCHAR;
6658
6659   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6660
6661   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6662     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6663     return 1;
6664   }
6665
6666   if (gameMode == BeginningOfGame) {
6667     if (appData.noChessProgram) {
6668       gameMode = EditGame;
6669       SetGameInfo();
6670     } else {
6671       char buf[MSG_SIZ];
6672       gameMode = MachinePlaysBlack;
6673       StartClocks();
6674       SetGameInfo();
6675       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6676       DisplayTitle(buf);
6677       if (first.sendName) {
6678         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6679         SendToProgram(buf, &first);
6680       }
6681       StartClocks();
6682     }
6683     ModeHighlight();
6684   }
6685
6686   /* Relay move to ICS or chess engine */
6687   if (appData.icsActive) {
6688     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6689         gameMode == IcsExamining) {
6690       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6691         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6692         SendToICS("draw ");
6693         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6694       }
6695       // also send plain move, in case ICS does not understand atomic claims
6696       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6697       ics_user_moved = 1;
6698     }
6699   } else {
6700     if (first.sendTime && (gameMode == BeginningOfGame ||
6701                            gameMode == MachinePlaysWhite ||
6702                            gameMode == MachinePlaysBlack)) {
6703       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6704     }
6705     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6706          // [HGM] book: if program might be playing, let it use book
6707         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6708         first.maybeThinking = TRUE;
6709     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6710         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6711         SendBoard(&first, currentMove+1);
6712     } else SendMoveToProgram(forwardMostMove-1, &first);
6713     if (currentMove == cmailOldMove + 1) {
6714       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6715     }
6716   }
6717
6718   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6719
6720   switch (gameMode) {
6721   case EditGame:
6722     if(appData.testLegality)
6723     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6724     case MT_NONE:
6725     case MT_CHECK:
6726       break;
6727     case MT_CHECKMATE:
6728     case MT_STAINMATE:
6729       if (WhiteOnMove(currentMove)) {
6730         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6731       } else {
6732         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6733       }
6734       break;
6735     case MT_STALEMATE:
6736       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6737       break;
6738     }
6739     break;
6740
6741   case MachinePlaysBlack:
6742   case MachinePlaysWhite:
6743     /* disable certain menu options while machine is thinking */
6744     SetMachineThinkingEnables();
6745     break;
6746
6747   default:
6748     break;
6749   }
6750
6751   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6752   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6753
6754   if(bookHit) { // [HGM] book: simulate book reply
6755         static char bookMove[MSG_SIZ]; // a bit generous?
6756
6757         programStats.nodes = programStats.depth = programStats.time =
6758         programStats.score = programStats.got_only_move = 0;
6759         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6760
6761         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6762         strcat(bookMove, bookHit);
6763         HandleMachineMove(bookMove, &first);
6764   }
6765   return 1;
6766 }
6767
6768 void
6769 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6770 {
6771     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6772     Markers *m = (Markers *) closure;
6773     if(rf == fromY && ff == fromX)
6774         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6775                          || kind == WhiteCapturesEnPassant
6776                          || kind == BlackCapturesEnPassant);
6777     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6778 }
6779
6780 void
6781 MarkTargetSquares (int clear)
6782 {
6783   int x, y;
6784   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6785      !appData.testLegality || gameMode == EditPosition) return;
6786   if(clear) {
6787     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6788   } else {
6789     int capt = 0;
6790     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6791     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6792       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6793       if(capt)
6794       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6795     }
6796   }
6797   DrawPosition(TRUE, NULL);
6798 }
6799
6800 int
6801 Explode (Board board, int fromX, int fromY, int toX, int toY)
6802 {
6803     if(gameInfo.variant == VariantAtomic &&
6804        (board[toY][toX] != EmptySquare ||                     // capture?
6805         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6806                          board[fromY][fromX] == BlackPawn   )
6807       )) {
6808         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6809         return TRUE;
6810     }
6811     return FALSE;
6812 }
6813
6814 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6815
6816 int
6817 CanPromote (ChessSquare piece, int y)
6818 {
6819         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6820         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6821         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6822            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6823            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6824                                                   gameInfo.variant == VariantMakruk) return FALSE;
6825         return (piece == BlackPawn && y == 1 ||
6826                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6827                 piece == BlackLance && y == 1 ||
6828                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6829 }
6830
6831 void
6832 LeftClick (ClickType clickType, int xPix, int yPix)
6833 {
6834     int x, y;
6835     Boolean saveAnimate;
6836     static int second = 0, promotionChoice = 0, clearFlag = 0;
6837     char promoChoice = NULLCHAR;
6838     ChessSquare piece;
6839
6840     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6841
6842     if (clickType == Press) ErrorPopDown();
6843
6844     x = EventToSquare(xPix, BOARD_WIDTH);
6845     y = EventToSquare(yPix, BOARD_HEIGHT);
6846     if (!flipView && y >= 0) {
6847         y = BOARD_HEIGHT - 1 - y;
6848     }
6849     if (flipView && x >= 0) {
6850         x = BOARD_WIDTH - 1 - x;
6851     }
6852
6853     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6854         defaultPromoChoice = promoSweep;
6855         promoSweep = EmptySquare;   // terminate sweep
6856         promoDefaultAltered = TRUE;
6857         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6858     }
6859
6860     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6861         if(clickType == Release) return; // ignore upclick of click-click destination
6862         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6863         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6864         if(gameInfo.holdingsWidth &&
6865                 (WhiteOnMove(currentMove)
6866                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6867                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6868             // click in right holdings, for determining promotion piece
6869             ChessSquare p = boards[currentMove][y][x];
6870             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6871             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6872             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6873                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6874                 fromX = fromY = -1;
6875                 return;
6876             }
6877         }
6878         DrawPosition(FALSE, boards[currentMove]);
6879         return;
6880     }
6881
6882     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6883     if(clickType == Press
6884             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6885               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6886               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6887         return;
6888
6889     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
6890         // could be static click on premove from-square: abort premove
6891         gotPremove = 0;
6892         ClearPremoveHighlights();
6893     }
6894
6895     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6896         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6897
6898     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6899         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6900                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6901         defaultPromoChoice = DefaultPromoChoice(side);
6902     }
6903
6904     autoQueen = appData.alwaysPromoteToQueen;
6905
6906     if (fromX == -1) {
6907       int originalY = y;
6908       gatingPiece = EmptySquare;
6909       if (clickType != Press) {
6910         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6911             DragPieceEnd(xPix, yPix); dragging = 0;
6912             DrawPosition(FALSE, NULL);
6913         }
6914         return;
6915       }
6916       fromX = x; fromY = y; toX = toY = -1;
6917       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6918          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6919          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6920             /* First square */
6921             if (OKToStartUserMove(fromX, fromY)) {
6922                 second = 0;
6923                 MarkTargetSquares(0);
6924                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6925                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6926                     promoSweep = defaultPromoChoice;
6927                     selectFlag = 0; lastX = xPix; lastY = yPix;
6928                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6929                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6930                 }
6931                 if (appData.highlightDragging) {
6932                     SetHighlights(fromX, fromY, -1, -1);
6933                 }
6934             } else fromX = fromY = -1;
6935             return;
6936         }
6937     }
6938
6939     /* fromX != -1 */
6940     if (clickType == Press && gameMode != EditPosition) {
6941         ChessSquare fromP;
6942         ChessSquare toP;
6943         int frc;
6944
6945         // ignore off-board to clicks
6946         if(y < 0 || x < 0) return;
6947
6948         /* Check if clicking again on the same color piece */
6949         fromP = boards[currentMove][fromY][fromX];
6950         toP = boards[currentMove][y][x];
6951         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6952         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6953              WhitePawn <= toP && toP <= WhiteKing &&
6954              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6955              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6956             (BlackPawn <= fromP && fromP <= BlackKing &&
6957              BlackPawn <= toP && toP <= BlackKing &&
6958              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6959              !(fromP == BlackKing && toP == BlackRook && frc))) {
6960             /* Clicked again on same color piece -- changed his mind */
6961             second = (x == fromX && y == fromY);
6962             promoDefaultAltered = FALSE;
6963             MarkTargetSquares(1);
6964            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6965             if (appData.highlightDragging) {
6966                 SetHighlights(x, y, -1, -1);
6967             } else {
6968                 ClearHighlights();
6969             }
6970             if (OKToStartUserMove(x, y)) {
6971                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6972                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6973                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6974                  gatingPiece = boards[currentMove][fromY][fromX];
6975                 else gatingPiece = EmptySquare;
6976                 fromX = x;
6977                 fromY = y; dragging = 1;
6978                 MarkTargetSquares(0);
6979                 DragPieceBegin(xPix, yPix, FALSE);
6980                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6981                     promoSweep = defaultPromoChoice;
6982                     selectFlag = 0; lastX = xPix; lastY = yPix;
6983                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6984                 }
6985             }
6986            }
6987            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6988            second = FALSE; 
6989         }
6990         // ignore clicks on holdings
6991         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6992     }
6993
6994     if (clickType == Release && x == fromX && y == fromY) {
6995         DragPieceEnd(xPix, yPix); dragging = 0;
6996         if(clearFlag) {
6997             // a deferred attempt to click-click move an empty square on top of a piece
6998             boards[currentMove][y][x] = EmptySquare;
6999             ClearHighlights();
7000             DrawPosition(FALSE, boards[currentMove]);
7001             fromX = fromY = -1; clearFlag = 0;
7002             return;
7003         }
7004         if (appData.animateDragging) {
7005             /* Undo animation damage if any */
7006             DrawPosition(FALSE, NULL);
7007         }
7008         if (second) {
7009             /* Second up/down in same square; just abort move */
7010             second = 0;
7011             fromX = fromY = -1;
7012             gatingPiece = EmptySquare;
7013             ClearHighlights();
7014             gotPremove = 0;
7015             ClearPremoveHighlights();
7016         } else {
7017             /* First upclick in same square; start click-click mode */
7018             SetHighlights(x, y, -1, -1);
7019         }
7020         return;
7021     }
7022
7023     clearFlag = 0;
7024
7025     /* we now have a different from- and (possibly off-board) to-square */
7026     /* Completed move */
7027     toX = x;
7028     toY = y;
7029     saveAnimate = appData.animate;
7030     MarkTargetSquares(1);
7031     if (clickType == Press) {
7032         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7033             // must be Edit Position mode with empty-square selected
7034             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7035             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7036             return;
7037         }
7038         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7039             ChessSquare piece = boards[currentMove][fromY][fromX];
7040             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7041             promoSweep = defaultPromoChoice;
7042             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7043             selectFlag = 0; lastX = xPix; lastY = yPix;
7044             Sweep(0); // Pawn that is going to promote: preview promotion piece
7045             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7046             DrawPosition(FALSE, boards[currentMove]);
7047             return;
7048         }
7049         /* Finish clickclick move */
7050         if (appData.animate || appData.highlightLastMove) {
7051             SetHighlights(fromX, fromY, toX, toY);
7052         } else {
7053             ClearHighlights();
7054         }
7055     } else {
7056         /* Finish drag move */
7057         if (appData.highlightLastMove) {
7058             SetHighlights(fromX, fromY, toX, toY);
7059         } else {
7060             ClearHighlights();
7061         }
7062         DragPieceEnd(xPix, yPix); dragging = 0;
7063         /* Don't animate move and drag both */
7064         appData.animate = FALSE;
7065     }
7066
7067     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7068     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7069         ChessSquare piece = boards[currentMove][fromY][fromX];
7070         if(gameMode == EditPosition && piece != EmptySquare &&
7071            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7072             int n;
7073
7074             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7075                 n = PieceToNumber(piece - (int)BlackPawn);
7076                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7077                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7078                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7079             } else
7080             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7081                 n = PieceToNumber(piece);
7082                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7083                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7084                 boards[currentMove][n][BOARD_WIDTH-2]++;
7085             }
7086             boards[currentMove][fromY][fromX] = EmptySquare;
7087         }
7088         ClearHighlights();
7089         fromX = fromY = -1;
7090         DrawPosition(TRUE, boards[currentMove]);
7091         return;
7092     }
7093
7094     // off-board moves should not be highlighted
7095     if(x < 0 || y < 0) ClearHighlights();
7096
7097     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7098
7099     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7100         SetHighlights(fromX, fromY, toX, toY);
7101         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7102             // [HGM] super: promotion to captured piece selected from holdings
7103             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7104             promotionChoice = TRUE;
7105             // kludge follows to temporarily execute move on display, without promoting yet
7106             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7107             boards[currentMove][toY][toX] = p;
7108             DrawPosition(FALSE, boards[currentMove]);
7109             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7110             boards[currentMove][toY][toX] = q;
7111             DisplayMessage("Click in holdings to choose piece", "");
7112             return;
7113         }
7114         PromotionPopUp();
7115     } else {
7116         int oldMove = currentMove;
7117         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7118         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7119         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7120         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7121            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7122             DrawPosition(TRUE, boards[currentMove]);
7123         fromX = fromY = -1;
7124     }
7125     appData.animate = saveAnimate;
7126     if (appData.animate || appData.animateDragging) {
7127         /* Undo animation damage if needed */
7128         DrawPosition(FALSE, NULL);
7129     }
7130 }
7131
7132 int
7133 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7134 {   // front-end-free part taken out of PieceMenuPopup
7135     int whichMenu; int xSqr, ySqr;
7136
7137     if(seekGraphUp) { // [HGM] seekgraph
7138         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7139         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7140         return -2;
7141     }
7142
7143     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7144          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7145         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7146         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7147         if(action == Press)   {
7148             originalFlip = flipView;
7149             flipView = !flipView; // temporarily flip board to see game from partners perspective
7150             DrawPosition(TRUE, partnerBoard);
7151             DisplayMessage(partnerStatus, "");
7152             partnerUp = TRUE;
7153         } else if(action == Release) {
7154             flipView = originalFlip;
7155             DrawPosition(TRUE, boards[currentMove]);
7156             partnerUp = FALSE;
7157         }
7158         return -2;
7159     }
7160
7161     xSqr = EventToSquare(x, BOARD_WIDTH);
7162     ySqr = EventToSquare(y, BOARD_HEIGHT);
7163     if (action == Release) {
7164         if(pieceSweep != EmptySquare) {
7165             EditPositionMenuEvent(pieceSweep, toX, toY);
7166             pieceSweep = EmptySquare;
7167         } else UnLoadPV(); // [HGM] pv
7168     }
7169     if (action != Press) return -2; // return code to be ignored
7170     switch (gameMode) {
7171       case IcsExamining:
7172         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7173       case EditPosition:
7174         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7175         if (xSqr < 0 || ySqr < 0) return -1;
7176         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7177         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7178         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7179         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7180         NextPiece(0);
7181         return 2; // grab
7182       case IcsObserving:
7183         if(!appData.icsEngineAnalyze) return -1;
7184       case IcsPlayingWhite:
7185       case IcsPlayingBlack:
7186         if(!appData.zippyPlay) goto noZip;
7187       case AnalyzeMode:
7188       case AnalyzeFile:
7189       case MachinePlaysWhite:
7190       case MachinePlaysBlack:
7191       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7192         if (!appData.dropMenu) {
7193           LoadPV(x, y);
7194           return 2; // flag front-end to grab mouse events
7195         }
7196         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7197            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7198       case EditGame:
7199       noZip:
7200         if (xSqr < 0 || ySqr < 0) return -1;
7201         if (!appData.dropMenu || appData.testLegality &&
7202             gameInfo.variant != VariantBughouse &&
7203             gameInfo.variant != VariantCrazyhouse) return -1;
7204         whichMenu = 1; // drop menu
7205         break;
7206       default:
7207         return -1;
7208     }
7209
7210     if (((*fromX = xSqr) < 0) ||
7211         ((*fromY = ySqr) < 0)) {
7212         *fromX = *fromY = -1;
7213         return -1;
7214     }
7215     if (flipView)
7216       *fromX = BOARD_WIDTH - 1 - *fromX;
7217     else
7218       *fromY = BOARD_HEIGHT - 1 - *fromY;
7219
7220     return whichMenu;
7221 }
7222
7223 void
7224 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7225 {
7226 //    char * hint = lastHint;
7227     FrontEndProgramStats stats;
7228
7229     stats.which = cps == &first ? 0 : 1;
7230     stats.depth = cpstats->depth;
7231     stats.nodes = cpstats->nodes;
7232     stats.score = cpstats->score;
7233     stats.time = cpstats->time;
7234     stats.pv = cpstats->movelist;
7235     stats.hint = lastHint;
7236     stats.an_move_index = 0;
7237     stats.an_move_count = 0;
7238
7239     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7240         stats.hint = cpstats->move_name;
7241         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7242         stats.an_move_count = cpstats->nr_moves;
7243     }
7244
7245     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
7246
7247     SetProgramStats( &stats );
7248 }
7249
7250 void
7251 ClearEngineOutputPane (int which)
7252 {
7253     static FrontEndProgramStats dummyStats;
7254     dummyStats.which = which;
7255     dummyStats.pv = "#";
7256     SetProgramStats( &dummyStats );
7257 }
7258
7259 #define MAXPLAYERS 500
7260
7261 char *
7262 TourneyStandings (int display)
7263 {
7264     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7265     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7266     char result, *p, *names[MAXPLAYERS];
7267
7268     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7269         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7270     names[0] = p = strdup(appData.participants);
7271     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7272
7273     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7274
7275     while(result = appData.results[nr]) {
7276         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7277         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7278         wScore = bScore = 0;
7279         switch(result) {
7280           case '+': wScore = 2; break;
7281           case '-': bScore = 2; break;
7282           case '=': wScore = bScore = 1; break;
7283           case ' ':
7284           case '*': return strdup("busy"); // tourney not finished
7285         }
7286         score[w] += wScore;
7287         score[b] += bScore;
7288         games[w]++;
7289         games[b]++;
7290         nr++;
7291     }
7292     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7293     for(w=0; w<nPlayers; w++) {
7294         bScore = -1;
7295         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7296         ranking[w] = b; points[w] = bScore; score[b] = -2;
7297     }
7298     p = malloc(nPlayers*34+1);
7299     for(w=0; w<nPlayers && w<display; w++)
7300         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7301     free(names[0]);
7302     return p;
7303 }
7304
7305 void
7306 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7307 {       // count all piece types
7308         int p, f, r;
7309         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7310         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7311         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7312                 p = board[r][f];
7313                 pCnt[p]++;
7314                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7315                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7316                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7317                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7318                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7319                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7320         }
7321 }
7322
7323 int
7324 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7325 {
7326         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7327         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7328
7329         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7330         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7331         if(myPawns == 2 && nMine == 3) // KPP
7332             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7333         if(myPawns == 1 && nMine == 2) // KP
7334             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7335         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7336             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7337         if(myPawns) return FALSE;
7338         if(pCnt[WhiteRook+side])
7339             return pCnt[BlackRook-side] ||
7340                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7341                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7342                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7343         if(pCnt[WhiteCannon+side]) {
7344             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7345             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7346         }
7347         if(pCnt[WhiteKnight+side])
7348             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7349         return FALSE;
7350 }
7351
7352 int
7353 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7354 {
7355         VariantClass v = gameInfo.variant;
7356
7357         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7358         if(v == VariantShatranj) return TRUE; // always winnable through baring
7359         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7360         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7361
7362         if(v == VariantXiangqi) {
7363                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7364
7365                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7366                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7367                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7368                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7369                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7370                 if(stale) // we have at least one last-rank P plus perhaps C
7371                     return majors // KPKX
7372                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7373                 else // KCA*E*
7374                     return pCnt[WhiteFerz+side] // KCAK
7375                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7376                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7377                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7378
7379         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7380                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7381
7382                 if(nMine == 1) return FALSE; // bare King
7383                 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
7384                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7385                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7386                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7387                 if(pCnt[WhiteKnight+side])
7388                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7389                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7390                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7391                 if(nBishops)
7392                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7393                 if(pCnt[WhiteAlfil+side])
7394                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7395                 if(pCnt[WhiteWazir+side])
7396                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7397         }
7398
7399         return TRUE;
7400 }
7401
7402 int
7403 CompareWithRights (Board b1, Board b2)
7404 {
7405     int rights = 0;
7406     if(!CompareBoards(b1, b2)) return FALSE;
7407     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7408     /* compare castling rights */
7409     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7410            rights++; /* King lost rights, while rook still had them */
7411     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7412         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7413            rights++; /* but at least one rook lost them */
7414     }
7415     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7416            rights++;
7417     if( b1[CASTLING][5] != NoRights ) {
7418         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7419            rights++;
7420     }
7421     return rights == 0;
7422 }
7423
7424 int
7425 Adjudicate (ChessProgramState *cps)
7426 {       // [HGM] some adjudications useful with buggy engines
7427         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7428         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7429         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7430         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7431         int k, count = 0; static int bare = 1;
7432         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7433         Boolean canAdjudicate = !appData.icsActive;
7434
7435         // most tests only when we understand the game, i.e. legality-checking on
7436             if( appData.testLegality )
7437             {   /* [HGM] Some more adjudications for obstinate engines */
7438                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7439                 static int moveCount = 6;
7440                 ChessMove result;
7441                 char *reason = NULL;
7442
7443                 /* Count what is on board. */
7444                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7445
7446                 /* Some material-based adjudications that have to be made before stalemate test */
7447                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7448                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7449                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7450                      if(canAdjudicate && appData.checkMates) {
7451                          if(engineOpponent)
7452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7454                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7455                          return 1;
7456                      }
7457                 }
7458
7459                 /* Bare King in Shatranj (loses) or Losers (wins) */
7460                 if( nrW == 1 || nrB == 1) {
7461                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7462                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7463                      if(canAdjudicate && appData.checkMates) {
7464                          if(engineOpponent)
7465                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7466                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7467                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7468                          return 1;
7469                      }
7470                   } else
7471                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7472                   {    /* bare King */
7473                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7474                         if(canAdjudicate && appData.checkMates) {
7475                             /* but only adjudicate if adjudication enabled */
7476                             if(engineOpponent)
7477                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7478                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7479                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7480                             return 1;
7481                         }
7482                   }
7483                 } else bare = 1;
7484
7485
7486             // don't wait for engine to announce game end if we can judge ourselves
7487             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7488               case MT_CHECK:
7489                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7490                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7491                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7492                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7493                             checkCnt++;
7494                         if(checkCnt >= 2) {
7495                             reason = "Xboard adjudication: 3rd check";
7496                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7497                             break;
7498                         }
7499                     }
7500                 }
7501               case MT_NONE:
7502               default:
7503                 break;
7504               case MT_STALEMATE:
7505               case MT_STAINMATE:
7506                 reason = "Xboard adjudication: Stalemate";
7507                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7508                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7509                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7510                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7511                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7512                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7513                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7514                                                                         EP_CHECKMATE : EP_WINS);
7515                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7516                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7517                 }
7518                 break;
7519               case MT_CHECKMATE:
7520                 reason = "Xboard adjudication: Checkmate";
7521                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7522                 break;
7523             }
7524
7525                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7526                     case EP_STALEMATE:
7527                         result = GameIsDrawn; break;
7528                     case EP_CHECKMATE:
7529                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7530                     case EP_WINS:
7531                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7532                     default:
7533                         result = EndOfFile;
7534                 }
7535                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7536                     if(engineOpponent)
7537                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7538                     GameEnds( result, reason, GE_XBOARD );
7539                     return 1;
7540                 }
7541
7542                 /* Next absolutely insufficient mating material. */
7543                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7544                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7545                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7546
7547                      /* always flag draws, for judging claims */
7548                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7549
7550                      if(canAdjudicate && appData.materialDraws) {
7551                          /* but only adjudicate them if adjudication enabled */
7552                          if(engineOpponent) {
7553                            SendToProgram("force\n", engineOpponent); // suppress reply
7554                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7555                          }
7556                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7557                          return 1;
7558                      }
7559                 }
7560
7561                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7562                 if(gameInfo.variant == VariantXiangqi ?
7563                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7564                  : nrW + nrB == 4 &&
7565                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7566                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7567                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7568                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7569                    ) ) {
7570                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7571                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7572                           if(engineOpponent) {
7573                             SendToProgram("force\n", engineOpponent); // suppress reply
7574                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7575                           }
7576                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7577                           return 1;
7578                      }
7579                 } else moveCount = 6;
7580             }
7581
7582         // Repetition draws and 50-move rule can be applied independently of legality testing
7583
7584                 /* Check for rep-draws */
7585                 count = 0;
7586                 for(k = forwardMostMove-2;
7587                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7588                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7589                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7590                     k-=2)
7591                 {   int rights=0;
7592                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7593                         /* compare castling rights */
7594                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7595                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7596                                 rights++; /* King lost rights, while rook still had them */
7597                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7598                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7599                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7600                                    rights++; /* but at least one rook lost them */
7601                         }
7602                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7603                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7604                                 rights++;
7605                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7606                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7607                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7608                                    rights++;
7609                         }
7610                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7611                             && appData.drawRepeats > 1) {
7612                              /* adjudicate after user-specified nr of repeats */
7613                              int result = GameIsDrawn;
7614                              char *details = "XBoard adjudication: repetition draw";
7615                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7616                                 // [HGM] xiangqi: check for forbidden perpetuals
7617                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7618                                 for(m=forwardMostMove; m>k; m-=2) {
7619                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7620                                         ourPerpetual = 0; // the current mover did not always check
7621                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7622                                         hisPerpetual = 0; // the opponent did not always check
7623                                 }
7624                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7625                                                                         ourPerpetual, hisPerpetual);
7626                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7627                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7628                                     details = "Xboard adjudication: perpetual checking";
7629                                 } else
7630                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7631                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7632                                 } else
7633                                 // Now check for perpetual chases
7634                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7635                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7636                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7637                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7638                                         static char resdet[MSG_SIZ];
7639                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7640                                         details = resdet;
7641                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7642                                     } else
7643                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7644                                         break; // Abort repetition-checking loop.
7645                                 }
7646                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7647                              }
7648                              if(engineOpponent) {
7649                                SendToProgram("force\n", engineOpponent); // suppress reply
7650                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7651                              }
7652                              GameEnds( result, details, GE_XBOARD );
7653                              return 1;
7654                         }
7655                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7656                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7657                     }
7658                 }
7659
7660                 /* Now we test for 50-move draws. Determine ply count */
7661                 count = forwardMostMove;
7662                 /* look for last irreversble move */
7663                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7664                     count--;
7665                 /* if we hit starting position, add initial plies */
7666                 if( count == backwardMostMove )
7667                     count -= initialRulePlies;
7668                 count = forwardMostMove - count;
7669                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7670                         // adjust reversible move counter for checks in Xiangqi
7671                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7672                         if(i < backwardMostMove) i = backwardMostMove;
7673                         while(i <= forwardMostMove) {
7674                                 lastCheck = inCheck; // check evasion does not count
7675                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7676                                 if(inCheck || lastCheck) count--; // check does not count
7677                                 i++;
7678                         }
7679                 }
7680                 if( count >= 100)
7681                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7682                          /* this is used to judge if draw claims are legal */
7683                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7684                          if(engineOpponent) {
7685                            SendToProgram("force\n", engineOpponent); // suppress reply
7686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7687                          }
7688                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7689                          return 1;
7690                 }
7691
7692                 /* if draw offer is pending, treat it as a draw claim
7693                  * when draw condition present, to allow engines a way to
7694                  * claim draws before making their move to avoid a race
7695                  * condition occurring after their move
7696                  */
7697                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7698                          char *p = NULL;
7699                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7700                              p = "Draw claim: 50-move rule";
7701                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7702                              p = "Draw claim: 3-fold repetition";
7703                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7704                              p = "Draw claim: insufficient mating material";
7705                          if( p != NULL && canAdjudicate) {
7706                              if(engineOpponent) {
7707                                SendToProgram("force\n", engineOpponent); // suppress reply
7708                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7709                              }
7710                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7711                              return 1;
7712                          }
7713                 }
7714
7715                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7716                     if(engineOpponent) {
7717                       SendToProgram("force\n", engineOpponent); // suppress reply
7718                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7719                     }
7720                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7721                     return 1;
7722                 }
7723         return 0;
7724 }
7725
7726 char *
7727 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7728 {   // [HGM] book: this routine intercepts moves to simulate book replies
7729     char *bookHit = NULL;
7730
7731     //first determine if the incoming move brings opponent into his book
7732     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7733         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7734     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7735     if(bookHit != NULL && !cps->bookSuspend) {
7736         // make sure opponent is not going to reply after receiving move to book position
7737         SendToProgram("force\n", cps);
7738         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7739     }
7740     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7741     // now arrange restart after book miss
7742     if(bookHit) {
7743         // after a book hit we never send 'go', and the code after the call to this routine
7744         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7745         char buf[MSG_SIZ], *move = bookHit;
7746         if(cps->useSAN) {
7747             int fromX, fromY, toX, toY;
7748             char promoChar;
7749             ChessMove moveType;
7750             move = buf + 30;
7751             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7752                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7753                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7754                                     PosFlags(forwardMostMove),
7755                                     fromY, fromX, toY, toX, promoChar, move);
7756             } else {
7757                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7758                 bookHit = NULL;
7759             }
7760         }
7761         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7762         SendToProgram(buf, cps);
7763         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7764     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7765         SendToProgram("go\n", cps);
7766         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7767     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7768         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7769             SendToProgram("go\n", cps);
7770         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7771     }
7772     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7773 }
7774
7775 char *savedMessage;
7776 ChessProgramState *savedState;
7777 void
7778 DeferredBookMove (void)
7779 {
7780         if(savedState->lastPing != savedState->lastPong)
7781                     ScheduleDelayedEvent(DeferredBookMove, 10);
7782         else
7783         HandleMachineMove(savedMessage, savedState);
7784 }
7785
7786 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7787
7788 void
7789 HandleMachineMove (char *message, ChessProgramState *cps)
7790 {
7791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7792     char realname[MSG_SIZ];
7793     int fromX, fromY, toX, toY;
7794     ChessMove moveType;
7795     char promoChar;
7796     char *p, *pv=buf1;
7797     int machineWhite;
7798     char *bookHit;
7799
7800     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7801         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7802         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7803             DisplayError(_("Invalid pairing from pairing engine"), 0);
7804             return;
7805         }
7806         pairingReceived = 1;
7807         NextMatchGame();
7808         return; // Skim the pairing messages here.
7809     }
7810
7811     cps->userError = 0;
7812
7813 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7814     /*
7815      * Kludge to ignore BEL characters
7816      */
7817     while (*message == '\007') message++;
7818
7819     /*
7820      * [HGM] engine debug message: ignore lines starting with '#' character
7821      */
7822     if(cps->debug && *message == '#') return;
7823
7824     /*
7825      * Look for book output
7826      */
7827     if (cps == &first && bookRequested) {
7828         if (message[0] == '\t' || message[0] == ' ') {
7829             /* Part of the book output is here; append it */
7830             strcat(bookOutput, message);
7831             strcat(bookOutput, "  \n");
7832             return;
7833         } else if (bookOutput[0] != NULLCHAR) {
7834             /* All of book output has arrived; display it */
7835             char *p = bookOutput;
7836             while (*p != NULLCHAR) {
7837                 if (*p == '\t') *p = ' ';
7838                 p++;
7839             }
7840             DisplayInformation(bookOutput);
7841             bookRequested = FALSE;
7842             /* Fall through to parse the current output */
7843         }
7844     }
7845
7846     /*
7847      * Look for machine move.
7848      */
7849     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7850         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7851     {
7852         /* This method is only useful on engines that support ping */
7853         if (cps->lastPing != cps->lastPong) {
7854           if (gameMode == BeginningOfGame) {
7855             /* Extra move from before last new; ignore */
7856             if (appData.debugMode) {
7857                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7858             }
7859           } else {
7860             if (appData.debugMode) {
7861                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7862                         cps->which, gameMode);
7863             }
7864
7865             SendToProgram("undo\n", cps);
7866           }
7867           return;
7868         }
7869
7870         switch (gameMode) {
7871           case BeginningOfGame:
7872             /* Extra move from before last reset; ignore */
7873             if (appData.debugMode) {
7874                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7875             }
7876             return;
7877
7878           case EndOfGame:
7879           case IcsIdle:
7880           default:
7881             /* Extra move after we tried to stop.  The mode test is
7882                not a reliable way of detecting this problem, but it's
7883                the best we can do on engines that don't support ping.
7884             */
7885             if (appData.debugMode) {
7886                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7887                         cps->which, gameMode);
7888             }
7889             SendToProgram("undo\n", cps);
7890             return;
7891
7892           case MachinePlaysWhite:
7893           case IcsPlayingWhite:
7894             machineWhite = TRUE;
7895             break;
7896
7897           case MachinePlaysBlack:
7898           case IcsPlayingBlack:
7899             machineWhite = FALSE;
7900             break;
7901
7902           case TwoMachinesPlay:
7903             machineWhite = (cps->twoMachinesColor[0] == 'w');
7904             break;
7905         }
7906         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7907             if (appData.debugMode) {
7908                 fprintf(debugFP,
7909                         "Ignoring move out of turn by %s, gameMode %d"
7910                         ", forwardMost %d\n",
7911                         cps->which, gameMode, forwardMostMove);
7912             }
7913             return;
7914         }
7915
7916         if(cps->alphaRank) AlphaRank(machineMove, 4);
7917         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7918                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7919             /* Machine move could not be parsed; ignore it. */
7920           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7921                     machineMove, _(cps->which));
7922             DisplayError(buf1, 0);
7923             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7924                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7925             if (gameMode == TwoMachinesPlay) {
7926               GameEnds(machineWhite ? BlackWins : WhiteWins,
7927                        buf1, GE_XBOARD);
7928             }
7929             return;
7930         }
7931
7932         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7933         /* So we have to redo legality test with true e.p. status here,  */
7934         /* to make sure an illegal e.p. capture does not slip through,   */
7935         /* to cause a forfeit on a justified illegal-move complaint      */
7936         /* of the opponent.                                              */
7937         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7938            ChessMove moveType;
7939            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7940                              fromY, fromX, toY, toX, promoChar);
7941             if(moveType == IllegalMove) {
7942               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7944                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7945                            buf1, GE_XBOARD);
7946                 return;
7947            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7948            /* [HGM] Kludge to handle engines that send FRC-style castling
7949               when they shouldn't (like TSCP-Gothic) */
7950            switch(moveType) {
7951              case WhiteASideCastleFR:
7952              case BlackASideCastleFR:
7953                toX+=2;
7954                currentMoveString[2]++;
7955                break;
7956              case WhiteHSideCastleFR:
7957              case BlackHSideCastleFR:
7958                toX--;
7959                currentMoveString[2]--;
7960                break;
7961              default: ; // nothing to do, but suppresses warning of pedantic compilers
7962            }
7963         }
7964         hintRequested = FALSE;
7965         lastHint[0] = NULLCHAR;
7966         bookRequested = FALSE;
7967         /* Program may be pondering now */
7968         cps->maybeThinking = TRUE;
7969         if (cps->sendTime == 2) cps->sendTime = 1;
7970         if (cps->offeredDraw) cps->offeredDraw--;
7971
7972         /* [AS] Save move info*/
7973         pvInfoList[ forwardMostMove ].score = programStats.score;
7974         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7975         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7976
7977         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7978
7979         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7980         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7981             int count = 0;
7982
7983             while( count < adjudicateLossPlies ) {
7984                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7985
7986                 if( count & 1 ) {
7987                     score = -score; /* Flip score for winning side */
7988                 }
7989
7990                 if( score > adjudicateLossThreshold ) {
7991                     break;
7992                 }
7993
7994                 count++;
7995             }
7996
7997             if( count >= adjudicateLossPlies ) {
7998                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7999
8000                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8001                     "Xboard adjudication",
8002                     GE_XBOARD );
8003
8004                 return;
8005             }
8006         }
8007
8008         if(Adjudicate(cps)) {
8009             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8010             return; // [HGM] adjudicate: for all automatic game ends
8011         }
8012
8013 #if ZIPPY
8014         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8015             first.initDone) {
8016           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8017                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8018                 SendToICS("draw ");
8019                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8020           }
8021           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           ics_user_moved = 1;
8023           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8024                 char buf[3*MSG_SIZ];
8025
8026                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8027                         programStats.score / 100.,
8028                         programStats.depth,
8029                         programStats.time / 100.,
8030                         (unsigned int)programStats.nodes,
8031                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8032                         programStats.movelist);
8033                 SendToICS(buf);
8034 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8035           }
8036         }
8037 #endif
8038
8039         /* [AS] Clear stats for next move */
8040         ClearProgramStats();
8041         thinkOutput[0] = NULLCHAR;
8042         hiddenThinkOutputState = 0;
8043
8044         bookHit = NULL;
8045         if (gameMode == TwoMachinesPlay) {
8046             /* [HGM] relaying draw offers moved to after reception of move */
8047             /* and interpreting offer as claim if it brings draw condition */
8048             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8049                 SendToProgram("draw\n", cps->other);
8050             }
8051             if (cps->other->sendTime) {
8052                 SendTimeRemaining(cps->other,
8053                                   cps->other->twoMachinesColor[0] == 'w');
8054             }
8055             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8056             if (firstMove && !bookHit) {
8057                 firstMove = FALSE;
8058                 if (cps->other->useColors) {
8059                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8060                 }
8061                 SendToProgram("go\n", cps->other);
8062             }
8063             cps->other->maybeThinking = TRUE;
8064         }
8065
8066         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8067
8068         if (!pausing && appData.ringBellAfterMoves) {
8069             RingBell();
8070         }
8071
8072         /*
8073          * Reenable menu items that were disabled while
8074          * machine was thinking
8075          */
8076         if (gameMode != TwoMachinesPlay)
8077             SetUserThinkingEnables();
8078
8079         // [HGM] book: after book hit opponent has received move and is now in force mode
8080         // force the book reply into it, and then fake that it outputted this move by jumping
8081         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8082         if(bookHit) {
8083                 static char bookMove[MSG_SIZ]; // a bit generous?
8084
8085                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8086                 strcat(bookMove, bookHit);
8087                 message = bookMove;
8088                 cps = cps->other;
8089                 programStats.nodes = programStats.depth = programStats.time =
8090                 programStats.score = programStats.got_only_move = 0;
8091                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8092
8093                 if(cps->lastPing != cps->lastPong) {
8094                     savedMessage = message; // args for deferred call
8095                     savedState = cps;
8096                     ScheduleDelayedEvent(DeferredBookMove, 10);
8097                     return;
8098                 }
8099                 goto FakeBookMove;
8100         }
8101
8102         return;
8103     }
8104
8105     /* Set special modes for chess engines.  Later something general
8106      *  could be added here; for now there is just one kludge feature,
8107      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8108      *  when "xboard" is given as an interactive command.
8109      */
8110     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8111         cps->useSigint = FALSE;
8112         cps->useSigterm = FALSE;
8113     }
8114     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8115       ParseFeatures(message+8, cps);
8116       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8117     }
8118
8119     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8120                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8121       int dummy, s=6; char buf[MSG_SIZ];
8122       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8123       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8124       if(startedFromSetupPosition) return;
8125       ParseFEN(boards[0], &dummy, message+s);
8126       DrawPosition(TRUE, boards[0]);
8127       startedFromSetupPosition = TRUE;
8128       return;
8129     }
8130     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8131      * want this, I was asked to put it in, and obliged.
8132      */
8133     if (!strncmp(message, "setboard ", 9)) {
8134         Board initial_position;
8135
8136         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8137
8138         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8139             DisplayError(_("Bad FEN received from engine"), 0);
8140             return ;
8141         } else {
8142            Reset(TRUE, FALSE);
8143            CopyBoard(boards[0], initial_position);
8144            initialRulePlies = FENrulePlies;
8145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8146            else gameMode = MachinePlaysBlack;
8147            DrawPosition(FALSE, boards[currentMove]);
8148         }
8149         return;
8150     }
8151
8152     /*
8153      * Look for communication commands
8154      */
8155     if (!strncmp(message, "telluser ", 9)) {
8156         if(message[9] == '\\' && message[10] == '\\')
8157             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8158         PlayTellSound();
8159         DisplayNote(message + 9);
8160         return;
8161     }
8162     if (!strncmp(message, "tellusererror ", 14)) {
8163         cps->userError = 1;
8164         if(message[14] == '\\' && message[15] == '\\')
8165             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8166         PlayTellSound();
8167         DisplayError(message + 14, 0);
8168         return;
8169     }
8170     if (!strncmp(message, "tellopponent ", 13)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 13);
8178       }
8179       return;
8180     }
8181     if (!strncmp(message, "tellothers ", 11)) {
8182       if (appData.icsActive) {
8183         if (loggedOn) {
8184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8185           SendToICS(buf1);
8186         }
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellall ", 8)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8194           SendToICS(buf1);
8195         }
8196       } else {
8197         DisplayNote(message + 8);
8198       }
8199       return;
8200     }
8201     if (strncmp(message, "warning", 7) == 0) {
8202         /* Undocumented feature, use tellusererror in new code */
8203         DisplayError(message, 0);
8204         return;
8205     }
8206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8207         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8208         strcat(realname, " query");
8209         AskQuestion(realname, buf2, buf1, cps->pr);
8210         return;
8211     }
8212     /* Commands from the engine directly to ICS.  We don't allow these to be
8213      *  sent until we are logged on. Crafty kibitzes have been known to
8214      *  interfere with the login process.
8215      */
8216     if (loggedOn) {
8217         if (!strncmp(message, "tellics ", 8)) {
8218             SendToICS(message + 8);
8219             SendToICS("\n");
8220             return;
8221         }
8222         if (!strncmp(message, "tellicsnoalias ", 15)) {
8223             SendToICS(ics_prefix);
8224             SendToICS(message + 15);
8225             SendToICS("\n");
8226             return;
8227         }
8228         /* The following are for backward compatibility only */
8229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8231             SendToICS(ics_prefix);
8232             SendToICS(message);
8233             SendToICS("\n");
8234             return;
8235         }
8236     }
8237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8238         return;
8239     }
8240     /*
8241      * If the move is illegal, cancel it and redraw the board.
8242      * Also deal with other error cases.  Matching is rather loose
8243      * here to accommodate engines written before the spec.
8244      */
8245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8246         strncmp(message, "Error", 5) == 0) {
8247         if (StrStr(message, "name") ||
8248             StrStr(message, "rating") || StrStr(message, "?") ||
8249             StrStr(message, "result") || StrStr(message, "board") ||
8250             StrStr(message, "bk") || StrStr(message, "computer") ||
8251             StrStr(message, "variant") || StrStr(message, "hint") ||
8252             StrStr(message, "random") || StrStr(message, "depth") ||
8253             StrStr(message, "accepted")) {
8254             return;
8255         }
8256         if (StrStr(message, "protover")) {
8257           /* Program is responding to input, so it's apparently done
8258              initializing, and this error message indicates it is
8259              protocol version 1.  So we don't need to wait any longer
8260              for it to initialize and send feature commands. */
8261           FeatureDone(cps, 1);
8262           cps->protocolVersion = 1;
8263           return;
8264         }
8265         cps->maybeThinking = FALSE;
8266
8267         if (StrStr(message, "draw")) {
8268             /* Program doesn't have "draw" command */
8269             cps->sendDrawOffers = 0;
8270             return;
8271         }
8272         if (cps->sendTime != 1 &&
8273             (StrStr(message, "time") || StrStr(message, "otim"))) {
8274           /* Program apparently doesn't have "time" or "otim" command */
8275           cps->sendTime = 0;
8276           return;
8277         }
8278         if (StrStr(message, "analyze")) {
8279             cps->analysisSupport = FALSE;
8280             cps->analyzing = FALSE;
8281 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8282             EditGameEvent(); // [HGM] try to preserve loaded game
8283             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8284             DisplayError(buf2, 0);
8285             return;
8286         }
8287         if (StrStr(message, "(no matching move)st")) {
8288           /* Special kludge for GNU Chess 4 only */
8289           cps->stKludge = TRUE;
8290           SendTimeControl(cps, movesPerSession, timeControl,
8291                           timeIncrement, appData.searchDepth,
8292                           searchTime);
8293           return;
8294         }
8295         if (StrStr(message, "(no matching move)sd")) {
8296           /* Special kludge for GNU Chess 4 only */
8297           cps->sdKludge = TRUE;
8298           SendTimeControl(cps, movesPerSession, timeControl,
8299                           timeIncrement, appData.searchDepth,
8300                           searchTime);
8301           return;
8302         }
8303         if (!StrStr(message, "llegal")) {
8304             return;
8305         }
8306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8307             gameMode == IcsIdle) return;
8308         if (forwardMostMove <= backwardMostMove) return;
8309         if (pausing) PauseEvent();
8310       if(appData.forceIllegal) {
8311             // [HGM] illegal: machine refused move; force position after move into it
8312           SendToProgram("force\n", cps);
8313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8315                 // when black is to move, while there might be nothing on a2 or black
8316                 // might already have the move. So send the board as if white has the move.
8317                 // But first we must change the stm of the engine, as it refused the last move
8318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8319                 if(WhiteOnMove(forwardMostMove)) {
8320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8321                     SendBoard(cps, forwardMostMove); // kludgeless board
8322                 } else {
8323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8326                 }
8327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8329                  gameMode == TwoMachinesPlay)
8330               SendToProgram("go\n", cps);
8331             return;
8332       } else
8333         if (gameMode == PlayFromGameFile) {
8334             /* Stop reading this game file */
8335             gameMode = EditGame;
8336             ModeHighlight();
8337         }
8338         /* [HGM] illegal-move claim should forfeit game when Xboard */
8339         /* only passes fully legal moves                            */
8340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8342                                 "False illegal-move claim", GE_XBOARD );
8343             return; // do not take back move we tested as valid
8344         }
8345         currentMove = forwardMostMove-1;
8346         DisplayMove(currentMove-1); /* before DisplayMoveError */
8347         SwitchClocks(forwardMostMove-1); // [HGM] race
8348         DisplayBothClocks();
8349         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8350                 parseList[currentMove], _(cps->which));
8351         DisplayMoveError(buf1);
8352         DrawPosition(FALSE, boards[currentMove]);
8353
8354         SetUserThinkingEnables();
8355         return;
8356     }
8357     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8358         /* Program has a broken "time" command that
8359            outputs a string not ending in newline.
8360            Don't use it. */
8361         cps->sendTime = 0;
8362     }
8363
8364     /*
8365      * If chess program startup fails, exit with an error message.
8366      * Attempts to recover here are futile.
8367      */
8368     if ((StrStr(message, "unknown host") != NULL)
8369         || (StrStr(message, "No remote directory") != NULL)
8370         || (StrStr(message, "not found") != NULL)
8371         || (StrStr(message, "No such file") != NULL)
8372         || (StrStr(message, "can't alloc") != NULL)
8373         || (StrStr(message, "Permission denied") != NULL)) {
8374
8375         cps->maybeThinking = FALSE;
8376         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8377                 _(cps->which), cps->program, cps->host, message);
8378         RemoveInputSource(cps->isr);
8379         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8380             if(cps == &first) appData.noChessProgram = TRUE;
8381             DisplayError(buf1, 0);
8382         }
8383         return;
8384     }
8385
8386     /*
8387      * Look for hint output
8388      */
8389     if (sscanf(message, "Hint: %s", buf1) == 1) {
8390         if (cps == &first && hintRequested) {
8391             hintRequested = FALSE;
8392             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8393                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8394                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8395                                     PosFlags(forwardMostMove),
8396                                     fromY, fromX, toY, toX, promoChar, buf1);
8397                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8398                 DisplayInformation(buf2);
8399             } else {
8400                 /* Hint move could not be parsed!? */
8401               snprintf(buf2, sizeof(buf2),
8402                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8403                         buf1, _(cps->which));
8404                 DisplayError(buf2, 0);
8405             }
8406         } else {
8407           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8408         }
8409         return;
8410     }
8411
8412     /*
8413      * Ignore other messages if game is not in progress
8414      */
8415     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8416         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8417
8418     /*
8419      * look for win, lose, draw, or draw offer
8420      */
8421     if (strncmp(message, "1-0", 3) == 0) {
8422         char *p, *q, *r = "";
8423         p = strchr(message, '{');
8424         if (p) {
8425             q = strchr(p, '}');
8426             if (q) {
8427                 *q = NULLCHAR;
8428                 r = p + 1;
8429             }
8430         }
8431         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8432         return;
8433     } else if (strncmp(message, "0-1", 3) == 0) {
8434         char *p, *q, *r = "";
8435         p = strchr(message, '{');
8436         if (p) {
8437             q = strchr(p, '}');
8438             if (q) {
8439                 *q = NULLCHAR;
8440                 r = p + 1;
8441             }
8442         }
8443         /* Kludge for Arasan 4.1 bug */
8444         if (strcmp(r, "Black resigns") == 0) {
8445             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8446             return;
8447         }
8448         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "1/2", 3) == 0) {
8451         char *p, *q, *r = "";
8452         p = strchr(message, '{');
8453         if (p) {
8454             q = strchr(p, '}');
8455             if (q) {
8456                 *q = NULLCHAR;
8457                 r = p + 1;
8458             }
8459         }
8460
8461         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8462         return;
8463
8464     } else if (strncmp(message, "White resign", 12) == 0) {
8465         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "Black resign", 12) == 0) {
8468         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8469         return;
8470     } else if (strncmp(message, "White matches", 13) == 0 ||
8471                strncmp(message, "Black matches", 13) == 0   ) {
8472         /* [HGM] ignore GNUShogi noises */
8473         return;
8474     } else if (strncmp(message, "White", 5) == 0 &&
8475                message[5] != '(' &&
8476                StrStr(message, "Black") == NULL) {
8477         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8478         return;
8479     } else if (strncmp(message, "Black", 5) == 0 &&
8480                message[5] != '(') {
8481         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strcmp(message, "resign") == 0 ||
8484                strcmp(message, "computer resigns") == 0) {
8485         switch (gameMode) {
8486           case MachinePlaysBlack:
8487           case IcsPlayingBlack:
8488             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8489             break;
8490           case MachinePlaysWhite:
8491           case IcsPlayingWhite:
8492             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8493             break;
8494           case TwoMachinesPlay:
8495             if (cps->twoMachinesColor[0] == 'w')
8496               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8497             else
8498               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8499             break;
8500           default:
8501             /* can't happen */
8502             break;
8503         }
8504         return;
8505     } else if (strncmp(message, "opponent mates", 14) == 0) {
8506         switch (gameMode) {
8507           case MachinePlaysBlack:
8508           case IcsPlayingBlack:
8509             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510             break;
8511           case MachinePlaysWhite:
8512           case IcsPlayingWhite:
8513             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8514             break;
8515           case TwoMachinesPlay:
8516             if (cps->twoMachinesColor[0] == 'w')
8517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8518             else
8519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8520             break;
8521           default:
8522             /* can't happen */
8523             break;
8524         }
8525         return;
8526     } else if (strncmp(message, "computer mates", 14) == 0) {
8527         switch (gameMode) {
8528           case MachinePlaysBlack:
8529           case IcsPlayingBlack:
8530             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8531             break;
8532           case MachinePlaysWhite:
8533           case IcsPlayingWhite:
8534             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8535             break;
8536           case TwoMachinesPlay:
8537             if (cps->twoMachinesColor[0] == 'w')
8538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539             else
8540               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8541             break;
8542           default:
8543             /* can't happen */
8544             break;
8545         }
8546         return;
8547     } else if (strncmp(message, "checkmate", 9) == 0) {
8548         if (WhiteOnMove(forwardMostMove)) {
8549             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8550         } else {
8551             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8552         }
8553         return;
8554     } else if (strstr(message, "Draw") != NULL ||
8555                strstr(message, "game is a draw") != NULL) {
8556         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8557         return;
8558     } else if (strstr(message, "offer") != NULL &&
8559                strstr(message, "draw") != NULL) {
8560 #if ZIPPY
8561         if (appData.zippyPlay && first.initDone) {
8562             /* Relay offer to ICS */
8563             SendToICS(ics_prefix);
8564             SendToICS("draw\n");
8565         }
8566 #endif
8567         cps->offeredDraw = 2; /* valid until this engine moves twice */
8568         if (gameMode == TwoMachinesPlay) {
8569             if (cps->other->offeredDraw) {
8570                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8571             /* [HGM] in two-machine mode we delay relaying draw offer      */
8572             /* until after we also have move, to see if it is really claim */
8573             }
8574         } else if (gameMode == MachinePlaysWhite ||
8575                    gameMode == MachinePlaysBlack) {
8576           if (userOfferedDraw) {
8577             DisplayInformation(_("Machine accepts your draw offer"));
8578             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579           } else {
8580             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8581           }
8582         }
8583     }
8584
8585
8586     /*
8587      * Look for thinking output
8588      */
8589     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8590           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8591                                 ) {
8592         int plylev, mvleft, mvtot, curscore, time;
8593         char mvname[MOVE_LEN];
8594         u64 nodes; // [DM]
8595         char plyext;
8596         int ignore = FALSE;
8597         int prefixHint = FALSE;
8598         mvname[0] = NULLCHAR;
8599
8600         switch (gameMode) {
8601           case MachinePlaysBlack:
8602           case IcsPlayingBlack:
8603             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8604             break;
8605           case MachinePlaysWhite:
8606           case IcsPlayingWhite:
8607             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8608             break;
8609           case AnalyzeMode:
8610           case AnalyzeFile:
8611             break;
8612           case IcsObserving: /* [DM] icsEngineAnalyze */
8613             if (!appData.icsEngineAnalyze) ignore = TRUE;
8614             break;
8615           case TwoMachinesPlay:
8616             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8617                 ignore = TRUE;
8618             }
8619             break;
8620           default:
8621             ignore = TRUE;
8622             break;
8623         }
8624
8625         if (!ignore) {
8626             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8627             buf1[0] = NULLCHAR;
8628             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8629                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8630
8631                 if (plyext != ' ' && plyext != '\t') {
8632                     time *= 100;
8633                 }
8634
8635                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8636                 if( cps->scoreIsAbsolute &&
8637                     ( gameMode == MachinePlaysBlack ||
8638                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8639                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8640                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8641                      !WhiteOnMove(currentMove)
8642                     ) )
8643                 {
8644                     curscore = -curscore;
8645                 }
8646
8647                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8648
8649                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8650                         char buf[MSG_SIZ];
8651                         FILE *f;
8652                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8653                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8654                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8655                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8656                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8657                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8658                                 fclose(f);
8659                         } else DisplayError(_("failed writing PV"), 0);
8660                 }
8661
8662                 tempStats.depth = plylev;
8663                 tempStats.nodes = nodes;
8664                 tempStats.time = time;
8665                 tempStats.score = curscore;
8666                 tempStats.got_only_move = 0;
8667
8668                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8669                         int ticklen;
8670
8671                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8672                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8673                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8674                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8676                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8677                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8678                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8679                 }
8680
8681                 /* Buffer overflow protection */
8682                 if (pv[0] != NULLCHAR) {
8683                     if (strlen(pv) >= sizeof(tempStats.movelist)
8684                         && appData.debugMode) {
8685                         fprintf(debugFP,
8686                                 "PV is too long; using the first %u bytes.\n",
8687                                 (unsigned) sizeof(tempStats.movelist) - 1);
8688                     }
8689
8690                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8691                 } else {
8692                     sprintf(tempStats.movelist, " no PV\n");
8693                 }
8694
8695                 if (tempStats.seen_stat) {
8696                     tempStats.ok_to_send = 1;
8697                 }
8698
8699                 if (strchr(tempStats.movelist, '(') != NULL) {
8700                     tempStats.line_is_book = 1;
8701                     tempStats.nr_moves = 0;
8702                     tempStats.moves_left = 0;
8703                 } else {
8704                     tempStats.line_is_book = 0;
8705                 }
8706
8707                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8708                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8709
8710                 SendProgramStatsToFrontend( cps, &tempStats );
8711
8712                 /*
8713                     [AS] Protect the thinkOutput buffer from overflow... this
8714                     is only useful if buf1 hasn't overflowed first!
8715                 */
8716                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8717                          plylev,
8718                          (gameMode == TwoMachinesPlay ?
8719                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8720                          ((double) curscore) / 100.0,
8721                          prefixHint ? lastHint : "",
8722                          prefixHint ? " " : "" );
8723
8724                 if( buf1[0] != NULLCHAR ) {
8725                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8726
8727                     if( strlen(pv) > max_len ) {
8728                         if( appData.debugMode) {
8729                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8730                         }
8731                         pv[max_len+1] = '\0';
8732                     }
8733
8734                     strcat( thinkOutput, pv);
8735                 }
8736
8737                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8738                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742
8743             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8744                 /* crafty (9.25+) says "(only move) <move>"
8745                  * if there is only 1 legal move
8746                  */
8747                 sscanf(p, "(only move) %s", buf1);
8748                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8749                 sprintf(programStats.movelist, "%s (only move)", buf1);
8750                 programStats.depth = 1;
8751                 programStats.nr_moves = 1;
8752                 programStats.moves_left = 1;
8753                 programStats.nodes = 1;
8754                 programStats.time = 1;
8755                 programStats.got_only_move = 1;
8756
8757                 /* Not really, but we also use this member to
8758                    mean "line isn't going to change" (Crafty
8759                    isn't searching, so stats won't change) */
8760                 programStats.line_is_book = 1;
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8765                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8766                     DisplayMove(currentMove - 1);
8767                 }
8768                 return;
8769             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8770                               &time, &nodes, &plylev, &mvleft,
8771                               &mvtot, mvname) >= 5) {
8772                 /* The stat01: line is from Crafty (9.29+) in response
8773                    to the "." command */
8774                 programStats.seen_stat = 1;
8775                 cps->maybeThinking = TRUE;
8776
8777                 if (programStats.got_only_move || !appData.periodicUpdates)
8778                   return;
8779
8780                 programStats.depth = plylev;
8781                 programStats.time = time;
8782                 programStats.nodes = nodes;
8783                 programStats.moves_left = mvleft;
8784                 programStats.nr_moves = mvtot;
8785                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8786                 programStats.ok_to_send = 1;
8787                 programStats.movelist[0] = '\0';
8788
8789                 SendProgramStatsToFrontend( cps, &programStats );
8790
8791                 return;
8792
8793             } else if (strncmp(message,"++",2) == 0) {
8794                 /* Crafty 9.29+ outputs this */
8795                 programStats.got_fail = 2;
8796                 return;
8797
8798             } else if (strncmp(message,"--",2) == 0) {
8799                 /* Crafty 9.29+ outputs this */
8800                 programStats.got_fail = 1;
8801                 return;
8802
8803             } else if (thinkOutput[0] != NULLCHAR &&
8804                        strncmp(message, "    ", 4) == 0) {
8805                 unsigned message_len;
8806
8807                 p = message;
8808                 while (*p && *p == ' ') p++;
8809
8810                 message_len = strlen( p );
8811
8812                 /* [AS] Avoid buffer overflow */
8813                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8814                     strcat(thinkOutput, " ");
8815                     strcat(thinkOutput, p);
8816                 }
8817
8818                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8819                     strcat(programStats.movelist, " ");
8820                     strcat(programStats.movelist, p);
8821                 }
8822
8823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8825                     DisplayMove(currentMove - 1);
8826                 }
8827                 return;
8828             }
8829         }
8830         else {
8831             buf1[0] = NULLCHAR;
8832
8833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8835             {
8836                 ChessProgramStats cpstats;
8837
8838                 if (plyext != ' ' && plyext != '\t') {
8839                     time *= 100;
8840                 }
8841
8842                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8843                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8844                     curscore = -curscore;
8845                 }
8846
8847                 cpstats.depth = plylev;
8848                 cpstats.nodes = nodes;
8849                 cpstats.time = time;
8850                 cpstats.score = curscore;
8851                 cpstats.got_only_move = 0;
8852                 cpstats.movelist[0] = '\0';
8853
8854                 if (buf1[0] != NULLCHAR) {
8855                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8856                 }
8857
8858                 cpstats.ok_to_send = 0;
8859                 cpstats.line_is_book = 0;
8860                 cpstats.nr_moves = 0;
8861                 cpstats.moves_left = 0;
8862
8863                 SendProgramStatsToFrontend( cps, &cpstats );
8864             }
8865         }
8866     }
8867 }
8868
8869
8870 /* Parse a game score from the character string "game", and
8871    record it as the history of the current game.  The game
8872    score is NOT assumed to start from the standard position.
8873    The display is not updated in any way.
8874    */
8875 void
8876 ParseGameHistory (char *game)
8877 {
8878     ChessMove moveType;
8879     int fromX, fromY, toX, toY, boardIndex;
8880     char promoChar;
8881     char *p, *q;
8882     char buf[MSG_SIZ];
8883
8884     if (appData.debugMode)
8885       fprintf(debugFP, "Parsing game history: %s\n", game);
8886
8887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8888     gameInfo.site = StrSave(appData.icsHost);
8889     gameInfo.date = PGNDate();
8890     gameInfo.round = StrSave("-");
8891
8892     /* Parse out names of players */
8893     while (*game == ' ') game++;
8894     p = buf;
8895     while (*game != ' ') *p++ = *game++;
8896     *p = NULLCHAR;
8897     gameInfo.white = StrSave(buf);
8898     while (*game == ' ') game++;
8899     p = buf;
8900     while (*game != ' ' && *game != '\n') *p++ = *game++;
8901     *p = NULLCHAR;
8902     gameInfo.black = StrSave(buf);
8903
8904     /* Parse moves */
8905     boardIndex = blackPlaysFirst ? 1 : 0;
8906     yynewstr(game);
8907     for (;;) {
8908         yyboardindex = boardIndex;
8909         moveType = (ChessMove) Myylex();
8910         switch (moveType) {
8911           case IllegalMove:             /* maybe suicide chess, etc. */
8912   if (appData.debugMode) {
8913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8915     setbuf(debugFP, NULL);
8916   }
8917           case WhitePromotion:
8918           case BlackPromotion:
8919           case WhiteNonPromotion:
8920           case BlackNonPromotion:
8921           case NormalMove:
8922           case WhiteCapturesEnPassant:
8923           case BlackCapturesEnPassant:
8924           case WhiteKingSideCastle:
8925           case WhiteQueenSideCastle:
8926           case BlackKingSideCastle:
8927           case BlackQueenSideCastle:
8928           case WhiteKingSideCastleWild:
8929           case WhiteQueenSideCastleWild:
8930           case BlackKingSideCastleWild:
8931           case BlackQueenSideCastleWild:
8932           /* PUSH Fabien */
8933           case WhiteHSideCastleFR:
8934           case WhiteASideCastleFR:
8935           case BlackHSideCastleFR:
8936           case BlackASideCastleFR:
8937           /* POP Fabien */
8938             fromX = currentMoveString[0] - AAA;
8939             fromY = currentMoveString[1] - ONE;
8940             toX = currentMoveString[2] - AAA;
8941             toY = currentMoveString[3] - ONE;
8942             promoChar = currentMoveString[4];
8943             break;
8944           case WhiteDrop:
8945           case BlackDrop:
8946             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8947             fromX = moveType == WhiteDrop ?
8948               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8949             (int) CharToPiece(ToLower(currentMoveString[0]));
8950             fromY = DROP_RANK;
8951             toX = currentMoveString[2] - AAA;
8952             toY = currentMoveString[3] - ONE;
8953             promoChar = NULLCHAR;
8954             break;
8955           case AmbiguousMove:
8956             /* bug? */
8957             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8958   if (appData.debugMode) {
8959     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8960     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8961     setbuf(debugFP, NULL);
8962   }
8963             DisplayError(buf, 0);
8964             return;
8965           case ImpossibleMove:
8966             /* bug? */
8967             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8968   if (appData.debugMode) {
8969     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8970     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8971     setbuf(debugFP, NULL);
8972   }
8973             DisplayError(buf, 0);
8974             return;
8975           case EndOfFile:
8976             if (boardIndex < backwardMostMove) {
8977                 /* Oops, gap.  How did that happen? */
8978                 DisplayError(_("Gap in move list"), 0);
8979                 return;
8980             }
8981             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8982             if (boardIndex > forwardMostMove) {
8983                 forwardMostMove = boardIndex;
8984             }
8985             return;
8986           case ElapsedTime:
8987             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8988                 strcat(parseList[boardIndex-1], " ");
8989                 strcat(parseList[boardIndex-1], yy_text);
8990             }
8991             continue;
8992           case Comment:
8993           case PGNTag:
8994           case NAG:
8995           default:
8996             /* ignore */
8997             continue;
8998           case WhiteWins:
8999           case BlackWins:
9000           case GameIsDrawn:
9001           case GameUnfinished:
9002             if (gameMode == IcsExamining) {
9003                 if (boardIndex < backwardMostMove) {
9004                     /* Oops, gap.  How did that happen? */
9005                     return;
9006                 }
9007                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9008                 return;
9009             }
9010             gameInfo.result = moveType;
9011             p = strchr(yy_text, '{');
9012             if (p == NULL) p = strchr(yy_text, '(');
9013             if (p == NULL) {
9014                 p = yy_text;
9015                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9016             } else {
9017                 q = strchr(p, *p == '{' ? '}' : ')');
9018                 if (q != NULL) *q = NULLCHAR;
9019                 p++;
9020             }
9021             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9022             gameInfo.resultDetails = StrSave(p);
9023             continue;
9024         }
9025         if (boardIndex >= forwardMostMove &&
9026             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9027             backwardMostMove = blackPlaysFirst ? 1 : 0;
9028             return;
9029         }
9030         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9031                                  fromY, fromX, toY, toX, promoChar,
9032                                  parseList[boardIndex]);
9033         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9034         /* currentMoveString is set as a side-effect of yylex */
9035         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9036         strcat(moveList[boardIndex], "\n");
9037         boardIndex++;
9038         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9039         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9040           case MT_NONE:
9041           case MT_STALEMATE:
9042           default:
9043             break;
9044           case MT_CHECK:
9045             if(gameInfo.variant != VariantShogi)
9046                 strcat(parseList[boardIndex - 1], "+");
9047             break;
9048           case MT_CHECKMATE:
9049           case MT_STAINMATE:
9050             strcat(parseList[boardIndex - 1], "#");
9051             break;
9052         }
9053     }
9054 }
9055
9056
9057 /* Apply a move to the given board  */
9058 void
9059 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9060 {
9061   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9062   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9063
9064     /* [HGM] compute & store e.p. status and castling rights for new position */
9065     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9066
9067       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9068       oldEP = (signed char)board[EP_STATUS];
9069       board[EP_STATUS] = EP_NONE;
9070
9071   if (fromY == DROP_RANK) {
9072         /* must be first */
9073         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9074             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9075             return;
9076         }
9077         piece = board[toY][toX] = (ChessSquare) fromX;
9078   } else {
9079       int i;
9080
9081       if( board[toY][toX] != EmptySquare )
9082            board[EP_STATUS] = EP_CAPTURE;
9083
9084       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9085            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9086                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9087       } else
9088       if( board[fromY][fromX] == WhitePawn ) {
9089            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9090                board[EP_STATUS] = EP_PAWN_MOVE;
9091            if( toY-fromY==2) {
9092                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9093                         gameInfo.variant != VariantBerolina || toX < fromX)
9094                       board[EP_STATUS] = toX | berolina;
9095                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9096                         gameInfo.variant != VariantBerolina || toX > fromX)
9097                       board[EP_STATUS] = toX;
9098            }
9099       } else
9100       if( board[fromY][fromX] == BlackPawn ) {
9101            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9102                board[EP_STATUS] = EP_PAWN_MOVE;
9103            if( toY-fromY== -2) {
9104                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9105                         gameInfo.variant != VariantBerolina || toX < fromX)
9106                       board[EP_STATUS] = toX | berolina;
9107                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9108                         gameInfo.variant != VariantBerolina || toX > fromX)
9109                       board[EP_STATUS] = toX;
9110            }
9111        }
9112
9113        for(i=0; i<nrCastlingRights; i++) {
9114            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9115               board[CASTLING][i] == toX   && castlingRank[i] == toY
9116              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9117        }
9118
9119      if (fromX == toX && fromY == toY) return;
9120
9121      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9122      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9123      if(gameInfo.variant == VariantKnightmate)
9124          king += (int) WhiteUnicorn - (int) WhiteKing;
9125
9126     /* Code added by Tord: */
9127     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9128     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9129         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9130       board[fromY][fromX] = EmptySquare;
9131       board[toY][toX] = EmptySquare;
9132       if((toX > fromX) != (piece == WhiteRook)) {
9133         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9134       } else {
9135         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9136       }
9137     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9138                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9139       board[fromY][fromX] = EmptySquare;
9140       board[toY][toX] = EmptySquare;
9141       if((toX > fromX) != (piece == BlackRook)) {
9142         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9143       } else {
9144         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9145       }
9146     /* End of code added by Tord */
9147
9148     } else if (board[fromY][fromX] == king
9149         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9150         && toY == fromY && toX > fromX+1) {
9151         board[fromY][fromX] = EmptySquare;
9152         board[toY][toX] = king;
9153         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9154         board[fromY][BOARD_RGHT-1] = EmptySquare;
9155     } else if (board[fromY][fromX] == king
9156         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9157                && toY == fromY && toX < fromX-1) {
9158         board[fromY][fromX] = EmptySquare;
9159         board[toY][toX] = king;
9160         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9161         board[fromY][BOARD_LEFT] = EmptySquare;
9162     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9163                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9164                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9165                ) {
9166         /* white pawn promotion */
9167         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9168         if(gameInfo.variant==VariantBughouse ||
9169            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9170             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9171         board[fromY][fromX] = EmptySquare;
9172     } else if ((fromY >= BOARD_HEIGHT>>1)
9173                && (toX != fromX)
9174                && gameInfo.variant != VariantXiangqi
9175                && gameInfo.variant != VariantBerolina
9176                && (board[fromY][fromX] == WhitePawn)
9177                && (board[toY][toX] == EmptySquare)) {
9178         board[fromY][fromX] = EmptySquare;
9179         board[toY][toX] = WhitePawn;
9180         captured = board[toY - 1][toX];
9181         board[toY - 1][toX] = EmptySquare;
9182     } else if ((fromY == BOARD_HEIGHT-4)
9183                && (toX == fromX)
9184                && gameInfo.variant == VariantBerolina
9185                && (board[fromY][fromX] == WhitePawn)
9186                && (board[toY][toX] == EmptySquare)) {
9187         board[fromY][fromX] = EmptySquare;
9188         board[toY][toX] = WhitePawn;
9189         if(oldEP & EP_BEROLIN_A) {
9190                 captured = board[fromY][fromX-1];
9191                 board[fromY][fromX-1] = EmptySquare;
9192         }else{  captured = board[fromY][fromX+1];
9193                 board[fromY][fromX+1] = EmptySquare;
9194         }
9195     } else if (board[fromY][fromX] == king
9196         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9197                && toY == fromY && toX > fromX+1) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = king;
9200         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9201         board[fromY][BOARD_RGHT-1] = EmptySquare;
9202     } else if (board[fromY][fromX] == king
9203         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9204                && toY == fromY && toX < fromX-1) {
9205         board[fromY][fromX] = EmptySquare;
9206         board[toY][toX] = king;
9207         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9208         board[fromY][BOARD_LEFT] = EmptySquare;
9209     } else if (fromY == 7 && fromX == 3
9210                && board[fromY][fromX] == BlackKing
9211                && toY == 7 && toX == 5) {
9212         board[fromY][fromX] = EmptySquare;
9213         board[toY][toX] = BlackKing;
9214         board[fromY][7] = EmptySquare;
9215         board[toY][4] = BlackRook;
9216     } else if (fromY == 7 && fromX == 3
9217                && board[fromY][fromX] == BlackKing
9218                && toY == 7 && toX == 1) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = BlackKing;
9221         board[fromY][0] = EmptySquare;
9222         board[toY][2] = BlackRook;
9223     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9224                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9225                && toY < promoRank && promoChar
9226                ) {
9227         /* black pawn promotion */
9228         board[toY][toX] = CharToPiece(ToLower(promoChar));
9229         if(gameInfo.variant==VariantBughouse ||
9230            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9231             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9232         board[fromY][fromX] = EmptySquare;
9233     } else if ((fromY < BOARD_HEIGHT>>1)
9234                && (toX != fromX)
9235                && gameInfo.variant != VariantXiangqi
9236                && gameInfo.variant != VariantBerolina
9237                && (board[fromY][fromX] == BlackPawn)
9238                && (board[toY][toX] == EmptySquare)) {
9239         board[fromY][fromX] = EmptySquare;
9240         board[toY][toX] = BlackPawn;
9241         captured = board[toY + 1][toX];
9242         board[toY + 1][toX] = EmptySquare;
9243     } else if ((fromY == 3)
9244                && (toX == fromX)
9245                && gameInfo.variant == VariantBerolina
9246                && (board[fromY][fromX] == BlackPawn)
9247                && (board[toY][toX] == EmptySquare)) {
9248         board[fromY][fromX] = EmptySquare;
9249         board[toY][toX] = BlackPawn;
9250         if(oldEP & EP_BEROLIN_A) {
9251                 captured = board[fromY][fromX-1];
9252                 board[fromY][fromX-1] = EmptySquare;
9253         }else{  captured = board[fromY][fromX+1];
9254                 board[fromY][fromX+1] = EmptySquare;
9255         }
9256     } else {
9257         board[toY][toX] = board[fromY][fromX];
9258         board[fromY][fromX] = EmptySquare;
9259     }
9260   }
9261
9262     if (gameInfo.holdingsWidth != 0) {
9263
9264       /* !!A lot more code needs to be written to support holdings  */
9265       /* [HGM] OK, so I have written it. Holdings are stored in the */
9266       /* penultimate board files, so they are automaticlly stored   */
9267       /* in the game history.                                       */
9268       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9269                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9270         /* Delete from holdings, by decreasing count */
9271         /* and erasing image if necessary            */
9272         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9273         if(p < (int) BlackPawn) { /* white drop */
9274              p -= (int)WhitePawn;
9275                  p = PieceToNumber((ChessSquare)p);
9276              if(p >= gameInfo.holdingsSize) p = 0;
9277              if(--board[p][BOARD_WIDTH-2] <= 0)
9278                   board[p][BOARD_WIDTH-1] = EmptySquare;
9279              if((int)board[p][BOARD_WIDTH-2] < 0)
9280                         board[p][BOARD_WIDTH-2] = 0;
9281         } else {                  /* black drop */
9282              p -= (int)BlackPawn;
9283                  p = PieceToNumber((ChessSquare)p);
9284              if(p >= gameInfo.holdingsSize) p = 0;
9285              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9286                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9287              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9288                         board[BOARD_HEIGHT-1-p][1] = 0;
9289         }
9290       }
9291       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9292           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9293         /* [HGM] holdings: Add to holdings, if holdings exist */
9294         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9295                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9296                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9297         }
9298         p = (int) captured;
9299         if (p >= (int) BlackPawn) {
9300           p -= (int)BlackPawn;
9301           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9302                   /* in Shogi restore piece to its original  first */
9303                   captured = (ChessSquare) (DEMOTED captured);
9304                   p = DEMOTED p;
9305           }
9306           p = PieceToNumber((ChessSquare)p);
9307           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9308           board[p][BOARD_WIDTH-2]++;
9309           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9310         } else {
9311           p -= (int)WhitePawn;
9312           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9313                   captured = (ChessSquare) (DEMOTED captured);
9314                   p = DEMOTED p;
9315           }
9316           p = PieceToNumber((ChessSquare)p);
9317           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9318           board[BOARD_HEIGHT-1-p][1]++;
9319           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9320         }
9321       }
9322     } else if (gameInfo.variant == VariantAtomic) {
9323       if (captured != EmptySquare) {
9324         int y, x;
9325         for (y = toY-1; y <= toY+1; y++) {
9326           for (x = toX-1; x <= toX+1; x++) {
9327             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9328                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9329               board[y][x] = EmptySquare;
9330             }
9331           }
9332         }
9333         board[toY][toX] = EmptySquare;
9334       }
9335     }
9336     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9337         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9338     } else
9339     if(promoChar == '+') {
9340         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9341         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9342     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9343         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9344     }
9345     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9346                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9347         // [HGM] superchess: take promotion piece out of holdings
9348         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9349         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9350             if(!--board[k][BOARD_WIDTH-2])
9351                 board[k][BOARD_WIDTH-1] = EmptySquare;
9352         } else {
9353             if(!--board[BOARD_HEIGHT-1-k][1])
9354                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9355         }
9356     }
9357
9358 }
9359
9360 /* Updates forwardMostMove */
9361 void
9362 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9363 {
9364 //    forwardMostMove++; // [HGM] bare: moved downstream
9365
9366     (void) CoordsToAlgebraic(boards[forwardMostMove],
9367                              PosFlags(forwardMostMove),
9368                              fromY, fromX, toY, toX, promoChar,
9369                              parseList[forwardMostMove]);
9370
9371     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9372         int timeLeft; static int lastLoadFlag=0; int king, piece;
9373         piece = boards[forwardMostMove][fromY][fromX];
9374         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9375         if(gameInfo.variant == VariantKnightmate)
9376             king += (int) WhiteUnicorn - (int) WhiteKing;
9377         if(forwardMostMove == 0) {
9378             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9379                 fprintf(serverMoves, "%s;", UserName());
9380             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9381                 fprintf(serverMoves, "%s;", second.tidy);
9382             fprintf(serverMoves, "%s;", first.tidy);
9383             if(gameMode == MachinePlaysWhite)
9384                 fprintf(serverMoves, "%s;", UserName());
9385             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9386                 fprintf(serverMoves, "%s;", second.tidy);
9387         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9388         lastLoadFlag = loadFlag;
9389         // print base move
9390         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9391         // print castling suffix
9392         if( toY == fromY && piece == king ) {
9393             if(toX-fromX > 1)
9394                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9395             if(fromX-toX >1)
9396                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9397         }
9398         // e.p. suffix
9399         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9400              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9401              boards[forwardMostMove][toY][toX] == EmptySquare
9402              && fromX != toX && fromY != toY)
9403                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9404         // promotion suffix
9405         if(promoChar != NULLCHAR)
9406                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9407         if(!loadFlag) {
9408                 char buf[MOVE_LEN*2], *p; int len;
9409             fprintf(serverMoves, "/%d/%d",
9410                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9411             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9412             else                      timeLeft = blackTimeRemaining/1000;
9413             fprintf(serverMoves, "/%d", timeLeft);
9414                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9415                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9416                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9417             fprintf(serverMoves, "/%s", buf);
9418         }
9419         fflush(serverMoves);
9420     }
9421
9422     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9423         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9424       return;
9425     }
9426     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9427     if (commentList[forwardMostMove+1] != NULL) {
9428         free(commentList[forwardMostMove+1]);
9429         commentList[forwardMostMove+1] = NULL;
9430     }
9431     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9432     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9433     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9434     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9435     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9436     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9437     adjustedClock = FALSE;
9438     gameInfo.result = GameUnfinished;
9439     if (gameInfo.resultDetails != NULL) {
9440         free(gameInfo.resultDetails);
9441         gameInfo.resultDetails = NULL;
9442     }
9443     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9444                               moveList[forwardMostMove - 1]);
9445     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9446       case MT_NONE:
9447       case MT_STALEMATE:
9448       default:
9449         break;
9450       case MT_CHECK:
9451         if(gameInfo.variant != VariantShogi)
9452             strcat(parseList[forwardMostMove - 1], "+");
9453         break;
9454       case MT_CHECKMATE:
9455       case MT_STAINMATE:
9456         strcat(parseList[forwardMostMove - 1], "#");
9457         break;
9458     }
9459
9460 }
9461
9462 /* Updates currentMove if not pausing */
9463 void
9464 ShowMove (int fromX, int fromY, int toX, int toY)
9465 {
9466     int instant = (gameMode == PlayFromGameFile) ?
9467         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9468     if(appData.noGUI) return;
9469     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9470         if (!instant) {
9471             if (forwardMostMove == currentMove + 1) {
9472                 AnimateMove(boards[forwardMostMove - 1],
9473                             fromX, fromY, toX, toY);
9474             }
9475             if (appData.highlightLastMove) {
9476                 SetHighlights(fromX, fromY, toX, toY);
9477             }
9478         }
9479         currentMove = forwardMostMove;
9480     }
9481
9482     if (instant) return;
9483
9484     DisplayMove(currentMove - 1);
9485     DrawPosition(FALSE, boards[currentMove]);
9486     DisplayBothClocks();
9487     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9488 }
9489
9490 void
9491 SendEgtPath (ChessProgramState *cps)
9492 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9493         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9494
9495         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9496
9497         while(*p) {
9498             char c, *q = name+1, *r, *s;
9499
9500             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9501             while(*p && *p != ',') *q++ = *p++;
9502             *q++ = ':'; *q = 0;
9503             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9504                 strcmp(name, ",nalimov:") == 0 ) {
9505                 // take nalimov path from the menu-changeable option first, if it is defined
9506               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9507                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9508             } else
9509             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9510                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9511                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9512                 s = r = StrStr(s, ":") + 1; // beginning of path info
9513                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9514                 c = *r; *r = 0;             // temporarily null-terminate path info
9515                     *--q = 0;               // strip of trailig ':' from name
9516                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9517                 *r = c;
9518                 SendToProgram(buf,cps);     // send egtbpath command for this format
9519             }
9520             if(*p == ',') p++; // read away comma to position for next format name
9521         }
9522 }
9523
9524 void
9525 InitChessProgram (ChessProgramState *cps, int setup)
9526 /* setup needed to setup FRC opening position */
9527 {
9528     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9529     if (appData.noChessProgram) return;
9530     hintRequested = FALSE;
9531     bookRequested = FALSE;
9532
9533     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9534     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9535     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9536     if(cps->memSize) { /* [HGM] memory */
9537       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9538         SendToProgram(buf, cps);
9539     }
9540     SendEgtPath(cps); /* [HGM] EGT */
9541     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9542       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9543         SendToProgram(buf, cps);
9544     }
9545
9546     SendToProgram(cps->initString, cps);
9547     if (gameInfo.variant != VariantNormal &&
9548         gameInfo.variant != VariantLoadable
9549         /* [HGM] also send variant if board size non-standard */
9550         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9551                                             ) {
9552       char *v = VariantName(gameInfo.variant);
9553       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9554         /* [HGM] in protocol 1 we have to assume all variants valid */
9555         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9556         DisplayFatalError(buf, 0, 1);
9557         return;
9558       }
9559
9560       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9561       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9562       if( gameInfo.variant == VariantXiangqi )
9563            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9564       if( gameInfo.variant == VariantShogi )
9565            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9566       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9567            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9568       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9569           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9570            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9571       if( gameInfo.variant == VariantCourier )
9572            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9573       if( gameInfo.variant == VariantSuper )
9574            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9575       if( gameInfo.variant == VariantGreat )
9576            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9577       if( gameInfo.variant == VariantSChess )
9578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9579       if( gameInfo.variant == VariantGrand )
9580            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9581
9582       if(overruled) {
9583         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9584                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9585            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9586            if(StrStr(cps->variants, b) == NULL) {
9587                // specific sized variant not known, check if general sizing allowed
9588                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9589                    if(StrStr(cps->variants, "boardsize") == NULL) {
9590                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9591                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9592                        DisplayFatalError(buf, 0, 1);
9593                        return;
9594                    }
9595                    /* [HGM] here we really should compare with the maximum supported board size */
9596                }
9597            }
9598       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9599       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9600       SendToProgram(buf, cps);
9601     }
9602     currentlyInitializedVariant = gameInfo.variant;
9603
9604     /* [HGM] send opening position in FRC to first engine */
9605     if(setup) {
9606           SendToProgram("force\n", cps);
9607           SendBoard(cps, 0);
9608           /* engine is now in force mode! Set flag to wake it up after first move. */
9609           setboardSpoiledMachineBlack = 1;
9610     }
9611
9612     if (cps->sendICS) {
9613       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9614       SendToProgram(buf, cps);
9615     }
9616     cps->maybeThinking = FALSE;
9617     cps->offeredDraw = 0;
9618     if (!appData.icsActive) {
9619         SendTimeControl(cps, movesPerSession, timeControl,
9620                         timeIncrement, appData.searchDepth,
9621                         searchTime);
9622     }
9623     if (appData.showThinking
9624         // [HGM] thinking: four options require thinking output to be sent
9625         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9626                                 ) {
9627         SendToProgram("post\n", cps);
9628     }
9629     SendToProgram("hard\n", cps);
9630     if (!appData.ponderNextMove) {
9631         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9632            it without being sure what state we are in first.  "hard"
9633            is not a toggle, so that one is OK.
9634          */
9635         SendToProgram("easy\n", cps);
9636     }
9637     if (cps->usePing) {
9638       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9639       SendToProgram(buf, cps);
9640     }
9641     cps->initDone = TRUE;
9642     ClearEngineOutputPane(cps == &second);
9643 }
9644
9645
9646 void
9647 StartChessProgram (ChessProgramState *cps)
9648 {
9649     char buf[MSG_SIZ];
9650     int err;
9651
9652     if (appData.noChessProgram) return;
9653     cps->initDone = FALSE;
9654
9655     if (strcmp(cps->host, "localhost") == 0) {
9656         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9657     } else if (*appData.remoteShell == NULLCHAR) {
9658         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9659     } else {
9660         if (*appData.remoteUser == NULLCHAR) {
9661           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9662                     cps->program);
9663         } else {
9664           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9665                     cps->host, appData.remoteUser, cps->program);
9666         }
9667         err = StartChildProcess(buf, "", &cps->pr);
9668     }
9669
9670     if (err != 0) {
9671       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9672         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9673         if(cps != &first) return;
9674         appData.noChessProgram = TRUE;
9675         ThawUI();
9676         SetNCPMode();
9677 //      DisplayFatalError(buf, err, 1);
9678 //      cps->pr = NoProc;
9679 //      cps->isr = NULL;
9680         return;
9681     }
9682
9683     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9684     if (cps->protocolVersion > 1) {
9685       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9686       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9687       cps->comboCnt = 0;  //                and values of combo boxes
9688       SendToProgram(buf, cps);
9689     } else {
9690       SendToProgram("xboard\n", cps);
9691     }
9692 }
9693
9694 void
9695 TwoMachinesEventIfReady P((void))
9696 {
9697   static int curMess = 0;
9698   if (first.lastPing != first.lastPong) {
9699     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9700     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9701     return;
9702   }
9703   if (second.lastPing != second.lastPong) {
9704     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9705     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9706     return;
9707   }
9708   DisplayMessage("", ""); curMess = 0;
9709   ThawUI();
9710   TwoMachinesEvent();
9711 }
9712
9713 char *
9714 MakeName (char *template)
9715 {
9716     time_t clock;
9717     struct tm *tm;
9718     static char buf[MSG_SIZ];
9719     char *p = buf;
9720     int i;
9721
9722     clock = time((time_t *)NULL);
9723     tm = localtime(&clock);
9724
9725     while(*p++ = *template++) if(p[-1] == '%') {
9726         switch(*template++) {
9727           case 0:   *p = 0; return buf;
9728           case 'Y': i = tm->tm_year+1900; break;
9729           case 'y': i = tm->tm_year-100; break;
9730           case 'M': i = tm->tm_mon+1; break;
9731           case 'd': i = tm->tm_mday; break;
9732           case 'h': i = tm->tm_hour; break;
9733           case 'm': i = tm->tm_min; break;
9734           case 's': i = tm->tm_sec; break;
9735           default:  i = 0;
9736         }
9737         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9738     }
9739     return buf;
9740 }
9741
9742 int
9743 CountPlayers (char *p)
9744 {
9745     int n = 0;
9746     while(p = strchr(p, '\n')) p++, n++; // count participants
9747     return n;
9748 }
9749
9750 FILE *
9751 WriteTourneyFile (char *results, FILE *f)
9752 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9753     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9754     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9755         // create a file with tournament description
9756         fprintf(f, "-participants {%s}\n", appData.participants);
9757         fprintf(f, "-seedBase %d\n", appData.seedBase);
9758         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9759         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9760         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9761         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9762         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9763         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9764         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9765         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9766         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9767         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9768         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9769         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9770         if(searchTime > 0)
9771                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9772         else {
9773                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9774                 fprintf(f, "-tc %s\n", appData.timeControl);
9775                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9776         }
9777         fprintf(f, "-results \"%s\"\n", results);
9778     }
9779     return f;
9780 }
9781
9782 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9783
9784 void
9785 Substitute (char *participants, int expunge)
9786 {
9787     int i, changed, changes=0, nPlayers=0;
9788     char *p, *q, *r, buf[MSG_SIZ];
9789     if(participants == NULL) return;
9790     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9791     r = p = participants; q = appData.participants;
9792     while(*p && *p == *q) {
9793         if(*p == '\n') r = p+1, nPlayers++;
9794         p++; q++;
9795     }
9796     if(*p) { // difference
9797         while(*p && *p++ != '\n');
9798         while(*q && *q++ != '\n');
9799       changed = nPlayers;
9800         changes = 1 + (strcmp(p, q) != 0);
9801     }
9802     if(changes == 1) { // a single engine mnemonic was changed
9803         q = r; while(*q) nPlayers += (*q++ == '\n');
9804         p = buf; while(*r && (*p = *r++) != '\n') p++;
9805         *p = NULLCHAR;
9806         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9807         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9808         if(mnemonic[i]) { // The substitute is valid
9809             FILE *f;
9810             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9811                 flock(fileno(f), LOCK_EX);
9812                 ParseArgsFromFile(f);
9813                 fseek(f, 0, SEEK_SET);
9814                 FREE(appData.participants); appData.participants = participants;
9815                 if(expunge) { // erase results of replaced engine
9816                     int len = strlen(appData.results), w, b, dummy;
9817                     for(i=0; i<len; i++) {
9818                         Pairing(i, nPlayers, &w, &b, &dummy);
9819                         if((w == changed || b == changed) && appData.results[i] == '*') {
9820                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9821                             fclose(f);
9822                             return;
9823                         }
9824                     }
9825                     for(i=0; i<len; i++) {
9826                         Pairing(i, nPlayers, &w, &b, &dummy);
9827                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9828                     }
9829                 }
9830                 WriteTourneyFile(appData.results, f);
9831                 fclose(f); // release lock
9832                 return;
9833             }
9834         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9835     }
9836     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9837     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9838     free(participants);
9839     return;
9840 }
9841
9842 int
9843 CreateTourney (char *name)
9844 {
9845         FILE *f;
9846         if(matchMode && strcmp(name, appData.tourneyFile)) {
9847              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9848         }
9849         if(name[0] == NULLCHAR) {
9850             if(appData.participants[0])
9851                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9852             return 0;
9853         }
9854         f = fopen(name, "r");
9855         if(f) { // file exists
9856             ASSIGN(appData.tourneyFile, name);
9857             ParseArgsFromFile(f); // parse it
9858         } else {
9859             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9860             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9861                 DisplayError(_("Not enough participants"), 0);
9862                 return 0;
9863             }
9864             ASSIGN(appData.tourneyFile, name);
9865             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9866             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9867         }
9868         fclose(f);
9869         appData.noChessProgram = FALSE;
9870         appData.clockMode = TRUE;
9871         SetGNUMode();
9872         return 1;
9873 }
9874
9875 int
9876 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9877 {
9878     char buf[MSG_SIZ], *p, *q;
9879     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9880     skip = !all && group[0]; // if group requested, we start in skip mode
9881     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9882         p = names; q = buf; header = 0;
9883         while(*p && *p != '\n') *q++ = *p++;
9884         *q = 0;
9885         if(*p == '\n') p++;
9886         if(buf[0] == '#') {
9887             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9888             depth++; // we must be entering a new group
9889             if(all) continue; // suppress printing group headers when complete list requested
9890             header = 1;
9891             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9892         }
9893         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9894         if(engineList[i]) free(engineList[i]);
9895         engineList[i] = strdup(buf);
9896         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9897         if(engineMnemonic[i]) free(engineMnemonic[i]);
9898         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9899             strcat(buf, " (");
9900             sscanf(q + 8, "%s", buf + strlen(buf));
9901             strcat(buf, ")");
9902         }
9903         engineMnemonic[i] = strdup(buf);
9904         i++;
9905     }
9906     engineList[i] = engineMnemonic[i] = NULL;
9907     return i;
9908 }
9909
9910 // following implemented as macro to avoid type limitations
9911 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9912
9913 void
9914 SwapEngines (int n)
9915 {   // swap settings for first engine and other engine (so far only some selected options)
9916     int h;
9917     char *p;
9918     if(n == 0) return;
9919     SWAP(directory, p)
9920     SWAP(chessProgram, p)
9921     SWAP(isUCI, h)
9922     SWAP(hasOwnBookUCI, h)
9923     SWAP(protocolVersion, h)
9924     SWAP(reuse, h)
9925     SWAP(scoreIsAbsolute, h)
9926     SWAP(timeOdds, h)
9927     SWAP(logo, p)
9928     SWAP(pgnName, p)
9929     SWAP(pvSAN, h)
9930     SWAP(engOptions, p)
9931 }
9932
9933 int
9934 SetPlayer (int player, char *p)
9935 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9936     int i;
9937     char buf[MSG_SIZ], *engineName;
9938     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9939     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9940     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9941     if(mnemonic[i]) {
9942         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9943         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9944         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9945         ParseArgsFromString(buf);
9946     }
9947     free(engineName);
9948     return i;
9949 }
9950
9951 char *recentEngines;
9952
9953 void
9954 RecentEngineEvent (int nr)
9955 {
9956     int n;
9957 //    SwapEngines(1); // bump first to second
9958 //    ReplaceEngine(&second, 1); // and load it there
9959     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9960     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9961     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9962         ReplaceEngine(&first, 0);
9963         FloatToFront(&appData.recentEngineList, command[n]);
9964     }
9965 }
9966
9967 int
9968 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9969 {   // determine players from game number
9970     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9971
9972     if(appData.tourneyType == 0) {
9973         roundsPerCycle = (nPlayers - 1) | 1;
9974         pairingsPerRound = nPlayers / 2;
9975     } else if(appData.tourneyType > 0) {
9976         roundsPerCycle = nPlayers - appData.tourneyType;
9977         pairingsPerRound = appData.tourneyType;
9978     }
9979     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9980     gamesPerCycle = gamesPerRound * roundsPerCycle;
9981     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9982     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9983     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9984     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9985     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9986     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9987
9988     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9989     if(appData.roundSync) *syncInterval = gamesPerRound;
9990
9991     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9992
9993     if(appData.tourneyType == 0) {
9994         if(curPairing == (nPlayers-1)/2 ) {
9995             *whitePlayer = curRound;
9996             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9997         } else {
9998             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9999             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10000             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10001             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10002         }
10003     } else if(appData.tourneyType > 1) {
10004         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10005         *whitePlayer = curRound + appData.tourneyType;
10006     } else if(appData.tourneyType > 0) {
10007         *whitePlayer = curPairing;
10008         *blackPlayer = curRound + appData.tourneyType;
10009     }
10010
10011     // take care of white/black alternation per round. 
10012     // For cycles and games this is already taken care of by default, derived from matchGame!
10013     return curRound & 1;
10014 }
10015
10016 int
10017 NextTourneyGame (int nr, int *swapColors)
10018 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10019     char *p, *q;
10020     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10021     FILE *tf;
10022     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10023     tf = fopen(appData.tourneyFile, "r");
10024     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10025     ParseArgsFromFile(tf); fclose(tf);
10026     InitTimeControls(); // TC might be altered from tourney file
10027
10028     nPlayers = CountPlayers(appData.participants); // count participants
10029     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10030     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10031
10032     if(syncInterval) {
10033         p = q = appData.results;
10034         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10035         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10036             DisplayMessage(_("Waiting for other game(s)"),"");
10037             waitingForGame = TRUE;
10038             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10039             return 0;
10040         }
10041         waitingForGame = FALSE;
10042     }
10043
10044     if(appData.tourneyType < 0) {
10045         if(nr>=0 && !pairingReceived) {
10046             char buf[1<<16];
10047             if(pairing.pr == NoProc) {
10048                 if(!appData.pairingEngine[0]) {
10049                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10050                     return 0;
10051                 }
10052                 StartChessProgram(&pairing); // starts the pairing engine
10053             }
10054             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10055             SendToProgram(buf, &pairing);
10056             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10057             SendToProgram(buf, &pairing);
10058             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10059         }
10060         pairingReceived = 0;                              // ... so we continue here 
10061         *swapColors = 0;
10062         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10063         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10064         matchGame = 1; roundNr = nr / syncInterval + 1;
10065     }
10066
10067     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10068
10069     // redefine engines, engine dir, etc.
10070     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10071     if(first.pr == NoProc) {
10072       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10073       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10074     }
10075     if(second.pr == NoProc) {
10076       SwapEngines(1);
10077       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10078       SwapEngines(1);         // and make that valid for second engine by swapping
10079       InitEngine(&second, 1);
10080     }
10081     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10082     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10083     return 1;
10084 }
10085
10086 void
10087 NextMatchGame ()
10088 {   // performs game initialization that does not invoke engines, and then tries to start the game
10089     int res, firstWhite, swapColors = 0;
10090     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10091     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
10092         char buf[MSG_SIZ];
10093         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10094         if(strcmp(buf, currentDebugFile)) { // name has changed
10095             FILE *f = fopen(buf, "w");
10096             if(f) { // if opening the new file failed, just keep using the old one
10097                 ASSIGN(currentDebugFile, buf);
10098                 fclose(debugFP);
10099                 debugFP = f;
10100             }
10101             if(appData.serverFileName) {
10102                 if(serverFP) fclose(serverFP);
10103                 serverFP = fopen(appData.serverFileName, "w");
10104                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10105                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10106             }
10107         }
10108     }
10109     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10110     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10111     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10112     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10113     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10114     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10115     Reset(FALSE, first.pr != NoProc);
10116     res = LoadGameOrPosition(matchGame); // setup game
10117     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10118     if(!res) return; // abort when bad game/pos file
10119     TwoMachinesEvent();
10120 }
10121
10122 void
10123 UserAdjudicationEvent (int result)
10124 {
10125     ChessMove gameResult = GameIsDrawn;
10126
10127     if( result > 0 ) {
10128         gameResult = WhiteWins;
10129     }
10130     else if( result < 0 ) {
10131         gameResult = BlackWins;
10132     }
10133
10134     if( gameMode == TwoMachinesPlay ) {
10135         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10136     }
10137 }
10138
10139
10140 // [HGM] save: calculate checksum of game to make games easily identifiable
10141 int
10142 StringCheckSum (char *s)
10143 {
10144         int i = 0;
10145         if(s==NULL) return 0;
10146         while(*s) i = i*259 + *s++;
10147         return i;
10148 }
10149
10150 int
10151 GameCheckSum ()
10152 {
10153         int i, sum=0;
10154         for(i=backwardMostMove; i<forwardMostMove; i++) {
10155                 sum += pvInfoList[i].depth;
10156                 sum += StringCheckSum(parseList[i]);
10157                 sum += StringCheckSum(commentList[i]);
10158                 sum *= 261;
10159         }
10160         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10161         return sum + StringCheckSum(commentList[i]);
10162 } // end of save patch
10163
10164 void
10165 GameEnds (ChessMove result, char *resultDetails, int whosays)
10166 {
10167     GameMode nextGameMode;
10168     int isIcsGame;
10169     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10170
10171     if(endingGame) return; /* [HGM] crash: forbid recursion */
10172     endingGame = 1;
10173     if(twoBoards) { // [HGM] dual: switch back to one board
10174         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10175         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10176     }
10177     if (appData.debugMode) {
10178       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10179               result, resultDetails ? resultDetails : "(null)", whosays);
10180     }
10181
10182     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10183
10184     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10185         /* If we are playing on ICS, the server decides when the
10186            game is over, but the engine can offer to draw, claim
10187            a draw, or resign.
10188          */
10189 #if ZIPPY
10190         if (appData.zippyPlay && first.initDone) {
10191             if (result == GameIsDrawn) {
10192                 /* In case draw still needs to be claimed */
10193                 SendToICS(ics_prefix);
10194                 SendToICS("draw\n");
10195             } else if (StrCaseStr(resultDetails, "resign")) {
10196                 SendToICS(ics_prefix);
10197                 SendToICS("resign\n");
10198             }
10199         }
10200 #endif
10201         endingGame = 0; /* [HGM] crash */
10202         return;
10203     }
10204
10205     /* If we're loading the game from a file, stop */
10206     if (whosays == GE_FILE) {
10207       (void) StopLoadGameTimer();
10208       gameFileFP = NULL;
10209     }
10210
10211     /* Cancel draw offers */
10212     first.offeredDraw = second.offeredDraw = 0;
10213
10214     /* If this is an ICS game, only ICS can really say it's done;
10215        if not, anyone can. */
10216     isIcsGame = (gameMode == IcsPlayingWhite ||
10217                  gameMode == IcsPlayingBlack ||
10218                  gameMode == IcsObserving    ||
10219                  gameMode == IcsExamining);
10220
10221     if (!isIcsGame || whosays == GE_ICS) {
10222         /* OK -- not an ICS game, or ICS said it was done */
10223         StopClocks();
10224         if (!isIcsGame && !appData.noChessProgram)
10225           SetUserThinkingEnables();
10226
10227         /* [HGM] if a machine claims the game end we verify this claim */
10228         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10229             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10230                 char claimer;
10231                 ChessMove trueResult = (ChessMove) -1;
10232
10233                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10234                                             first.twoMachinesColor[0] :
10235                                             second.twoMachinesColor[0] ;
10236
10237                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10238                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10239                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10240                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10241                 } else
10242                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10243                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10244                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10245                 } else
10246                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10247                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10248                 }
10249
10250                 // now verify win claims, but not in drop games, as we don't understand those yet
10251                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10252                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10253                     (result == WhiteWins && claimer == 'w' ||
10254                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10255                       if (appData.debugMode) {
10256                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10257                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10258                       }
10259                       if(result != trueResult) {
10260                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10261                               result = claimer == 'w' ? BlackWins : WhiteWins;
10262                               resultDetails = buf;
10263                       }
10264                 } else
10265                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10266                     && (forwardMostMove <= backwardMostMove ||
10267                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10268                         (claimer=='b')==(forwardMostMove&1))
10269                                                                                   ) {
10270                       /* [HGM] verify: draws that were not flagged are false claims */
10271                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10272                       result = claimer == 'w' ? BlackWins : WhiteWins;
10273                       resultDetails = buf;
10274                 }
10275                 /* (Claiming a loss is accepted no questions asked!) */
10276             }
10277             /* [HGM] bare: don't allow bare King to win */
10278             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10279                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10280                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10281                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10282                && result != GameIsDrawn)
10283             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10284                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10285                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10286                         if(p >= 0 && p <= (int)WhiteKing) k++;
10287                 }
10288                 if (appData.debugMode) {
10289                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10290                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10291                 }
10292                 if(k <= 1) {
10293                         result = GameIsDrawn;
10294                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10295                         resultDetails = buf;
10296                 }
10297             }
10298         }
10299
10300
10301         if(serverMoves != NULL && !loadFlag) { char c = '=';
10302             if(result==WhiteWins) c = '+';
10303             if(result==BlackWins) c = '-';
10304             if(resultDetails != NULL)
10305                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10306         }
10307         if (resultDetails != NULL) {
10308             gameInfo.result = result;
10309             gameInfo.resultDetails = StrSave(resultDetails);
10310
10311             /* display last move only if game was not loaded from file */
10312             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10313                 DisplayMove(currentMove - 1);
10314
10315             if (forwardMostMove != 0) {
10316                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10317                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10318                                                                 ) {
10319                     if (*appData.saveGameFile != NULLCHAR) {
10320                         SaveGameToFile(appData.saveGameFile, TRUE);
10321                     } else if (appData.autoSaveGames) {
10322                         AutoSaveGame();
10323                     }
10324                     if (*appData.savePositionFile != NULLCHAR) {
10325                         SavePositionToFile(appData.savePositionFile);
10326                     }
10327                 }
10328             }
10329
10330             /* Tell program how game ended in case it is learning */
10331             /* [HGM] Moved this to after saving the PGN, just in case */
10332             /* engine died and we got here through time loss. In that */
10333             /* case we will get a fatal error writing the pipe, which */
10334             /* would otherwise lose us the PGN.                       */
10335             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10336             /* output during GameEnds should never be fatal anymore   */
10337             if (gameMode == MachinePlaysWhite ||
10338                 gameMode == MachinePlaysBlack ||
10339                 gameMode == TwoMachinesPlay ||
10340                 gameMode == IcsPlayingWhite ||
10341                 gameMode == IcsPlayingBlack ||
10342                 gameMode == BeginningOfGame) {
10343                 char buf[MSG_SIZ];
10344                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10345                         resultDetails);
10346                 if (first.pr != NoProc) {
10347                     SendToProgram(buf, &first);
10348                 }
10349                 if (second.pr != NoProc &&
10350                     gameMode == TwoMachinesPlay) {
10351                     SendToProgram(buf, &second);
10352                 }
10353             }
10354         }
10355
10356         if (appData.icsActive) {
10357             if (appData.quietPlay &&
10358                 (gameMode == IcsPlayingWhite ||
10359                  gameMode == IcsPlayingBlack)) {
10360                 SendToICS(ics_prefix);
10361                 SendToICS("set shout 1\n");
10362             }
10363             nextGameMode = IcsIdle;
10364             ics_user_moved = FALSE;
10365             /* clean up premove.  It's ugly when the game has ended and the
10366              * premove highlights are still on the board.
10367              */
10368             if (gotPremove) {
10369               gotPremove = FALSE;
10370               ClearPremoveHighlights();
10371               DrawPosition(FALSE, boards[currentMove]);
10372             }
10373             if (whosays == GE_ICS) {
10374                 switch (result) {
10375                 case WhiteWins:
10376                     if (gameMode == IcsPlayingWhite)
10377                         PlayIcsWinSound();
10378                     else if(gameMode == IcsPlayingBlack)
10379                         PlayIcsLossSound();
10380                     break;
10381                 case BlackWins:
10382                     if (gameMode == IcsPlayingBlack)
10383                         PlayIcsWinSound();
10384                     else if(gameMode == IcsPlayingWhite)
10385                         PlayIcsLossSound();
10386                     break;
10387                 case GameIsDrawn:
10388                     PlayIcsDrawSound();
10389                     break;
10390                 default:
10391                     PlayIcsUnfinishedSound();
10392                 }
10393             }
10394         } else if (gameMode == EditGame ||
10395                    gameMode == PlayFromGameFile ||
10396                    gameMode == AnalyzeMode ||
10397                    gameMode == AnalyzeFile) {
10398             nextGameMode = gameMode;
10399         } else {
10400             nextGameMode = EndOfGame;
10401         }
10402         pausing = FALSE;
10403         ModeHighlight();
10404     } else {
10405         nextGameMode = gameMode;
10406     }
10407
10408     if (appData.noChessProgram) {
10409         gameMode = nextGameMode;
10410         ModeHighlight();
10411         endingGame = 0; /* [HGM] crash */
10412         return;
10413     }
10414
10415     if (first.reuse) {
10416         /* Put first chess program into idle state */
10417         if (first.pr != NoProc &&
10418             (gameMode == MachinePlaysWhite ||
10419              gameMode == MachinePlaysBlack ||
10420              gameMode == TwoMachinesPlay ||
10421              gameMode == IcsPlayingWhite ||
10422              gameMode == IcsPlayingBlack ||
10423              gameMode == BeginningOfGame)) {
10424             SendToProgram("force\n", &first);
10425             if (first.usePing) {
10426               char buf[MSG_SIZ];
10427               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10428               SendToProgram(buf, &first);
10429             }
10430         }
10431     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10432         /* Kill off first chess program */
10433         if (first.isr != NULL)
10434           RemoveInputSource(first.isr);
10435         first.isr = NULL;
10436
10437         if (first.pr != NoProc) {
10438             ExitAnalyzeMode();
10439             DoSleep( appData.delayBeforeQuit );
10440             SendToProgram("quit\n", &first);
10441             DoSleep( appData.delayAfterQuit );
10442             DestroyChildProcess(first.pr, first.useSigterm);
10443         }
10444         first.pr = NoProc;
10445     }
10446     if (second.reuse) {
10447         /* Put second chess program into idle state */
10448         if (second.pr != NoProc &&
10449             gameMode == TwoMachinesPlay) {
10450             SendToProgram("force\n", &second);
10451             if (second.usePing) {
10452               char buf[MSG_SIZ];
10453               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10454               SendToProgram(buf, &second);
10455             }
10456         }
10457     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10458         /* Kill off second chess program */
10459         if (second.isr != NULL)
10460           RemoveInputSource(second.isr);
10461         second.isr = NULL;
10462
10463         if (second.pr != NoProc) {
10464             DoSleep( appData.delayBeforeQuit );
10465             SendToProgram("quit\n", &second);
10466             DoSleep( appData.delayAfterQuit );
10467             DestroyChildProcess(second.pr, second.useSigterm);
10468         }
10469         second.pr = NoProc;
10470     }
10471
10472     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10473         char resChar = '=';
10474         switch (result) {
10475         case WhiteWins:
10476           resChar = '+';
10477           if (first.twoMachinesColor[0] == 'w') {
10478             first.matchWins++;
10479           } else {
10480             second.matchWins++;
10481           }
10482           break;
10483         case BlackWins:
10484           resChar = '-';
10485           if (first.twoMachinesColor[0] == 'b') {
10486             first.matchWins++;
10487           } else {
10488             second.matchWins++;
10489           }
10490           break;
10491         case GameUnfinished:
10492           resChar = ' ';
10493         default:
10494           break;
10495         }
10496
10497         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10498         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10499             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10500             ReserveGame(nextGame, resChar); // sets nextGame
10501             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10502             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10503         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10504
10505         if (nextGame <= appData.matchGames && !abortMatch) {
10506             gameMode = nextGameMode;
10507             matchGame = nextGame; // this will be overruled in tourney mode!
10508             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10509             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10510             endingGame = 0; /* [HGM] crash */
10511             return;
10512         } else {
10513             gameMode = nextGameMode;
10514             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10515                      first.tidy, second.tidy,
10516                      first.matchWins, second.matchWins,
10517                      appData.matchGames - (first.matchWins + second.matchWins));
10518             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10519             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10520             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10521             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10522                 first.twoMachinesColor = "black\n";
10523                 second.twoMachinesColor = "white\n";
10524             } else {
10525                 first.twoMachinesColor = "white\n";
10526                 second.twoMachinesColor = "black\n";
10527             }
10528         }
10529     }
10530     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10531         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10532       ExitAnalyzeMode();
10533     gameMode = nextGameMode;
10534     ModeHighlight();
10535     endingGame = 0;  /* [HGM] crash */
10536     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10537         if(matchMode == TRUE) { // match through command line: exit with or without popup
10538             if(ranking) {
10539                 ToNrEvent(forwardMostMove);
10540                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10541                 else ExitEvent(0);
10542             } else DisplayFatalError(buf, 0, 0);
10543         } else { // match through menu; just stop, with or without popup
10544             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10545             ModeHighlight();
10546             if(ranking){
10547                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10548             } else DisplayNote(buf);
10549       }
10550       if(ranking) free(ranking);
10551     }
10552 }
10553
10554 /* Assumes program was just initialized (initString sent).
10555    Leaves program in force mode. */
10556 void
10557 FeedMovesToProgram (ChessProgramState *cps, int upto)
10558 {
10559     int i;
10560
10561     if (appData.debugMode)
10562       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10563               startedFromSetupPosition ? "position and " : "",
10564               backwardMostMove, upto, cps->which);
10565     if(currentlyInitializedVariant != gameInfo.variant) {
10566       char buf[MSG_SIZ];
10567         // [HGM] variantswitch: make engine aware of new variant
10568         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10569                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10570         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10571         SendToProgram(buf, cps);
10572         currentlyInitializedVariant = gameInfo.variant;
10573     }
10574     SendToProgram("force\n", cps);
10575     if (startedFromSetupPosition) {
10576         SendBoard(cps, backwardMostMove);
10577     if (appData.debugMode) {
10578         fprintf(debugFP, "feedMoves\n");
10579     }
10580     }
10581     for (i = backwardMostMove; i < upto; i++) {
10582         SendMoveToProgram(i, cps);
10583     }
10584 }
10585
10586
10587 int
10588 ResurrectChessProgram ()
10589 {
10590      /* The chess program may have exited.
10591         If so, restart it and feed it all the moves made so far. */
10592     static int doInit = 0;
10593
10594     if (appData.noChessProgram) return 1;
10595
10596     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10597         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10598         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10599         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10600     } else {
10601         if (first.pr != NoProc) return 1;
10602         StartChessProgram(&first);
10603     }
10604     InitChessProgram(&first, FALSE);
10605     FeedMovesToProgram(&first, currentMove);
10606
10607     if (!first.sendTime) {
10608         /* can't tell gnuchess what its clock should read,
10609            so we bow to its notion. */
10610         ResetClocks();
10611         timeRemaining[0][currentMove] = whiteTimeRemaining;
10612         timeRemaining[1][currentMove] = blackTimeRemaining;
10613     }
10614
10615     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10616                 appData.icsEngineAnalyze) && first.analysisSupport) {
10617       SendToProgram("analyze\n", &first);
10618       first.analyzing = TRUE;
10619     }
10620     return 1;
10621 }
10622
10623 /*
10624  * Button procedures
10625  */
10626 void
10627 Reset (int redraw, int init)
10628 {
10629     int i;
10630
10631     if (appData.debugMode) {
10632         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10633                 redraw, init, gameMode);
10634     }
10635     CleanupTail(); // [HGM] vari: delete any stored variations
10636     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10637     pausing = pauseExamInvalid = FALSE;
10638     startedFromSetupPosition = blackPlaysFirst = FALSE;
10639     firstMove = TRUE;
10640     whiteFlag = blackFlag = FALSE;
10641     userOfferedDraw = FALSE;
10642     hintRequested = bookRequested = FALSE;
10643     first.maybeThinking = FALSE;
10644     second.maybeThinking = FALSE;
10645     first.bookSuspend = FALSE; // [HGM] book
10646     second.bookSuspend = FALSE;
10647     thinkOutput[0] = NULLCHAR;
10648     lastHint[0] = NULLCHAR;
10649     ClearGameInfo(&gameInfo);
10650     gameInfo.variant = StringToVariant(appData.variant);
10651     ics_user_moved = ics_clock_paused = FALSE;
10652     ics_getting_history = H_FALSE;
10653     ics_gamenum = -1;
10654     white_holding[0] = black_holding[0] = NULLCHAR;
10655     ClearProgramStats();
10656     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10657
10658     ResetFrontEnd();
10659     ClearHighlights();
10660     flipView = appData.flipView;
10661     ClearPremoveHighlights();
10662     gotPremove = FALSE;
10663     alarmSounded = FALSE;
10664
10665     GameEnds(EndOfFile, NULL, GE_PLAYER);
10666     if(appData.serverMovesName != NULL) {
10667         /* [HGM] prepare to make moves file for broadcasting */
10668         clock_t t = clock();
10669         if(serverMoves != NULL) fclose(serverMoves);
10670         serverMoves = fopen(appData.serverMovesName, "r");
10671         if(serverMoves != NULL) {
10672             fclose(serverMoves);
10673             /* delay 15 sec before overwriting, so all clients can see end */
10674             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10675         }
10676         serverMoves = fopen(appData.serverMovesName, "w");
10677     }
10678
10679     ExitAnalyzeMode();
10680     gameMode = BeginningOfGame;
10681     ModeHighlight();
10682     if(appData.icsActive) gameInfo.variant = VariantNormal;
10683     currentMove = forwardMostMove = backwardMostMove = 0;
10684     MarkTargetSquares(1);
10685     InitPosition(redraw);
10686     for (i = 0; i < MAX_MOVES; i++) {
10687         if (commentList[i] != NULL) {
10688             free(commentList[i]);
10689             commentList[i] = NULL;
10690         }
10691     }
10692     ResetClocks();
10693     timeRemaining[0][0] = whiteTimeRemaining;
10694     timeRemaining[1][0] = blackTimeRemaining;
10695
10696     if (first.pr == NoProc) {
10697         StartChessProgram(&first);
10698     }
10699     if (init) {
10700             InitChessProgram(&first, startedFromSetupPosition);
10701     }
10702     DisplayTitle("");
10703     DisplayMessage("", "");
10704     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10705     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10706 }
10707
10708 void
10709 AutoPlayGameLoop ()
10710 {
10711     for (;;) {
10712         if (!AutoPlayOneMove())
10713           return;
10714         if (matchMode || appData.timeDelay == 0)
10715           continue;
10716         if (appData.timeDelay < 0)
10717           return;
10718         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10719         break;
10720     }
10721 }
10722
10723
10724 int
10725 AutoPlayOneMove ()
10726 {
10727     int fromX, fromY, toX, toY;
10728
10729     if (appData.debugMode) {
10730       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10731     }
10732
10733     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10734       return FALSE;
10735
10736     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10737       pvInfoList[currentMove].depth = programStats.depth;
10738       pvInfoList[currentMove].score = programStats.score;
10739       pvInfoList[currentMove].time  = 0;
10740       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10741     }
10742
10743     if (currentMove >= forwardMostMove) {
10744       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10745 //      gameMode = EndOfGame;
10746 //      ModeHighlight();
10747
10748       /* [AS] Clear current move marker at the end of a game */
10749       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10750
10751       return FALSE;
10752     }
10753
10754     toX = moveList[currentMove][2] - AAA;
10755     toY = moveList[currentMove][3] - ONE;
10756
10757     if (moveList[currentMove][1] == '@') {
10758         if (appData.highlightLastMove) {
10759             SetHighlights(-1, -1, toX, toY);
10760         }
10761     } else {
10762         fromX = moveList[currentMove][0] - AAA;
10763         fromY = moveList[currentMove][1] - ONE;
10764
10765         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10766
10767         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10768
10769         if (appData.highlightLastMove) {
10770             SetHighlights(fromX, fromY, toX, toY);
10771         }
10772     }
10773     DisplayMove(currentMove);
10774     SendMoveToProgram(currentMove++, &first);
10775     DisplayBothClocks();
10776     DrawPosition(FALSE, boards[currentMove]);
10777     // [HGM] PV info: always display, routine tests if empty
10778     DisplayComment(currentMove - 1, commentList[currentMove]);
10779     return TRUE;
10780 }
10781
10782
10783 int
10784 LoadGameOneMove (ChessMove readAhead)
10785 {
10786     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10787     char promoChar = NULLCHAR;
10788     ChessMove moveType;
10789     char move[MSG_SIZ];
10790     char *p, *q;
10791
10792     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10793         gameMode != AnalyzeMode && gameMode != Training) {
10794         gameFileFP = NULL;
10795         return FALSE;
10796     }
10797
10798     yyboardindex = forwardMostMove;
10799     if (readAhead != EndOfFile) {
10800       moveType = readAhead;
10801     } else {
10802       if (gameFileFP == NULL)
10803           return FALSE;
10804       moveType = (ChessMove) Myylex();
10805     }
10806
10807     done = FALSE;
10808     switch (moveType) {
10809       case Comment:
10810         if (appData.debugMode)
10811           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10812         p = yy_text;
10813
10814         /* append the comment but don't display it */
10815         AppendComment(currentMove, p, FALSE);
10816         return TRUE;
10817
10818       case WhiteCapturesEnPassant:
10819       case BlackCapturesEnPassant:
10820       case WhitePromotion:
10821       case BlackPromotion:
10822       case WhiteNonPromotion:
10823       case BlackNonPromotion:
10824       case NormalMove:
10825       case WhiteKingSideCastle:
10826       case WhiteQueenSideCastle:
10827       case BlackKingSideCastle:
10828       case BlackQueenSideCastle:
10829       case WhiteKingSideCastleWild:
10830       case WhiteQueenSideCastleWild:
10831       case BlackKingSideCastleWild:
10832       case BlackQueenSideCastleWild:
10833       /* PUSH Fabien */
10834       case WhiteHSideCastleFR:
10835       case WhiteASideCastleFR:
10836       case BlackHSideCastleFR:
10837       case BlackASideCastleFR:
10838       /* POP Fabien */
10839         if (appData.debugMode)
10840           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10841         fromX = currentMoveString[0] - AAA;
10842         fromY = currentMoveString[1] - ONE;
10843         toX = currentMoveString[2] - AAA;
10844         toY = currentMoveString[3] - ONE;
10845         promoChar = currentMoveString[4];
10846         break;
10847
10848       case WhiteDrop:
10849       case BlackDrop:
10850         if (appData.debugMode)
10851           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10852         fromX = moveType == WhiteDrop ?
10853           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10854         (int) CharToPiece(ToLower(currentMoveString[0]));
10855         fromY = DROP_RANK;
10856         toX = currentMoveString[2] - AAA;
10857         toY = currentMoveString[3] - ONE;
10858         break;
10859
10860       case WhiteWins:
10861       case BlackWins:
10862       case GameIsDrawn:
10863       case GameUnfinished:
10864         if (appData.debugMode)
10865           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10866         p = strchr(yy_text, '{');
10867         if (p == NULL) p = strchr(yy_text, '(');
10868         if (p == NULL) {
10869             p = yy_text;
10870             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10871         } else {
10872             q = strchr(p, *p == '{' ? '}' : ')');
10873             if (q != NULL) *q = NULLCHAR;
10874             p++;
10875         }
10876         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10877         GameEnds(moveType, p, GE_FILE);
10878         done = TRUE;
10879         if (cmailMsgLoaded) {
10880             ClearHighlights();
10881             flipView = WhiteOnMove(currentMove);
10882             if (moveType == GameUnfinished) flipView = !flipView;
10883             if (appData.debugMode)
10884               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10885         }
10886         break;
10887
10888       case EndOfFile:
10889         if (appData.debugMode)
10890           fprintf(debugFP, "Parser hit end of file\n");
10891         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10892           case MT_NONE:
10893           case MT_CHECK:
10894             break;
10895           case MT_CHECKMATE:
10896           case MT_STAINMATE:
10897             if (WhiteOnMove(currentMove)) {
10898                 GameEnds(BlackWins, "Black mates", GE_FILE);
10899             } else {
10900                 GameEnds(WhiteWins, "White mates", GE_FILE);
10901             }
10902             break;
10903           case MT_STALEMATE:
10904             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10905             break;
10906         }
10907         done = TRUE;
10908         break;
10909
10910       case MoveNumberOne:
10911         if (lastLoadGameStart == GNUChessGame) {
10912             /* GNUChessGames have numbers, but they aren't move numbers */
10913             if (appData.debugMode)
10914               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10915                       yy_text, (int) moveType);
10916             return LoadGameOneMove(EndOfFile); /* tail recursion */
10917         }
10918         /* else fall thru */
10919
10920       case XBoardGame:
10921       case GNUChessGame:
10922       case PGNTag:
10923         /* Reached start of next game in file */
10924         if (appData.debugMode)
10925           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10926         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10927           case MT_NONE:
10928           case MT_CHECK:
10929             break;
10930           case MT_CHECKMATE:
10931           case MT_STAINMATE:
10932             if (WhiteOnMove(currentMove)) {
10933                 GameEnds(BlackWins, "Black mates", GE_FILE);
10934             } else {
10935                 GameEnds(WhiteWins, "White mates", GE_FILE);
10936             }
10937             break;
10938           case MT_STALEMATE:
10939             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10940             break;
10941         }
10942         done = TRUE;
10943         break;
10944
10945       case PositionDiagram:     /* should not happen; ignore */
10946       case ElapsedTime:         /* ignore */
10947       case NAG:                 /* ignore */
10948         if (appData.debugMode)
10949           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10950                   yy_text, (int) moveType);
10951         return LoadGameOneMove(EndOfFile); /* tail recursion */
10952
10953       case IllegalMove:
10954         if (appData.testLegality) {
10955             if (appData.debugMode)
10956               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10957             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10958                     (forwardMostMove / 2) + 1,
10959                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10960             DisplayError(move, 0);
10961             done = TRUE;
10962         } else {
10963             if (appData.debugMode)
10964               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10965                       yy_text, currentMoveString);
10966             fromX = currentMoveString[0] - AAA;
10967             fromY = currentMoveString[1] - ONE;
10968             toX = currentMoveString[2] - AAA;
10969             toY = currentMoveString[3] - ONE;
10970             promoChar = currentMoveString[4];
10971         }
10972         break;
10973
10974       case AmbiguousMove:
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10977         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10978                 (forwardMostMove / 2) + 1,
10979                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10980         DisplayError(move, 0);
10981         done = TRUE;
10982         break;
10983
10984       default:
10985       case ImpossibleMove:
10986         if (appData.debugMode)
10987           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10988         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10989                 (forwardMostMove / 2) + 1,
10990                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10991         DisplayError(move, 0);
10992         done = TRUE;
10993         break;
10994     }
10995
10996     if (done) {
10997         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10998             DrawPosition(FALSE, boards[currentMove]);
10999             DisplayBothClocks();
11000             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11001               DisplayComment(currentMove - 1, commentList[currentMove]);
11002         }
11003         (void) StopLoadGameTimer();
11004         gameFileFP = NULL;
11005         cmailOldMove = forwardMostMove;
11006         return FALSE;
11007     } else {
11008         /* currentMoveString is set as a side-effect of yylex */
11009
11010         thinkOutput[0] = NULLCHAR;
11011         MakeMove(fromX, fromY, toX, toY, promoChar);
11012         currentMove = forwardMostMove;
11013         return TRUE;
11014     }
11015 }
11016
11017 /* Load the nth game from the given file */
11018 int
11019 LoadGameFromFile (char *filename, int n, char *title, int useList)
11020 {
11021     FILE *f;
11022     char buf[MSG_SIZ];
11023
11024     if (strcmp(filename, "-") == 0) {
11025         f = stdin;
11026         title = "stdin";
11027     } else {
11028         f = fopen(filename, "rb");
11029         if (f == NULL) {
11030           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11031             DisplayError(buf, errno);
11032             return FALSE;
11033         }
11034     }
11035     if (fseek(f, 0, 0) == -1) {
11036         /* f is not seekable; probably a pipe */
11037         useList = FALSE;
11038     }
11039     if (useList && n == 0) {
11040         int error = GameListBuild(f);
11041         if (error) {
11042             DisplayError(_("Cannot build game list"), error);
11043         } else if (!ListEmpty(&gameList) &&
11044                    ((ListGame *) gameList.tailPred)->number > 1) {
11045             GameListPopUp(f, title);
11046             return TRUE;
11047         }
11048         GameListDestroy();
11049         n = 1;
11050     }
11051     if (n == 0) n = 1;
11052     return LoadGame(f, n, title, FALSE);
11053 }
11054
11055
11056 void
11057 MakeRegisteredMove ()
11058 {
11059     int fromX, fromY, toX, toY;
11060     char promoChar;
11061     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11062         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11063           case CMAIL_MOVE:
11064           case CMAIL_DRAW:
11065             if (appData.debugMode)
11066               fprintf(debugFP, "Restoring %s for game %d\n",
11067                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11068
11069             thinkOutput[0] = NULLCHAR;
11070             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11071             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11072             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11073             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11074             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11075             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11076             MakeMove(fromX, fromY, toX, toY, promoChar);
11077             ShowMove(fromX, fromY, toX, toY);
11078
11079             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11080               case MT_NONE:
11081               case MT_CHECK:
11082                 break;
11083
11084               case MT_CHECKMATE:
11085               case MT_STAINMATE:
11086                 if (WhiteOnMove(currentMove)) {
11087                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11088                 } else {
11089                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11090                 }
11091                 break;
11092
11093               case MT_STALEMATE:
11094                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11095                 break;
11096             }
11097
11098             break;
11099
11100           case CMAIL_RESIGN:
11101             if (WhiteOnMove(currentMove)) {
11102                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11103             } else {
11104                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11105             }
11106             break;
11107
11108           case CMAIL_ACCEPT:
11109             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11110             break;
11111
11112           default:
11113             break;
11114         }
11115     }
11116
11117     return;
11118 }
11119
11120 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11121 int
11122 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11123 {
11124     int retVal;
11125
11126     if (gameNumber > nCmailGames) {
11127         DisplayError(_("No more games in this message"), 0);
11128         return FALSE;
11129     }
11130     if (f == lastLoadGameFP) {
11131         int offset = gameNumber - lastLoadGameNumber;
11132         if (offset == 0) {
11133             cmailMsg[0] = NULLCHAR;
11134             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11135                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11136                 nCmailMovesRegistered--;
11137             }
11138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11139             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11140                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11141             }
11142         } else {
11143             if (! RegisterMove()) return FALSE;
11144         }
11145     }
11146
11147     retVal = LoadGame(f, gameNumber, title, useList);
11148
11149     /* Make move registered during previous look at this game, if any */
11150     MakeRegisteredMove();
11151
11152     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11153         commentList[currentMove]
11154           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11155         DisplayComment(currentMove - 1, commentList[currentMove]);
11156     }
11157
11158     return retVal;
11159 }
11160
11161 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11162 int
11163 ReloadGame (int offset)
11164 {
11165     int gameNumber = lastLoadGameNumber + offset;
11166     if (lastLoadGameFP == NULL) {
11167         DisplayError(_("No game has been loaded yet"), 0);
11168         return FALSE;
11169     }
11170     if (gameNumber <= 0) {
11171         DisplayError(_("Can't back up any further"), 0);
11172         return FALSE;
11173     }
11174     if (cmailMsgLoaded) {
11175         return CmailLoadGame(lastLoadGameFP, gameNumber,
11176                              lastLoadGameTitle, lastLoadGameUseList);
11177     } else {
11178         return LoadGame(lastLoadGameFP, gameNumber,
11179                         lastLoadGameTitle, lastLoadGameUseList);
11180     }
11181 }
11182
11183 int keys[EmptySquare+1];
11184
11185 int
11186 PositionMatches (Board b1, Board b2)
11187 {
11188     int r, f, sum=0;
11189     switch(appData.searchMode) {
11190         case 1: return CompareWithRights(b1, b2);
11191         case 2:
11192             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11193                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11194             }
11195             return TRUE;
11196         case 3:
11197             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11198               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11199                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11200             }
11201             return sum==0;
11202         case 4:
11203             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11204                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11205             }
11206             return sum==0;
11207     }
11208     return TRUE;
11209 }
11210
11211 #define Q_PROMO  4
11212 #define Q_EP     3
11213 #define Q_BCASTL 2
11214 #define Q_WCASTL 1
11215
11216 int pieceList[256], quickBoard[256];
11217 ChessSquare pieceType[256] = { EmptySquare };
11218 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11219 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11220 int soughtTotal, turn;
11221 Boolean epOK, flipSearch;
11222
11223 typedef struct {
11224     unsigned char piece, to;
11225 } Move;
11226
11227 #define DSIZE (250000)
11228
11229 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11230 Move *moveDatabase = initialSpace;
11231 unsigned int movePtr, dataSize = DSIZE;
11232
11233 int
11234 MakePieceList (Board board, int *counts)
11235 {
11236     int r, f, n=Q_PROMO, total=0;
11237     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11238     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11239         int sq = f + (r<<4);
11240         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11241             quickBoard[sq] = ++n;
11242             pieceList[n] = sq;
11243             pieceType[n] = board[r][f];
11244             counts[board[r][f]]++;
11245             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11246             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11247             total++;
11248         }
11249     }
11250     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11251     return total;
11252 }
11253
11254 void
11255 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11256 {
11257     int sq = fromX + (fromY<<4);
11258     int piece = quickBoard[sq];
11259     quickBoard[sq] = 0;
11260     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11261     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11262         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11263         moveDatabase[movePtr++].piece = Q_WCASTL;
11264         quickBoard[sq] = piece;
11265         piece = quickBoard[from]; quickBoard[from] = 0;
11266         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11267     } else
11268     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11269         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11270         moveDatabase[movePtr++].piece = Q_BCASTL;
11271         quickBoard[sq] = piece;
11272         piece = quickBoard[from]; quickBoard[from] = 0;
11273         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11274     } else
11275     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11276         quickBoard[(fromY<<4)+toX] = 0;
11277         moveDatabase[movePtr].piece = Q_EP;
11278         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11279         moveDatabase[movePtr].to = sq;
11280     } else
11281     if(promoPiece != pieceType[piece]) {
11282         moveDatabase[movePtr++].piece = Q_PROMO;
11283         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11284     }
11285     moveDatabase[movePtr].piece = piece;
11286     quickBoard[sq] = piece;
11287     movePtr++;
11288 }
11289
11290 int
11291 PackGame (Board board)
11292 {
11293     Move *newSpace = NULL;
11294     moveDatabase[movePtr].piece = 0; // terminate previous game
11295     if(movePtr > dataSize) {
11296         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11297         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11298         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11299         if(newSpace) {
11300             int i;
11301             Move *p = moveDatabase, *q = newSpace;
11302             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11303             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11304             moveDatabase = newSpace;
11305         } else { // calloc failed, we must be out of memory. Too bad...
11306             dataSize = 0; // prevent calloc events for all subsequent games
11307             return 0;     // and signal this one isn't cached
11308         }
11309     }
11310     movePtr++;
11311     MakePieceList(board, counts);
11312     return movePtr;
11313 }
11314
11315 int
11316 QuickCompare (Board board, int *minCounts, int *maxCounts)
11317 {   // compare according to search mode
11318     int r, f;
11319     switch(appData.searchMode)
11320     {
11321       case 1: // exact position match
11322         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11323         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11324             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11325         }
11326         break;
11327       case 2: // can have extra material on empty squares
11328         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11329             if(board[r][f] == EmptySquare) continue;
11330             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11331         }
11332         break;
11333       case 3: // material with exact Pawn structure
11334         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11335             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11336             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11337         } // fall through to material comparison
11338       case 4: // exact material
11339         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11340         break;
11341       case 6: // material range with given imbalance
11342         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11343         // fall through to range comparison
11344       case 5: // material range
11345         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11346     }
11347     return TRUE;
11348 }
11349
11350 int
11351 QuickScan (Board board, Move *move)
11352 {   // reconstruct game,and compare all positions in it
11353     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11354     do {
11355         int piece = move->piece;
11356         int to = move->to, from = pieceList[piece];
11357         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11358           if(!piece) return -1;
11359           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11360             piece = (++move)->piece;
11361             from = pieceList[piece];
11362             counts[pieceType[piece]]--;
11363             pieceType[piece] = (ChessSquare) move->to;
11364             counts[move->to]++;
11365           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11366             counts[pieceType[quickBoard[to]]]--;
11367             quickBoard[to] = 0; total--;
11368             move++;
11369             continue;
11370           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11371             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11372             from  = pieceList[piece]; // so this must be King
11373             quickBoard[from] = 0;
11374             quickBoard[to] = piece;
11375             pieceList[piece] = to;
11376             move++;
11377             continue;
11378           }
11379         }
11380         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11381         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11382         quickBoard[from] = 0;
11383         quickBoard[to] = piece;
11384         pieceList[piece] = to;
11385         cnt++; turn ^= 3;
11386         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11387            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11388            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11389                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11390           ) {
11391             static int lastCounts[EmptySquare+1];
11392             int i;
11393             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11394             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11395         } else stretch = 0;
11396         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11397         move++;
11398     } while(1);
11399 }
11400
11401 void
11402 InitSearch ()
11403 {
11404     int r, f;
11405     flipSearch = FALSE;
11406     CopyBoard(soughtBoard, boards[currentMove]);
11407     soughtTotal = MakePieceList(soughtBoard, maxSought);
11408     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11409     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11410     CopyBoard(reverseBoard, boards[currentMove]);
11411     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11412         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11413         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11414         reverseBoard[r][f] = piece;
11415     }
11416     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11417     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11418     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11419                  || (boards[currentMove][CASTLING][2] == NoRights || 
11420                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11421                  && (boards[currentMove][CASTLING][5] == NoRights || 
11422                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11423       ) {
11424         flipSearch = TRUE;
11425         CopyBoard(flipBoard, soughtBoard);
11426         CopyBoard(rotateBoard, reverseBoard);
11427         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11428             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11429             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11430         }
11431     }
11432     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11433     if(appData.searchMode >= 5) {
11434         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11435         MakePieceList(soughtBoard, minSought);
11436         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11437     }
11438     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11439         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11440 }
11441
11442 GameInfo dummyInfo;
11443
11444 int
11445 GameContainsPosition (FILE *f, ListGame *lg)
11446 {
11447     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11448     int fromX, fromY, toX, toY;
11449     char promoChar;
11450     static int initDone=FALSE;
11451
11452     // weed out games based on numerical tag comparison
11453     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11454     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11455     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11456     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11457     if(!initDone) {
11458         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11459         initDone = TRUE;
11460     }
11461     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11462     else CopyBoard(boards[scratch], initialPosition); // default start position
11463     if(lg->moves) {
11464         turn = btm + 1;
11465         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11466         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11467     }
11468     if(btm) plyNr++;
11469     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11470     fseek(f, lg->offset, 0);
11471     yynewfile(f);
11472     while(1) {
11473         yyboardindex = scratch;
11474         quickFlag = plyNr+1;
11475         next = Myylex();
11476         quickFlag = 0;
11477         switch(next) {
11478             case PGNTag:
11479                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11480             default:
11481                 continue;
11482
11483             case XBoardGame:
11484             case GNUChessGame:
11485                 if(plyNr) return -1; // after we have seen moves, this is for new game
11486               continue;
11487
11488             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11489             case ImpossibleMove:
11490             case WhiteWins: // game ends here with these four
11491             case BlackWins:
11492             case GameIsDrawn:
11493             case GameUnfinished:
11494                 return -1;
11495
11496             case IllegalMove:
11497                 if(appData.testLegality) return -1;
11498             case WhiteCapturesEnPassant:
11499             case BlackCapturesEnPassant:
11500             case WhitePromotion:
11501             case BlackPromotion:
11502             case WhiteNonPromotion:
11503             case BlackNonPromotion:
11504             case NormalMove:
11505             case WhiteKingSideCastle:
11506             case WhiteQueenSideCastle:
11507             case BlackKingSideCastle:
11508             case BlackQueenSideCastle:
11509             case WhiteKingSideCastleWild:
11510             case WhiteQueenSideCastleWild:
11511             case BlackKingSideCastleWild:
11512             case BlackQueenSideCastleWild:
11513             case WhiteHSideCastleFR:
11514             case WhiteASideCastleFR:
11515             case BlackHSideCastleFR:
11516             case BlackASideCastleFR:
11517                 fromX = currentMoveString[0] - AAA;
11518                 fromY = currentMoveString[1] - ONE;
11519                 toX = currentMoveString[2] - AAA;
11520                 toY = currentMoveString[3] - ONE;
11521                 promoChar = currentMoveString[4];
11522                 break;
11523             case WhiteDrop:
11524             case BlackDrop:
11525                 fromX = next == WhiteDrop ?
11526                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11527                   (int) CharToPiece(ToLower(currentMoveString[0]));
11528                 fromY = DROP_RANK;
11529                 toX = currentMoveString[2] - AAA;
11530                 toY = currentMoveString[3] - ONE;
11531                 promoChar = 0;
11532                 break;
11533         }
11534         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11535         plyNr++;
11536         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11537         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11538         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11539         if(appData.findMirror) {
11540             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11541             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11542         }
11543     }
11544 }
11545
11546 /* Load the nth game from open file f */
11547 int
11548 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11549 {
11550     ChessMove cm;
11551     char buf[MSG_SIZ];
11552     int gn = gameNumber;
11553     ListGame *lg = NULL;
11554     int numPGNTags = 0;
11555     int err, pos = -1;
11556     GameMode oldGameMode;
11557     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11558
11559     if (appData.debugMode)
11560         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11561
11562     if (gameMode == Training )
11563         SetTrainingModeOff();
11564
11565     oldGameMode = gameMode;
11566     if (gameMode != BeginningOfGame) {
11567       Reset(FALSE, TRUE);
11568     }
11569
11570     gameFileFP = f;
11571     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11572         fclose(lastLoadGameFP);
11573     }
11574
11575     if (useList) {
11576         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11577
11578         if (lg) {
11579             fseek(f, lg->offset, 0);
11580             GameListHighlight(gameNumber);
11581             pos = lg->position;
11582             gn = 1;
11583         }
11584         else {
11585             DisplayError(_("Game number out of range"), 0);
11586             return FALSE;
11587         }
11588     } else {
11589         GameListDestroy();
11590         if (fseek(f, 0, 0) == -1) {
11591             if (f == lastLoadGameFP ?
11592                 gameNumber == lastLoadGameNumber + 1 :
11593                 gameNumber == 1) {
11594                 gn = 1;
11595             } else {
11596                 DisplayError(_("Can't seek on game file"), 0);
11597                 return FALSE;
11598             }
11599         }
11600     }
11601     lastLoadGameFP = f;
11602     lastLoadGameNumber = gameNumber;
11603     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11604     lastLoadGameUseList = useList;
11605
11606     yynewfile(f);
11607
11608     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11609       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11610                 lg->gameInfo.black);
11611             DisplayTitle(buf);
11612     } else if (*title != NULLCHAR) {
11613         if (gameNumber > 1) {
11614           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11615             DisplayTitle(buf);
11616         } else {
11617             DisplayTitle(title);
11618         }
11619     }
11620
11621     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11622         gameMode = PlayFromGameFile;
11623         ModeHighlight();
11624     }
11625
11626     currentMove = forwardMostMove = backwardMostMove = 0;
11627     CopyBoard(boards[0], initialPosition);
11628     StopClocks();
11629
11630     /*
11631      * Skip the first gn-1 games in the file.
11632      * Also skip over anything that precedes an identifiable
11633      * start of game marker, to avoid being confused by
11634      * garbage at the start of the file.  Currently
11635      * recognized start of game markers are the move number "1",
11636      * the pattern "gnuchess .* game", the pattern
11637      * "^[#;%] [^ ]* game file", and a PGN tag block.
11638      * A game that starts with one of the latter two patterns
11639      * will also have a move number 1, possibly
11640      * following a position diagram.
11641      * 5-4-02: Let's try being more lenient and allowing a game to
11642      * start with an unnumbered move.  Does that break anything?
11643      */
11644     cm = lastLoadGameStart = EndOfFile;
11645     while (gn > 0) {
11646         yyboardindex = forwardMostMove;
11647         cm = (ChessMove) Myylex();
11648         switch (cm) {
11649           case EndOfFile:
11650             if (cmailMsgLoaded) {
11651                 nCmailGames = CMAIL_MAX_GAMES - gn;
11652             } else {
11653                 Reset(TRUE, TRUE);
11654                 DisplayError(_("Game not found in file"), 0);
11655             }
11656             return FALSE;
11657
11658           case GNUChessGame:
11659           case XBoardGame:
11660             gn--;
11661             lastLoadGameStart = cm;
11662             break;
11663
11664           case MoveNumberOne:
11665             switch (lastLoadGameStart) {
11666               case GNUChessGame:
11667               case XBoardGame:
11668               case PGNTag:
11669                 break;
11670               case MoveNumberOne:
11671               case EndOfFile:
11672                 gn--;           /* count this game */
11673                 lastLoadGameStart = cm;
11674                 break;
11675               default:
11676                 /* impossible */
11677                 break;
11678             }
11679             break;
11680
11681           case PGNTag:
11682             switch (lastLoadGameStart) {
11683               case GNUChessGame:
11684               case PGNTag:
11685               case MoveNumberOne:
11686               case EndOfFile:
11687                 gn--;           /* count this game */
11688                 lastLoadGameStart = cm;
11689                 break;
11690               case XBoardGame:
11691                 lastLoadGameStart = cm; /* game counted already */
11692                 break;
11693               default:
11694                 /* impossible */
11695                 break;
11696             }
11697             if (gn > 0) {
11698                 do {
11699                     yyboardindex = forwardMostMove;
11700                     cm = (ChessMove) Myylex();
11701                 } while (cm == PGNTag || cm == Comment);
11702             }
11703             break;
11704
11705           case WhiteWins:
11706           case BlackWins:
11707           case GameIsDrawn:
11708             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11709                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11710                     != CMAIL_OLD_RESULT) {
11711                     nCmailResults ++ ;
11712                     cmailResult[  CMAIL_MAX_GAMES
11713                                 - gn - 1] = CMAIL_OLD_RESULT;
11714                 }
11715             }
11716             break;
11717
11718           case NormalMove:
11719             /* Only a NormalMove can be at the start of a game
11720              * without a position diagram. */
11721             if (lastLoadGameStart == EndOfFile ) {
11722               gn--;
11723               lastLoadGameStart = MoveNumberOne;
11724             }
11725             break;
11726
11727           default:
11728             break;
11729         }
11730     }
11731
11732     if (appData.debugMode)
11733       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11734
11735     if (cm == XBoardGame) {
11736         /* Skip any header junk before position diagram and/or move 1 */
11737         for (;;) {
11738             yyboardindex = forwardMostMove;
11739             cm = (ChessMove) Myylex();
11740
11741             if (cm == EndOfFile ||
11742                 cm == GNUChessGame || cm == XBoardGame) {
11743                 /* Empty game; pretend end-of-file and handle later */
11744                 cm = EndOfFile;
11745                 break;
11746             }
11747
11748             if (cm == MoveNumberOne || cm == PositionDiagram ||
11749                 cm == PGNTag || cm == Comment)
11750               break;
11751         }
11752     } else if (cm == GNUChessGame) {
11753         if (gameInfo.event != NULL) {
11754             free(gameInfo.event);
11755         }
11756         gameInfo.event = StrSave(yy_text);
11757     }
11758
11759     startedFromSetupPosition = FALSE;
11760     while (cm == PGNTag) {
11761         if (appData.debugMode)
11762           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11763         err = ParsePGNTag(yy_text, &gameInfo);
11764         if (!err) numPGNTags++;
11765
11766         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11767         if(gameInfo.variant != oldVariant) {
11768             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11769             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11770             InitPosition(TRUE);
11771             oldVariant = gameInfo.variant;
11772             if (appData.debugMode)
11773               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11774         }
11775
11776
11777         if (gameInfo.fen != NULL) {
11778           Board initial_position;
11779           startedFromSetupPosition = TRUE;
11780           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11781             Reset(TRUE, TRUE);
11782             DisplayError(_("Bad FEN position in file"), 0);
11783             return FALSE;
11784           }
11785           CopyBoard(boards[0], initial_position);
11786           if (blackPlaysFirst) {
11787             currentMove = forwardMostMove = backwardMostMove = 1;
11788             CopyBoard(boards[1], initial_position);
11789             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11790             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11791             timeRemaining[0][1] = whiteTimeRemaining;
11792             timeRemaining[1][1] = blackTimeRemaining;
11793             if (commentList[0] != NULL) {
11794               commentList[1] = commentList[0];
11795               commentList[0] = NULL;
11796             }
11797           } else {
11798             currentMove = forwardMostMove = backwardMostMove = 0;
11799           }
11800           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11801           {   int i;
11802               initialRulePlies = FENrulePlies;
11803               for( i=0; i< nrCastlingRights; i++ )
11804                   initialRights[i] = initial_position[CASTLING][i];
11805           }
11806           yyboardindex = forwardMostMove;
11807           free(gameInfo.fen);
11808           gameInfo.fen = NULL;
11809         }
11810
11811         yyboardindex = forwardMostMove;
11812         cm = (ChessMove) Myylex();
11813
11814         /* Handle comments interspersed among the tags */
11815         while (cm == Comment) {
11816             char *p;
11817             if (appData.debugMode)
11818               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11819             p = yy_text;
11820             AppendComment(currentMove, p, FALSE);
11821             yyboardindex = forwardMostMove;
11822             cm = (ChessMove) Myylex();
11823         }
11824     }
11825
11826     /* don't rely on existence of Event tag since if game was
11827      * pasted from clipboard the Event tag may not exist
11828      */
11829     if (numPGNTags > 0){
11830         char *tags;
11831         if (gameInfo.variant == VariantNormal) {
11832           VariantClass v = StringToVariant(gameInfo.event);
11833           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11834           if(v < VariantShogi) gameInfo.variant = v;
11835         }
11836         if (!matchMode) {
11837           if( appData.autoDisplayTags ) {
11838             tags = PGNTags(&gameInfo);
11839             TagsPopUp(tags, CmailMsg());
11840             free(tags);
11841           }
11842         }
11843     } else {
11844         /* Make something up, but don't display it now */
11845         SetGameInfo();
11846         TagsPopDown();
11847     }
11848
11849     if (cm == PositionDiagram) {
11850         int i, j;
11851         char *p;
11852         Board initial_position;
11853
11854         if (appData.debugMode)
11855           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11856
11857         if (!startedFromSetupPosition) {
11858             p = yy_text;
11859             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11860               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11861                 switch (*p) {
11862                   case '{':
11863                   case '[':
11864                   case '-':
11865                   case ' ':
11866                   case '\t':
11867                   case '\n':
11868                   case '\r':
11869                     break;
11870                   default:
11871                     initial_position[i][j++] = CharToPiece(*p);
11872                     break;
11873                 }
11874             while (*p == ' ' || *p == '\t' ||
11875                    *p == '\n' || *p == '\r') p++;
11876
11877             if (strncmp(p, "black", strlen("black"))==0)
11878               blackPlaysFirst = TRUE;
11879             else
11880               blackPlaysFirst = FALSE;
11881             startedFromSetupPosition = TRUE;
11882
11883             CopyBoard(boards[0], initial_position);
11884             if (blackPlaysFirst) {
11885                 currentMove = forwardMostMove = backwardMostMove = 1;
11886                 CopyBoard(boards[1], initial_position);
11887                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11888                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11889                 timeRemaining[0][1] = whiteTimeRemaining;
11890                 timeRemaining[1][1] = blackTimeRemaining;
11891                 if (commentList[0] != NULL) {
11892                     commentList[1] = commentList[0];
11893                     commentList[0] = NULL;
11894                 }
11895             } else {
11896                 currentMove = forwardMostMove = backwardMostMove = 0;
11897             }
11898         }
11899         yyboardindex = forwardMostMove;
11900         cm = (ChessMove) Myylex();
11901     }
11902
11903     if (first.pr == NoProc) {
11904         StartChessProgram(&first);
11905     }
11906     InitChessProgram(&first, FALSE);
11907     SendToProgram("force\n", &first);
11908     if (startedFromSetupPosition) {
11909         SendBoard(&first, forwardMostMove);
11910     if (appData.debugMode) {
11911         fprintf(debugFP, "Load Game\n");
11912     }
11913         DisplayBothClocks();
11914     }
11915
11916     /* [HGM] server: flag to write setup moves in broadcast file as one */
11917     loadFlag = appData.suppressLoadMoves;
11918
11919     while (cm == Comment) {
11920         char *p;
11921         if (appData.debugMode)
11922           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11923         p = yy_text;
11924         AppendComment(currentMove, p, FALSE);
11925         yyboardindex = forwardMostMove;
11926         cm = (ChessMove) Myylex();
11927     }
11928
11929     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11930         cm == WhiteWins || cm == BlackWins ||
11931         cm == GameIsDrawn || cm == GameUnfinished) {
11932         DisplayMessage("", _("No moves in game"));
11933         if (cmailMsgLoaded) {
11934             if (appData.debugMode)
11935               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11936             ClearHighlights();
11937             flipView = FALSE;
11938         }
11939         DrawPosition(FALSE, boards[currentMove]);
11940         DisplayBothClocks();
11941         gameMode = EditGame;
11942         ModeHighlight();
11943         gameFileFP = NULL;
11944         cmailOldMove = 0;
11945         return TRUE;
11946     }
11947
11948     // [HGM] PV info: routine tests if comment empty
11949     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11950         DisplayComment(currentMove - 1, commentList[currentMove]);
11951     }
11952     if (!matchMode && appData.timeDelay != 0)
11953       DrawPosition(FALSE, boards[currentMove]);
11954
11955     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11956       programStats.ok_to_send = 1;
11957     }
11958
11959     /* if the first token after the PGN tags is a move
11960      * and not move number 1, retrieve it from the parser
11961      */
11962     if (cm != MoveNumberOne)
11963         LoadGameOneMove(cm);
11964
11965     /* load the remaining moves from the file */
11966     while (LoadGameOneMove(EndOfFile)) {
11967       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11968       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11969     }
11970
11971     /* rewind to the start of the game */
11972     currentMove = backwardMostMove;
11973
11974     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11975
11976     if (oldGameMode == AnalyzeFile ||
11977         oldGameMode == AnalyzeMode) {
11978       AnalyzeFileEvent();
11979     }
11980
11981     if (!matchMode && pos >= 0) {
11982         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11983     } else
11984     if (matchMode || appData.timeDelay == 0) {
11985       ToEndEvent();
11986     } else if (appData.timeDelay > 0) {
11987       AutoPlayGameLoop();
11988     }
11989
11990     if (appData.debugMode)
11991         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11992
11993     loadFlag = 0; /* [HGM] true game starts */
11994     return TRUE;
11995 }
11996
11997 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11998 int
11999 ReloadPosition (int offset)
12000 {
12001     int positionNumber = lastLoadPositionNumber + offset;
12002     if (lastLoadPositionFP == NULL) {
12003         DisplayError(_("No position has been loaded yet"), 0);
12004         return FALSE;
12005     }
12006     if (positionNumber <= 0) {
12007         DisplayError(_("Can't back up any further"), 0);
12008         return FALSE;
12009     }
12010     return LoadPosition(lastLoadPositionFP, positionNumber,
12011                         lastLoadPositionTitle);
12012 }
12013
12014 /* Load the nth position from the given file */
12015 int
12016 LoadPositionFromFile (char *filename, int n, char *title)
12017 {
12018     FILE *f;
12019     char buf[MSG_SIZ];
12020
12021     if (strcmp(filename, "-") == 0) {
12022         return LoadPosition(stdin, n, "stdin");
12023     } else {
12024         f = fopen(filename, "rb");
12025         if (f == NULL) {
12026             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12027             DisplayError(buf, errno);
12028             return FALSE;
12029         } else {
12030             return LoadPosition(f, n, title);
12031         }
12032     }
12033 }
12034
12035 /* Load the nth position from the given open file, and close it */
12036 int
12037 LoadPosition (FILE *f, int positionNumber, char *title)
12038 {
12039     char *p, line[MSG_SIZ];
12040     Board initial_position;
12041     int i, j, fenMode, pn;
12042
12043     if (gameMode == Training )
12044         SetTrainingModeOff();
12045
12046     if (gameMode != BeginningOfGame) {
12047         Reset(FALSE, TRUE);
12048     }
12049     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12050         fclose(lastLoadPositionFP);
12051     }
12052     if (positionNumber == 0) positionNumber = 1;
12053     lastLoadPositionFP = f;
12054     lastLoadPositionNumber = positionNumber;
12055     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12056     if (first.pr == NoProc && !appData.noChessProgram) {
12057       StartChessProgram(&first);
12058       InitChessProgram(&first, FALSE);
12059     }
12060     pn = positionNumber;
12061     if (positionNumber < 0) {
12062         /* Negative position number means to seek to that byte offset */
12063         if (fseek(f, -positionNumber, 0) == -1) {
12064             DisplayError(_("Can't seek on position file"), 0);
12065             return FALSE;
12066         };
12067         pn = 1;
12068     } else {
12069         if (fseek(f, 0, 0) == -1) {
12070             if (f == lastLoadPositionFP ?
12071                 positionNumber == lastLoadPositionNumber + 1 :
12072                 positionNumber == 1) {
12073                 pn = 1;
12074             } else {
12075                 DisplayError(_("Can't seek on position file"), 0);
12076                 return FALSE;
12077             }
12078         }
12079     }
12080     /* See if this file is FEN or old-style xboard */
12081     if (fgets(line, MSG_SIZ, f) == NULL) {
12082         DisplayError(_("Position not found in file"), 0);
12083         return FALSE;
12084     }
12085     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12086     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12087
12088     if (pn >= 2) {
12089         if (fenMode || line[0] == '#') pn--;
12090         while (pn > 0) {
12091             /* skip positions before number pn */
12092             if (fgets(line, MSG_SIZ, f) == NULL) {
12093                 Reset(TRUE, TRUE);
12094                 DisplayError(_("Position not found in file"), 0);
12095                 return FALSE;
12096             }
12097             if (fenMode || line[0] == '#') pn--;
12098         }
12099     }
12100
12101     if (fenMode) {
12102         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12103             DisplayError(_("Bad FEN position in file"), 0);
12104             return FALSE;
12105         }
12106     } else {
12107         (void) fgets(line, MSG_SIZ, f);
12108         (void) fgets(line, MSG_SIZ, f);
12109
12110         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12111             (void) fgets(line, MSG_SIZ, f);
12112             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12113                 if (*p == ' ')
12114                   continue;
12115                 initial_position[i][j++] = CharToPiece(*p);
12116             }
12117         }
12118
12119         blackPlaysFirst = FALSE;
12120         if (!feof(f)) {
12121             (void) fgets(line, MSG_SIZ, f);
12122             if (strncmp(line, "black", strlen("black"))==0)
12123               blackPlaysFirst = TRUE;
12124         }
12125     }
12126     startedFromSetupPosition = TRUE;
12127
12128     CopyBoard(boards[0], initial_position);
12129     if (blackPlaysFirst) {
12130         currentMove = forwardMostMove = backwardMostMove = 1;
12131         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12132         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12133         CopyBoard(boards[1], initial_position);
12134         DisplayMessage("", _("Black to play"));
12135     } else {
12136         currentMove = forwardMostMove = backwardMostMove = 0;
12137         DisplayMessage("", _("White to play"));
12138     }
12139     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12140     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12141         SendToProgram("force\n", &first);
12142         SendBoard(&first, forwardMostMove);
12143     }
12144     if (appData.debugMode) {
12145 int i, j;
12146   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12147   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12148         fprintf(debugFP, "Load Position\n");
12149     }
12150
12151     if (positionNumber > 1) {
12152       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12153         DisplayTitle(line);
12154     } else {
12155         DisplayTitle(title);
12156     }
12157     gameMode = EditGame;
12158     ModeHighlight();
12159     ResetClocks();
12160     timeRemaining[0][1] = whiteTimeRemaining;
12161     timeRemaining[1][1] = blackTimeRemaining;
12162     DrawPosition(FALSE, boards[currentMove]);
12163
12164     return TRUE;
12165 }
12166
12167
12168 void
12169 CopyPlayerNameIntoFileName (char **dest, char *src)
12170 {
12171     while (*src != NULLCHAR && *src != ',') {
12172         if (*src == ' ') {
12173             *(*dest)++ = '_';
12174             src++;
12175         } else {
12176             *(*dest)++ = *src++;
12177         }
12178     }
12179 }
12180
12181 char *
12182 DefaultFileName (char *ext)
12183 {
12184     static char def[MSG_SIZ];
12185     char *p;
12186
12187     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12188         p = def;
12189         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12190         *p++ = '-';
12191         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12192         *p++ = '.';
12193         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12194     } else {
12195         def[0] = NULLCHAR;
12196     }
12197     return def;
12198 }
12199
12200 /* Save the current game to the given file */
12201 int
12202 SaveGameToFile (char *filename, int append)
12203 {
12204     FILE *f;
12205     char buf[MSG_SIZ];
12206     int result, i, t,tot=0;
12207
12208     if (strcmp(filename, "-") == 0) {
12209         return SaveGame(stdout, 0, NULL);
12210     } else {
12211         for(i=0; i<10; i++) { // upto 10 tries
12212              f = fopen(filename, append ? "a" : "w");
12213              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12214              if(f || errno != 13) break;
12215              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12216              tot += t;
12217         }
12218         if (f == NULL) {
12219             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12220             DisplayError(buf, errno);
12221             return FALSE;
12222         } else {
12223             safeStrCpy(buf, lastMsg, MSG_SIZ);
12224             DisplayMessage(_("Waiting for access to save file"), "");
12225             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12226             DisplayMessage(_("Saving game"), "");
12227             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12228             result = SaveGame(f, 0, NULL);
12229             DisplayMessage(buf, "");
12230             return result;
12231         }
12232     }
12233 }
12234
12235 char *
12236 SavePart (char *str)
12237 {
12238     static char buf[MSG_SIZ];
12239     char *p;
12240
12241     p = strchr(str, ' ');
12242     if (p == NULL) return str;
12243     strncpy(buf, str, p - str);
12244     buf[p - str] = NULLCHAR;
12245     return buf;
12246 }
12247
12248 #define PGN_MAX_LINE 75
12249
12250 #define PGN_SIDE_WHITE  0
12251 #define PGN_SIDE_BLACK  1
12252
12253 static int
12254 FindFirstMoveOutOfBook (int side)
12255 {
12256     int result = -1;
12257
12258     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12259         int index = backwardMostMove;
12260         int has_book_hit = 0;
12261
12262         if( (index % 2) != side ) {
12263             index++;
12264         }
12265
12266         while( index < forwardMostMove ) {
12267             /* Check to see if engine is in book */
12268             int depth = pvInfoList[index].depth;
12269             int score = pvInfoList[index].score;
12270             int in_book = 0;
12271
12272             if( depth <= 2 ) {
12273                 in_book = 1;
12274             }
12275             else if( score == 0 && depth == 63 ) {
12276                 in_book = 1; /* Zappa */
12277             }
12278             else if( score == 2 && depth == 99 ) {
12279                 in_book = 1; /* Abrok */
12280             }
12281
12282             has_book_hit += in_book;
12283
12284             if( ! in_book ) {
12285                 result = index;
12286
12287                 break;
12288             }
12289
12290             index += 2;
12291         }
12292     }
12293
12294     return result;
12295 }
12296
12297 void
12298 GetOutOfBookInfo (char * buf)
12299 {
12300     int oob[2];
12301     int i;
12302     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12303
12304     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12305     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12306
12307     *buf = '\0';
12308
12309     if( oob[0] >= 0 || oob[1] >= 0 ) {
12310         for( i=0; i<2; i++ ) {
12311             int idx = oob[i];
12312
12313             if( idx >= 0 ) {
12314                 if( i > 0 && oob[0] >= 0 ) {
12315                     strcat( buf, "   " );
12316                 }
12317
12318                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12319                 sprintf( buf+strlen(buf), "%s%.2f",
12320                     pvInfoList[idx].score >= 0 ? "+" : "",
12321                     pvInfoList[idx].score / 100.0 );
12322             }
12323         }
12324     }
12325 }
12326
12327 /* Save game in PGN style and close the file */
12328 int
12329 SaveGamePGN (FILE *f)
12330 {
12331     int i, offset, linelen, newblock;
12332     time_t tm;
12333 //    char *movetext;
12334     char numtext[32];
12335     int movelen, numlen, blank;
12336     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12337
12338     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12339
12340     tm = time((time_t *) NULL);
12341
12342     PrintPGNTags(f, &gameInfo);
12343
12344     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12345
12346     if (backwardMostMove > 0 || startedFromSetupPosition) {
12347         char *fen = PositionToFEN(backwardMostMove, NULL);
12348         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12349         fprintf(f, "\n{--------------\n");
12350         PrintPosition(f, backwardMostMove);
12351         fprintf(f, "--------------}\n");
12352         free(fen);
12353     }
12354     else {
12355         /* [AS] Out of book annotation */
12356         if( appData.saveOutOfBookInfo ) {
12357             char buf[64];
12358
12359             GetOutOfBookInfo( buf );
12360
12361             if( buf[0] != '\0' ) {
12362                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12363             }
12364         }
12365
12366         fprintf(f, "\n");
12367     }
12368
12369     i = backwardMostMove;
12370     linelen = 0;
12371     newblock = TRUE;
12372
12373     while (i < forwardMostMove) {
12374         /* Print comments preceding this move */
12375         if (commentList[i] != NULL) {
12376             if (linelen > 0) fprintf(f, "\n");
12377             fprintf(f, "%s", commentList[i]);
12378             linelen = 0;
12379             newblock = TRUE;
12380         }
12381
12382         /* Format move number */
12383         if ((i % 2) == 0)
12384           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12385         else
12386           if (newblock)
12387             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12388           else
12389             numtext[0] = NULLCHAR;
12390
12391         numlen = strlen(numtext);
12392         newblock = FALSE;
12393
12394         /* Print move number */
12395         blank = linelen > 0 && numlen > 0;
12396         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12397             fprintf(f, "\n");
12398             linelen = 0;
12399             blank = 0;
12400         }
12401         if (blank) {
12402             fprintf(f, " ");
12403             linelen++;
12404         }
12405         fprintf(f, "%s", numtext);
12406         linelen += numlen;
12407
12408         /* Get move */
12409         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12410         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12411
12412         /* Print move */
12413         blank = linelen > 0 && movelen > 0;
12414         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12415             fprintf(f, "\n");
12416             linelen = 0;
12417             blank = 0;
12418         }
12419         if (blank) {
12420             fprintf(f, " ");
12421             linelen++;
12422         }
12423         fprintf(f, "%s", move_buffer);
12424         linelen += movelen;
12425
12426         /* [AS] Add PV info if present */
12427         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12428             /* [HGM] add time */
12429             char buf[MSG_SIZ]; int seconds;
12430
12431             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12432
12433             if( seconds <= 0)
12434               buf[0] = 0;
12435             else
12436               if( seconds < 30 )
12437                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12438               else
12439                 {
12440                   seconds = (seconds + 4)/10; // round to full seconds
12441                   if( seconds < 60 )
12442                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12443                   else
12444                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12445                 }
12446
12447             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12448                       pvInfoList[i].score >= 0 ? "+" : "",
12449                       pvInfoList[i].score / 100.0,
12450                       pvInfoList[i].depth,
12451                       buf );
12452
12453             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12454
12455             /* Print score/depth */
12456             blank = linelen > 0 && movelen > 0;
12457             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12458                 fprintf(f, "\n");
12459                 linelen = 0;
12460                 blank = 0;
12461             }
12462             if (blank) {
12463                 fprintf(f, " ");
12464                 linelen++;
12465             }
12466             fprintf(f, "%s", move_buffer);
12467             linelen += movelen;
12468         }
12469
12470         i++;
12471     }
12472
12473     /* Start a new line */
12474     if (linelen > 0) fprintf(f, "\n");
12475
12476     /* Print comments after last move */
12477     if (commentList[i] != NULL) {
12478         fprintf(f, "%s\n", commentList[i]);
12479     }
12480
12481     /* Print result */
12482     if (gameInfo.resultDetails != NULL &&
12483         gameInfo.resultDetails[0] != NULLCHAR) {
12484         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12485                 PGNResult(gameInfo.result));
12486     } else {
12487         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12488     }
12489
12490     fclose(f);
12491     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12492     return TRUE;
12493 }
12494
12495 /* Save game in old style and close the file */
12496 int
12497 SaveGameOldStyle (FILE *f)
12498 {
12499     int i, offset;
12500     time_t tm;
12501
12502     tm = time((time_t *) NULL);
12503
12504     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12505     PrintOpponents(f);
12506
12507     if (backwardMostMove > 0 || startedFromSetupPosition) {
12508         fprintf(f, "\n[--------------\n");
12509         PrintPosition(f, backwardMostMove);
12510         fprintf(f, "--------------]\n");
12511     } else {
12512         fprintf(f, "\n");
12513     }
12514
12515     i = backwardMostMove;
12516     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12517
12518     while (i < forwardMostMove) {
12519         if (commentList[i] != NULL) {
12520             fprintf(f, "[%s]\n", commentList[i]);
12521         }
12522
12523         if ((i % 2) == 1) {
12524             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12525             i++;
12526         } else {
12527             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12528             i++;
12529             if (commentList[i] != NULL) {
12530                 fprintf(f, "\n");
12531                 continue;
12532             }
12533             if (i >= forwardMostMove) {
12534                 fprintf(f, "\n");
12535                 break;
12536             }
12537             fprintf(f, "%s\n", parseList[i]);
12538             i++;
12539         }
12540     }
12541
12542     if (commentList[i] != NULL) {
12543         fprintf(f, "[%s]\n", commentList[i]);
12544     }
12545
12546     /* This isn't really the old style, but it's close enough */
12547     if (gameInfo.resultDetails != NULL &&
12548         gameInfo.resultDetails[0] != NULLCHAR) {
12549         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12550                 gameInfo.resultDetails);
12551     } else {
12552         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12553     }
12554
12555     fclose(f);
12556     return TRUE;
12557 }
12558
12559 /* Save the current game to open file f and close the file */
12560 int
12561 SaveGame (FILE *f, int dummy, char *dummy2)
12562 {
12563     if (gameMode == EditPosition) EditPositionDone(TRUE);
12564     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12565     if (appData.oldSaveStyle)
12566       return SaveGameOldStyle(f);
12567     else
12568       return SaveGamePGN(f);
12569 }
12570
12571 /* Save the current position to the given file */
12572 int
12573 SavePositionToFile (char *filename)
12574 {
12575     FILE *f;
12576     char buf[MSG_SIZ];
12577
12578     if (strcmp(filename, "-") == 0) {
12579         return SavePosition(stdout, 0, NULL);
12580     } else {
12581         f = fopen(filename, "a");
12582         if (f == NULL) {
12583             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12584             DisplayError(buf, errno);
12585             return FALSE;
12586         } else {
12587             safeStrCpy(buf, lastMsg, MSG_SIZ);
12588             DisplayMessage(_("Waiting for access to save file"), "");
12589             flock(fileno(f), LOCK_EX); // [HGM] lock
12590             DisplayMessage(_("Saving position"), "");
12591             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12592             SavePosition(f, 0, NULL);
12593             DisplayMessage(buf, "");
12594             return TRUE;
12595         }
12596     }
12597 }
12598
12599 /* Save the current position to the given open file and close the file */
12600 int
12601 SavePosition (FILE *f, int dummy, char *dummy2)
12602 {
12603     time_t tm;
12604     char *fen;
12605
12606     if (gameMode == EditPosition) EditPositionDone(TRUE);
12607     if (appData.oldSaveStyle) {
12608         tm = time((time_t *) NULL);
12609
12610         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12611         PrintOpponents(f);
12612         fprintf(f, "[--------------\n");
12613         PrintPosition(f, currentMove);
12614         fprintf(f, "--------------]\n");
12615     } else {
12616         fen = PositionToFEN(currentMove, NULL);
12617         fprintf(f, "%s\n", fen);
12618         free(fen);
12619     }
12620     fclose(f);
12621     return TRUE;
12622 }
12623
12624 void
12625 ReloadCmailMsgEvent (int unregister)
12626 {
12627 #if !WIN32
12628     static char *inFilename = NULL;
12629     static char *outFilename;
12630     int i;
12631     struct stat inbuf, outbuf;
12632     int status;
12633
12634     /* Any registered moves are unregistered if unregister is set, */
12635     /* i.e. invoked by the signal handler */
12636     if (unregister) {
12637         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12638             cmailMoveRegistered[i] = FALSE;
12639             if (cmailCommentList[i] != NULL) {
12640                 free(cmailCommentList[i]);
12641                 cmailCommentList[i] = NULL;
12642             }
12643         }
12644         nCmailMovesRegistered = 0;
12645     }
12646
12647     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12648         cmailResult[i] = CMAIL_NOT_RESULT;
12649     }
12650     nCmailResults = 0;
12651
12652     if (inFilename == NULL) {
12653         /* Because the filenames are static they only get malloced once  */
12654         /* and they never get freed                                      */
12655         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12656         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12657
12658         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12659         sprintf(outFilename, "%s.out", appData.cmailGameName);
12660     }
12661
12662     status = stat(outFilename, &outbuf);
12663     if (status < 0) {
12664         cmailMailedMove = FALSE;
12665     } else {
12666         status = stat(inFilename, &inbuf);
12667         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12668     }
12669
12670     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12671        counts the games, notes how each one terminated, etc.
12672
12673        It would be nice to remove this kludge and instead gather all
12674        the information while building the game list.  (And to keep it
12675        in the game list nodes instead of having a bunch of fixed-size
12676        parallel arrays.)  Note this will require getting each game's
12677        termination from the PGN tags, as the game list builder does
12678        not process the game moves.  --mann
12679        */
12680     cmailMsgLoaded = TRUE;
12681     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12682
12683     /* Load first game in the file or popup game menu */
12684     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12685
12686 #endif /* !WIN32 */
12687     return;
12688 }
12689
12690 int
12691 RegisterMove ()
12692 {
12693     FILE *f;
12694     char string[MSG_SIZ];
12695
12696     if (   cmailMailedMove
12697         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12698         return TRUE;            /* Allow free viewing  */
12699     }
12700
12701     /* Unregister move to ensure that we don't leave RegisterMove        */
12702     /* with the move registered when the conditions for registering no   */
12703     /* longer hold                                                       */
12704     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12705         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12706         nCmailMovesRegistered --;
12707
12708         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12709           {
12710               free(cmailCommentList[lastLoadGameNumber - 1]);
12711               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12712           }
12713     }
12714
12715     if (cmailOldMove == -1) {
12716         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12717         return FALSE;
12718     }
12719
12720     if (currentMove > cmailOldMove + 1) {
12721         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12722         return FALSE;
12723     }
12724
12725     if (currentMove < cmailOldMove) {
12726         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12727         return FALSE;
12728     }
12729
12730     if (forwardMostMove > currentMove) {
12731         /* Silently truncate extra moves */
12732         TruncateGame();
12733     }
12734
12735     if (   (currentMove == cmailOldMove + 1)
12736         || (   (currentMove == cmailOldMove)
12737             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12738                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12739         if (gameInfo.result != GameUnfinished) {
12740             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12741         }
12742
12743         if (commentList[currentMove] != NULL) {
12744             cmailCommentList[lastLoadGameNumber - 1]
12745               = StrSave(commentList[currentMove]);
12746         }
12747         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12748
12749         if (appData.debugMode)
12750           fprintf(debugFP, "Saving %s for game %d\n",
12751                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12752
12753         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12754
12755         f = fopen(string, "w");
12756         if (appData.oldSaveStyle) {
12757             SaveGameOldStyle(f); /* also closes the file */
12758
12759             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12760             f = fopen(string, "w");
12761             SavePosition(f, 0, NULL); /* also closes the file */
12762         } else {
12763             fprintf(f, "{--------------\n");
12764             PrintPosition(f, currentMove);
12765             fprintf(f, "--------------}\n\n");
12766
12767             SaveGame(f, 0, NULL); /* also closes the file*/
12768         }
12769
12770         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12771         nCmailMovesRegistered ++;
12772     } else if (nCmailGames == 1) {
12773         DisplayError(_("You have not made a move yet"), 0);
12774         return FALSE;
12775     }
12776
12777     return TRUE;
12778 }
12779
12780 void
12781 MailMoveEvent ()
12782 {
12783 #if !WIN32
12784     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12785     FILE *commandOutput;
12786     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12787     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12788     int nBuffers;
12789     int i;
12790     int archived;
12791     char *arcDir;
12792
12793     if (! cmailMsgLoaded) {
12794         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12795         return;
12796     }
12797
12798     if (nCmailGames == nCmailResults) {
12799         DisplayError(_("No unfinished games"), 0);
12800         return;
12801     }
12802
12803 #if CMAIL_PROHIBIT_REMAIL
12804     if (cmailMailedMove) {
12805       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);
12806         DisplayError(msg, 0);
12807         return;
12808     }
12809 #endif
12810
12811     if (! (cmailMailedMove || RegisterMove())) return;
12812
12813     if (   cmailMailedMove
12814         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12815       snprintf(string, MSG_SIZ, partCommandString,
12816                appData.debugMode ? " -v" : "", appData.cmailGameName);
12817         commandOutput = popen(string, "r");
12818
12819         if (commandOutput == NULL) {
12820             DisplayError(_("Failed to invoke cmail"), 0);
12821         } else {
12822             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12823                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12824             }
12825             if (nBuffers > 1) {
12826                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12827                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12828                 nBytes = MSG_SIZ - 1;
12829             } else {
12830                 (void) memcpy(msg, buffer, nBytes);
12831             }
12832             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12833
12834             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12835                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12836
12837                 archived = TRUE;
12838                 for (i = 0; i < nCmailGames; i ++) {
12839                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12840                         archived = FALSE;
12841                     }
12842                 }
12843                 if (   archived
12844                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12845                         != NULL)) {
12846                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12847                            arcDir,
12848                            appData.cmailGameName,
12849                            gameInfo.date);
12850                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12851                     cmailMsgLoaded = FALSE;
12852                 }
12853             }
12854
12855             DisplayInformation(msg);
12856             pclose(commandOutput);
12857         }
12858     } else {
12859         if ((*cmailMsg) != '\0') {
12860             DisplayInformation(cmailMsg);
12861         }
12862     }
12863
12864     return;
12865 #endif /* !WIN32 */
12866 }
12867
12868 char *
12869 CmailMsg ()
12870 {
12871 #if WIN32
12872     return NULL;
12873 #else
12874     int  prependComma = 0;
12875     char number[5];
12876     char string[MSG_SIZ];       /* Space for game-list */
12877     int  i;
12878
12879     if (!cmailMsgLoaded) return "";
12880
12881     if (cmailMailedMove) {
12882       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12883     } else {
12884         /* Create a list of games left */
12885       snprintf(string, MSG_SIZ, "[");
12886         for (i = 0; i < nCmailGames; i ++) {
12887             if (! (   cmailMoveRegistered[i]
12888                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12889                 if (prependComma) {
12890                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12891                 } else {
12892                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12893                     prependComma = 1;
12894                 }
12895
12896                 strcat(string, number);
12897             }
12898         }
12899         strcat(string, "]");
12900
12901         if (nCmailMovesRegistered + nCmailResults == 0) {
12902             switch (nCmailGames) {
12903               case 1:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12905                 break;
12906
12907               case 2:
12908                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12909                 break;
12910
12911               default:
12912                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12913                          nCmailGames);
12914                 break;
12915             }
12916         } else {
12917             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12918               case 1:
12919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12920                          string);
12921                 break;
12922
12923               case 0:
12924                 if (nCmailResults == nCmailGames) {
12925                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12926                 } else {
12927                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12928                 }
12929                 break;
12930
12931               default:
12932                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12933                          string);
12934             }
12935         }
12936     }
12937     return cmailMsg;
12938 #endif /* WIN32 */
12939 }
12940
12941 void
12942 ResetGameEvent ()
12943 {
12944     if (gameMode == Training)
12945       SetTrainingModeOff();
12946
12947     Reset(TRUE, TRUE);
12948     cmailMsgLoaded = FALSE;
12949     if (appData.icsActive) {
12950       SendToICS(ics_prefix);
12951       SendToICS("refresh\n");
12952     }
12953 }
12954
12955 void
12956 ExitEvent (int status)
12957 {
12958     exiting++;
12959     if (exiting > 2) {
12960       /* Give up on clean exit */
12961       exit(status);
12962     }
12963     if (exiting > 1) {
12964       /* Keep trying for clean exit */
12965       return;
12966     }
12967
12968     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12969
12970     if (telnetISR != NULL) {
12971       RemoveInputSource(telnetISR);
12972     }
12973     if (icsPR != NoProc) {
12974       DestroyChildProcess(icsPR, TRUE);
12975     }
12976
12977     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12978     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12979
12980     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12981     /* make sure this other one finishes before killing it!                  */
12982     if(endingGame) { int count = 0;
12983         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12984         while(endingGame && count++ < 10) DoSleep(1);
12985         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12986     }
12987
12988     /* Kill off chess programs */
12989     if (first.pr != NoProc) {
12990         ExitAnalyzeMode();
12991
12992         DoSleep( appData.delayBeforeQuit );
12993         SendToProgram("quit\n", &first);
12994         DoSleep( appData.delayAfterQuit );
12995         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12996     }
12997     if (second.pr != NoProc) {
12998         DoSleep( appData.delayBeforeQuit );
12999         SendToProgram("quit\n", &second);
13000         DoSleep( appData.delayAfterQuit );
13001         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13002     }
13003     if (first.isr != NULL) {
13004         RemoveInputSource(first.isr);
13005     }
13006     if (second.isr != NULL) {
13007         RemoveInputSource(second.isr);
13008     }
13009
13010     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13011     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13012
13013     ShutDownFrontEnd();
13014     exit(status);
13015 }
13016
13017 void
13018 PauseEvent ()
13019 {
13020     if (appData.debugMode)
13021         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13022     if (pausing) {
13023         pausing = FALSE;
13024         ModeHighlight();
13025         if (gameMode == MachinePlaysWhite ||
13026             gameMode == MachinePlaysBlack) {
13027             StartClocks();
13028         } else {
13029             DisplayBothClocks();
13030         }
13031         if (gameMode == PlayFromGameFile) {
13032             if (appData.timeDelay >= 0)
13033                 AutoPlayGameLoop();
13034         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13035             Reset(FALSE, TRUE);
13036             SendToICS(ics_prefix);
13037             SendToICS("refresh\n");
13038         } else if (currentMove < forwardMostMove) {
13039             ForwardInner(forwardMostMove);
13040         }
13041         pauseExamInvalid = FALSE;
13042     } else {
13043         switch (gameMode) {
13044           default:
13045             return;
13046           case IcsExamining:
13047             pauseExamForwardMostMove = forwardMostMove;
13048             pauseExamInvalid = FALSE;
13049             /* fall through */
13050           case IcsObserving:
13051           case IcsPlayingWhite:
13052           case IcsPlayingBlack:
13053             pausing = TRUE;
13054             ModeHighlight();
13055             return;
13056           case PlayFromGameFile:
13057             (void) StopLoadGameTimer();
13058             pausing = TRUE;
13059             ModeHighlight();
13060             break;
13061           case BeginningOfGame:
13062             if (appData.icsActive) return;
13063             /* else fall through */
13064           case MachinePlaysWhite:
13065           case MachinePlaysBlack:
13066           case TwoMachinesPlay:
13067             if (forwardMostMove == 0)
13068               return;           /* don't pause if no one has moved */
13069             if ((gameMode == MachinePlaysWhite &&
13070                  !WhiteOnMove(forwardMostMove)) ||
13071                 (gameMode == MachinePlaysBlack &&
13072                  WhiteOnMove(forwardMostMove))) {
13073                 StopClocks();
13074             }
13075             pausing = TRUE;
13076             ModeHighlight();
13077             break;
13078         }
13079     }
13080 }
13081
13082 void
13083 EditCommentEvent ()
13084 {
13085     char title[MSG_SIZ];
13086
13087     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13088       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13089     } else {
13090       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13091                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13092                parseList[currentMove - 1]);
13093     }
13094
13095     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13096 }
13097
13098
13099 void
13100 EditTagsEvent ()
13101 {
13102     char *tags = PGNTags(&gameInfo);
13103     bookUp = FALSE;
13104     EditTagsPopUp(tags, NULL);
13105     free(tags);
13106 }
13107
13108 void
13109 AnalyzeModeEvent ()
13110 {
13111     if (appData.noChessProgram || gameMode == AnalyzeMode)
13112       return;
13113
13114     if (gameMode != AnalyzeFile) {
13115         if (!appData.icsEngineAnalyze) {
13116                EditGameEvent();
13117                if (gameMode != EditGame) return;
13118         }
13119         ResurrectChessProgram();
13120         SendToProgram("analyze\n", &first);
13121         first.analyzing = TRUE;
13122         /*first.maybeThinking = TRUE;*/
13123         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13124         EngineOutputPopUp();
13125     }
13126     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13127     pausing = FALSE;
13128     ModeHighlight();
13129     SetGameInfo();
13130
13131     StartAnalysisClock();
13132     GetTimeMark(&lastNodeCountTime);
13133     lastNodeCount = 0;
13134 }
13135
13136 void
13137 AnalyzeFileEvent ()
13138 {
13139     if (appData.noChessProgram || gameMode == AnalyzeFile)
13140       return;
13141
13142     if (gameMode != AnalyzeMode) {
13143         EditGameEvent();
13144         if (gameMode != EditGame) return;
13145         ResurrectChessProgram();
13146         SendToProgram("analyze\n", &first);
13147         first.analyzing = TRUE;
13148         /*first.maybeThinking = TRUE;*/
13149         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13150         EngineOutputPopUp();
13151     }
13152     gameMode = AnalyzeFile;
13153     pausing = FALSE;
13154     ModeHighlight();
13155     SetGameInfo();
13156
13157     StartAnalysisClock();
13158     GetTimeMark(&lastNodeCountTime);
13159     lastNodeCount = 0;
13160     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13161 }
13162
13163 void
13164 MachineWhiteEvent ()
13165 {
13166     char buf[MSG_SIZ];
13167     char *bookHit = NULL;
13168
13169     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13170       return;
13171
13172
13173     if (gameMode == PlayFromGameFile ||
13174         gameMode == TwoMachinesPlay  ||
13175         gameMode == Training         ||
13176         gameMode == AnalyzeMode      ||
13177         gameMode == EndOfGame)
13178         EditGameEvent();
13179
13180     if (gameMode == EditPosition)
13181         EditPositionDone(TRUE);
13182
13183     if (!WhiteOnMove(currentMove)) {
13184         DisplayError(_("It is not White's turn"), 0);
13185         return;
13186     }
13187
13188     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13189       ExitAnalyzeMode();
13190
13191     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13192         gameMode == AnalyzeFile)
13193         TruncateGame();
13194
13195     ResurrectChessProgram();    /* in case it isn't running */
13196     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13197         gameMode = MachinePlaysWhite;
13198         ResetClocks();
13199     } else
13200     gameMode = MachinePlaysWhite;
13201     pausing = FALSE;
13202     ModeHighlight();
13203     SetGameInfo();
13204     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13205     DisplayTitle(buf);
13206     if (first.sendName) {
13207       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13208       SendToProgram(buf, &first);
13209     }
13210     if (first.sendTime) {
13211       if (first.useColors) {
13212         SendToProgram("black\n", &first); /*gnu kludge*/
13213       }
13214       SendTimeRemaining(&first, TRUE);
13215     }
13216     if (first.useColors) {
13217       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13218     }
13219     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13220     SetMachineThinkingEnables();
13221     first.maybeThinking = TRUE;
13222     StartClocks();
13223     firstMove = FALSE;
13224
13225     if (appData.autoFlipView && !flipView) {
13226       flipView = !flipView;
13227       DrawPosition(FALSE, NULL);
13228       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13229     }
13230
13231     if(bookHit) { // [HGM] book: simulate book reply
13232         static char bookMove[MSG_SIZ]; // a bit generous?
13233
13234         programStats.nodes = programStats.depth = programStats.time =
13235         programStats.score = programStats.got_only_move = 0;
13236         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13237
13238         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13239         strcat(bookMove, bookHit);
13240         HandleMachineMove(bookMove, &first);
13241     }
13242 }
13243
13244 void
13245 MachineBlackEvent ()
13246 {
13247   char buf[MSG_SIZ];
13248   char *bookHit = NULL;
13249
13250     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13251         return;
13252
13253
13254     if (gameMode == PlayFromGameFile ||
13255         gameMode == TwoMachinesPlay  ||
13256         gameMode == Training         ||
13257         gameMode == AnalyzeMode      ||
13258         gameMode == EndOfGame)
13259         EditGameEvent();
13260
13261     if (gameMode == EditPosition)
13262         EditPositionDone(TRUE);
13263
13264     if (WhiteOnMove(currentMove)) {
13265         DisplayError(_("It is not Black's turn"), 0);
13266         return;
13267     }
13268
13269     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13270       ExitAnalyzeMode();
13271
13272     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13273         gameMode == AnalyzeFile)
13274         TruncateGame();
13275
13276     ResurrectChessProgram();    /* in case it isn't running */
13277     gameMode = MachinePlaysBlack;
13278     pausing = FALSE;
13279     ModeHighlight();
13280     SetGameInfo();
13281     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13282     DisplayTitle(buf);
13283     if (first.sendName) {
13284       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13285       SendToProgram(buf, &first);
13286     }
13287     if (first.sendTime) {
13288       if (first.useColors) {
13289         SendToProgram("white\n", &first); /*gnu kludge*/
13290       }
13291       SendTimeRemaining(&first, FALSE);
13292     }
13293     if (first.useColors) {
13294       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13295     }
13296     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13297     SetMachineThinkingEnables();
13298     first.maybeThinking = TRUE;
13299     StartClocks();
13300
13301     if (appData.autoFlipView && flipView) {
13302       flipView = !flipView;
13303       DrawPosition(FALSE, NULL);
13304       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13305     }
13306     if(bookHit) { // [HGM] book: simulate book reply
13307         static char bookMove[MSG_SIZ]; // a bit generous?
13308
13309         programStats.nodes = programStats.depth = programStats.time =
13310         programStats.score = programStats.got_only_move = 0;
13311         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13312
13313         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13314         strcat(bookMove, bookHit);
13315         HandleMachineMove(bookMove, &first);
13316     }
13317 }
13318
13319
13320 void
13321 DisplayTwoMachinesTitle ()
13322 {
13323     char buf[MSG_SIZ];
13324     if (appData.matchGames > 0) {
13325         if(appData.tourneyFile[0]) {
13326           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13327                    gameInfo.white, _("vs."), gameInfo.black,
13328                    nextGame+1, appData.matchGames+1,
13329                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13330         } else 
13331         if (first.twoMachinesColor[0] == 'w') {
13332           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13333                    gameInfo.white, _("vs."),  gameInfo.black,
13334                    first.matchWins, second.matchWins,
13335                    matchGame - 1 - (first.matchWins + second.matchWins));
13336         } else {
13337           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13338                    gameInfo.white, _("vs."), gameInfo.black,
13339                    second.matchWins, first.matchWins,
13340                    matchGame - 1 - (first.matchWins + second.matchWins));
13341         }
13342     } else {
13343       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13344     }
13345     DisplayTitle(buf);
13346 }
13347
13348 void
13349 SettingsMenuIfReady ()
13350 {
13351   if (second.lastPing != second.lastPong) {
13352     DisplayMessage("", _("Waiting for second chess program"));
13353     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13354     return;
13355   }
13356   ThawUI();
13357   DisplayMessage("", "");
13358   SettingsPopUp(&second);
13359 }
13360
13361 int
13362 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13363 {
13364     char buf[MSG_SIZ];
13365     if (cps->pr == NoProc) {
13366         StartChessProgram(cps);
13367         if (cps->protocolVersion == 1) {
13368           retry();
13369         } else {
13370           /* kludge: allow timeout for initial "feature" command */
13371           FreezeUI();
13372           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13373           DisplayMessage("", buf);
13374           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13375         }
13376         return 1;
13377     }
13378     return 0;
13379 }
13380
13381 void
13382 TwoMachinesEvent P((void))
13383 {
13384     int i;
13385     char buf[MSG_SIZ];
13386     ChessProgramState *onmove;
13387     char *bookHit = NULL;
13388     static int stalling = 0;
13389     TimeMark now;
13390     long wait;
13391
13392     if (appData.noChessProgram) return;
13393
13394     switch (gameMode) {
13395       case TwoMachinesPlay:
13396         return;
13397       case MachinePlaysWhite:
13398       case MachinePlaysBlack:
13399         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13400             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13401             return;
13402         }
13403         /* fall through */
13404       case BeginningOfGame:
13405       case PlayFromGameFile:
13406       case EndOfGame:
13407         EditGameEvent();
13408         if (gameMode != EditGame) return;
13409         break;
13410       case EditPosition:
13411         EditPositionDone(TRUE);
13412         break;
13413       case AnalyzeMode:
13414       case AnalyzeFile:
13415         ExitAnalyzeMode();
13416         break;
13417       case EditGame:
13418       default:
13419         break;
13420     }
13421
13422 //    forwardMostMove = currentMove;
13423     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13424
13425     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13426
13427     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13428     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13429       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13430       return;
13431     }
13432     if(!stalling) {
13433       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13434       SendToProgram("force\n", &second);
13435       stalling = 1;
13436       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13437       return;
13438     }
13439     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13440     if(appData.matchPause>10000 || appData.matchPause<10)
13441                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13442     wait = SubtractTimeMarks(&now, &pauseStart);
13443     if(wait < appData.matchPause) {
13444         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13445         return;
13446     }
13447     // we are now committed to starting the game
13448     stalling = 0;
13449     DisplayMessage("", "");
13450     if (startedFromSetupPosition) {
13451         SendBoard(&second, backwardMostMove);
13452     if (appData.debugMode) {
13453         fprintf(debugFP, "Two Machines\n");
13454     }
13455     }
13456     for (i = backwardMostMove; i < forwardMostMove; i++) {
13457         SendMoveToProgram(i, &second);
13458     }
13459
13460     gameMode = TwoMachinesPlay;
13461     pausing = FALSE;
13462     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13463     SetGameInfo();
13464     DisplayTwoMachinesTitle();
13465     firstMove = TRUE;
13466     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13467         onmove = &first;
13468     } else {
13469         onmove = &second;
13470     }
13471     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13472     SendToProgram(first.computerString, &first);
13473     if (first.sendName) {
13474       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13475       SendToProgram(buf, &first);
13476     }
13477     SendToProgram(second.computerString, &second);
13478     if (second.sendName) {
13479       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13480       SendToProgram(buf, &second);
13481     }
13482
13483     ResetClocks();
13484     if (!first.sendTime || !second.sendTime) {
13485         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13486         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13487     }
13488     if (onmove->sendTime) {
13489       if (onmove->useColors) {
13490         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13491       }
13492       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13493     }
13494     if (onmove->useColors) {
13495       SendToProgram(onmove->twoMachinesColor, onmove);
13496     }
13497     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13498 //    SendToProgram("go\n", onmove);
13499     onmove->maybeThinking = TRUE;
13500     SetMachineThinkingEnables();
13501
13502     StartClocks();
13503
13504     if(bookHit) { // [HGM] book: simulate book reply
13505         static char bookMove[MSG_SIZ]; // a bit generous?
13506
13507         programStats.nodes = programStats.depth = programStats.time =
13508         programStats.score = programStats.got_only_move = 0;
13509         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13510
13511         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13512         strcat(bookMove, bookHit);
13513         savedMessage = bookMove; // args for deferred call
13514         savedState = onmove;
13515         ScheduleDelayedEvent(DeferredBookMove, 1);
13516     }
13517 }
13518
13519 void
13520 TrainingEvent ()
13521 {
13522     if (gameMode == Training) {
13523       SetTrainingModeOff();
13524       gameMode = PlayFromGameFile;
13525       DisplayMessage("", _("Training mode off"));
13526     } else {
13527       gameMode = Training;
13528       animateTraining = appData.animate;
13529
13530       /* make sure we are not already at the end of the game */
13531       if (currentMove < forwardMostMove) {
13532         SetTrainingModeOn();
13533         DisplayMessage("", _("Training mode on"));
13534       } else {
13535         gameMode = PlayFromGameFile;
13536         DisplayError(_("Already at end of game"), 0);
13537       }
13538     }
13539     ModeHighlight();
13540 }
13541
13542 void
13543 IcsClientEvent ()
13544 {
13545     if (!appData.icsActive) return;
13546     switch (gameMode) {
13547       case IcsPlayingWhite:
13548       case IcsPlayingBlack:
13549       case IcsObserving:
13550       case IcsIdle:
13551       case BeginningOfGame:
13552       case IcsExamining:
13553         return;
13554
13555       case EditGame:
13556         break;
13557
13558       case EditPosition:
13559         EditPositionDone(TRUE);
13560         break;
13561
13562       case AnalyzeMode:
13563       case AnalyzeFile:
13564         ExitAnalyzeMode();
13565         break;
13566
13567       default:
13568         EditGameEvent();
13569         break;
13570     }
13571
13572     gameMode = IcsIdle;
13573     ModeHighlight();
13574     return;
13575 }
13576
13577 void
13578 EditGameEvent ()
13579 {
13580     int i;
13581
13582     switch (gameMode) {
13583       case Training:
13584         SetTrainingModeOff();
13585         break;
13586       case MachinePlaysWhite:
13587       case MachinePlaysBlack:
13588       case BeginningOfGame:
13589         SendToProgram("force\n", &first);
13590         SetUserThinkingEnables();
13591         break;
13592       case PlayFromGameFile:
13593         (void) StopLoadGameTimer();
13594         if (gameFileFP != NULL) {
13595             gameFileFP = NULL;
13596         }
13597         break;
13598       case EditPosition:
13599         EditPositionDone(TRUE);
13600         break;
13601       case AnalyzeMode:
13602       case AnalyzeFile:
13603         ExitAnalyzeMode();
13604         SendToProgram("force\n", &first);
13605         break;
13606       case TwoMachinesPlay:
13607         GameEnds(EndOfFile, NULL, GE_PLAYER);
13608         ResurrectChessProgram();
13609         SetUserThinkingEnables();
13610         break;
13611       case EndOfGame:
13612         ResurrectChessProgram();
13613         break;
13614       case IcsPlayingBlack:
13615       case IcsPlayingWhite:
13616         DisplayError(_("Warning: You are still playing a game"), 0);
13617         break;
13618       case IcsObserving:
13619         DisplayError(_("Warning: You are still observing a game"), 0);
13620         break;
13621       case IcsExamining:
13622         DisplayError(_("Warning: You are still examining a game"), 0);
13623         break;
13624       case IcsIdle:
13625         break;
13626       case EditGame:
13627       default:
13628         return;
13629     }
13630
13631     pausing = FALSE;
13632     StopClocks();
13633     first.offeredDraw = second.offeredDraw = 0;
13634
13635     if (gameMode == PlayFromGameFile) {
13636         whiteTimeRemaining = timeRemaining[0][currentMove];
13637         blackTimeRemaining = timeRemaining[1][currentMove];
13638         DisplayTitle("");
13639     }
13640
13641     if (gameMode == MachinePlaysWhite ||
13642         gameMode == MachinePlaysBlack ||
13643         gameMode == TwoMachinesPlay ||
13644         gameMode == EndOfGame) {
13645         i = forwardMostMove;
13646         while (i > currentMove) {
13647             SendToProgram("undo\n", &first);
13648             i--;
13649         }
13650         if(!adjustedClock) {
13651         whiteTimeRemaining = timeRemaining[0][currentMove];
13652         blackTimeRemaining = timeRemaining[1][currentMove];
13653         DisplayBothClocks();
13654         }
13655         if (whiteFlag || blackFlag) {
13656             whiteFlag = blackFlag = 0;
13657         }
13658         DisplayTitle("");
13659     }
13660
13661     gameMode = EditGame;
13662     ModeHighlight();
13663     SetGameInfo();
13664 }
13665
13666
13667 void
13668 EditPositionEvent ()
13669 {
13670     if (gameMode == EditPosition) {
13671         EditGameEvent();
13672         return;
13673     }
13674
13675     EditGameEvent();
13676     if (gameMode != EditGame) return;
13677
13678     gameMode = EditPosition;
13679     ModeHighlight();
13680     SetGameInfo();
13681     if (currentMove > 0)
13682       CopyBoard(boards[0], boards[currentMove]);
13683
13684     blackPlaysFirst = !WhiteOnMove(currentMove);
13685     ResetClocks();
13686     currentMove = forwardMostMove = backwardMostMove = 0;
13687     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13688     DisplayMove(-1);
13689 }
13690
13691 void
13692 ExitAnalyzeMode ()
13693 {
13694     /* [DM] icsEngineAnalyze - possible call from other functions */
13695     if (appData.icsEngineAnalyze) {
13696         appData.icsEngineAnalyze = FALSE;
13697
13698         DisplayMessage("",_("Close ICS engine analyze..."));
13699     }
13700     if (first.analysisSupport && first.analyzing) {
13701       SendToProgram("exit\n", &first);
13702       first.analyzing = FALSE;
13703     }
13704     thinkOutput[0] = NULLCHAR;
13705 }
13706
13707 void
13708 EditPositionDone (Boolean fakeRights)
13709 {
13710     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13711
13712     startedFromSetupPosition = TRUE;
13713     InitChessProgram(&first, FALSE);
13714     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13715       boards[0][EP_STATUS] = EP_NONE;
13716       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13717     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13718         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13719         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13720       } else boards[0][CASTLING][2] = NoRights;
13721     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13722         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13723         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13724       } else boards[0][CASTLING][5] = NoRights;
13725     }
13726     SendToProgram("force\n", &first);
13727     if (blackPlaysFirst) {
13728         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13729         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13730         currentMove = forwardMostMove = backwardMostMove = 1;
13731         CopyBoard(boards[1], boards[0]);
13732     } else {
13733         currentMove = forwardMostMove = backwardMostMove = 0;
13734     }
13735     SendBoard(&first, forwardMostMove);
13736     if (appData.debugMode) {
13737         fprintf(debugFP, "EditPosDone\n");
13738     }
13739     DisplayTitle("");
13740     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13741     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13742     gameMode = EditGame;
13743     ModeHighlight();
13744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13745     ClearHighlights(); /* [AS] */
13746 }
13747
13748 /* Pause for `ms' milliseconds */
13749 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13750 void
13751 TimeDelay (long ms)
13752 {
13753     TimeMark m1, m2;
13754
13755     GetTimeMark(&m1);
13756     do {
13757         GetTimeMark(&m2);
13758     } while (SubtractTimeMarks(&m2, &m1) < ms);
13759 }
13760
13761 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13762 void
13763 SendMultiLineToICS (char *buf)
13764 {
13765     char temp[MSG_SIZ+1], *p;
13766     int len;
13767
13768     len = strlen(buf);
13769     if (len > MSG_SIZ)
13770       len = MSG_SIZ;
13771
13772     strncpy(temp, buf, len);
13773     temp[len] = 0;
13774
13775     p = temp;
13776     while (*p) {
13777         if (*p == '\n' || *p == '\r')
13778           *p = ' ';
13779         ++p;
13780     }
13781
13782     strcat(temp, "\n");
13783     SendToICS(temp);
13784     SendToPlayer(temp, strlen(temp));
13785 }
13786
13787 void
13788 SetWhiteToPlayEvent ()
13789 {
13790     if (gameMode == EditPosition) {
13791         blackPlaysFirst = FALSE;
13792         DisplayBothClocks();    /* works because currentMove is 0 */
13793     } else if (gameMode == IcsExamining) {
13794         SendToICS(ics_prefix);
13795         SendToICS("tomove white\n");
13796     }
13797 }
13798
13799 void
13800 SetBlackToPlayEvent ()
13801 {
13802     if (gameMode == EditPosition) {
13803         blackPlaysFirst = TRUE;
13804         currentMove = 1;        /* kludge */
13805         DisplayBothClocks();
13806         currentMove = 0;
13807     } else if (gameMode == IcsExamining) {
13808         SendToICS(ics_prefix);
13809         SendToICS("tomove black\n");
13810     }
13811 }
13812
13813 void
13814 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13815 {
13816     char buf[MSG_SIZ];
13817     ChessSquare piece = boards[0][y][x];
13818
13819     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13820
13821     switch (selection) {
13822       case ClearBoard:
13823         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13824             SendToICS(ics_prefix);
13825             SendToICS("bsetup clear\n");
13826         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13827             SendToICS(ics_prefix);
13828             SendToICS("clearboard\n");
13829         } else {
13830             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13831                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13832                 for (y = 0; y < BOARD_HEIGHT; y++) {
13833                     if (gameMode == IcsExamining) {
13834                         if (boards[currentMove][y][x] != EmptySquare) {
13835                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13836                                     AAA + x, ONE + y);
13837                             SendToICS(buf);
13838                         }
13839                     } else {
13840                         boards[0][y][x] = p;
13841                     }
13842                 }
13843             }
13844         }
13845         if (gameMode == EditPosition) {
13846             DrawPosition(FALSE, boards[0]);
13847         }
13848         break;
13849
13850       case WhitePlay:
13851         SetWhiteToPlayEvent();
13852         break;
13853
13854       case BlackPlay:
13855         SetBlackToPlayEvent();
13856         break;
13857
13858       case EmptySquare:
13859         if (gameMode == IcsExamining) {
13860             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13861             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13862             SendToICS(buf);
13863         } else {
13864             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13865                 if(x == BOARD_LEFT-2) {
13866                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13867                     boards[0][y][1] = 0;
13868                 } else
13869                 if(x == BOARD_RGHT+1) {
13870                     if(y >= gameInfo.holdingsSize) break;
13871                     boards[0][y][BOARD_WIDTH-2] = 0;
13872                 } else break;
13873             }
13874             boards[0][y][x] = EmptySquare;
13875             DrawPosition(FALSE, boards[0]);
13876         }
13877         break;
13878
13879       case PromotePiece:
13880         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13881            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13882             selection = (ChessSquare) (PROMOTED piece);
13883         } else if(piece == EmptySquare) selection = WhiteSilver;
13884         else selection = (ChessSquare)((int)piece - 1);
13885         goto defaultlabel;
13886
13887       case DemotePiece:
13888         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13889            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13890             selection = (ChessSquare) (DEMOTED piece);
13891         } else if(piece == EmptySquare) selection = BlackSilver;
13892         else selection = (ChessSquare)((int)piece + 1);
13893         goto defaultlabel;
13894
13895       case WhiteQueen:
13896       case BlackQueen:
13897         if(gameInfo.variant == VariantShatranj ||
13898            gameInfo.variant == VariantXiangqi  ||
13899            gameInfo.variant == VariantCourier  ||
13900            gameInfo.variant == VariantMakruk     )
13901             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13902         goto defaultlabel;
13903
13904       case WhiteKing:
13905       case BlackKing:
13906         if(gameInfo.variant == VariantXiangqi)
13907             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13908         if(gameInfo.variant == VariantKnightmate)
13909             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13910       default:
13911         defaultlabel:
13912         if (gameMode == IcsExamining) {
13913             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13914             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13915                      PieceToChar(selection), AAA + x, ONE + y);
13916             SendToICS(buf);
13917         } else {
13918             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13919                 int n;
13920                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13921                     n = PieceToNumber(selection - BlackPawn);
13922                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13923                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13924                     boards[0][BOARD_HEIGHT-1-n][1]++;
13925                 } else
13926                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13927                     n = PieceToNumber(selection);
13928                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13929                     boards[0][n][BOARD_WIDTH-1] = selection;
13930                     boards[0][n][BOARD_WIDTH-2]++;
13931                 }
13932             } else
13933             boards[0][y][x] = selection;
13934             DrawPosition(TRUE, boards[0]);
13935             ClearHighlights();
13936             fromX = fromY = -1;
13937         }
13938         break;
13939     }
13940 }
13941
13942
13943 void
13944 DropMenuEvent (ChessSquare selection, int x, int y)
13945 {
13946     ChessMove moveType;
13947
13948     switch (gameMode) {
13949       case IcsPlayingWhite:
13950       case MachinePlaysBlack:
13951         if (!WhiteOnMove(currentMove)) {
13952             DisplayMoveError(_("It is Black's turn"));
13953             return;
13954         }
13955         moveType = WhiteDrop;
13956         break;
13957       case IcsPlayingBlack:
13958       case MachinePlaysWhite:
13959         if (WhiteOnMove(currentMove)) {
13960             DisplayMoveError(_("It is White's turn"));
13961             return;
13962         }
13963         moveType = BlackDrop;
13964         break;
13965       case EditGame:
13966         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13967         break;
13968       default:
13969         return;
13970     }
13971
13972     if (moveType == BlackDrop && selection < BlackPawn) {
13973       selection = (ChessSquare) ((int) selection
13974                                  + (int) BlackPawn - (int) WhitePawn);
13975     }
13976     if (boards[currentMove][y][x] != EmptySquare) {
13977         DisplayMoveError(_("That square is occupied"));
13978         return;
13979     }
13980
13981     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13982 }
13983
13984 void
13985 AcceptEvent ()
13986 {
13987     /* Accept a pending offer of any kind from opponent */
13988
13989     if (appData.icsActive) {
13990         SendToICS(ics_prefix);
13991         SendToICS("accept\n");
13992     } else if (cmailMsgLoaded) {
13993         if (currentMove == cmailOldMove &&
13994             commentList[cmailOldMove] != NULL &&
13995             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13996                    "Black offers a draw" : "White offers a draw")) {
13997             TruncateGame();
13998             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13999             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14000         } else {
14001             DisplayError(_("There is no pending offer on this move"), 0);
14002             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14003         }
14004     } else {
14005         /* Not used for offers from chess program */
14006     }
14007 }
14008
14009 void
14010 DeclineEvent ()
14011 {
14012     /* Decline a pending offer of any kind from opponent */
14013
14014     if (appData.icsActive) {
14015         SendToICS(ics_prefix);
14016         SendToICS("decline\n");
14017     } else if (cmailMsgLoaded) {
14018         if (currentMove == cmailOldMove &&
14019             commentList[cmailOldMove] != NULL &&
14020             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14021                    "Black offers a draw" : "White offers a draw")) {
14022 #ifdef NOTDEF
14023             AppendComment(cmailOldMove, "Draw declined", TRUE);
14024             DisplayComment(cmailOldMove - 1, "Draw declined");
14025 #endif /*NOTDEF*/
14026         } else {
14027             DisplayError(_("There is no pending offer on this move"), 0);
14028         }
14029     } else {
14030         /* Not used for offers from chess program */
14031     }
14032 }
14033
14034 void
14035 RematchEvent ()
14036 {
14037     /* Issue ICS rematch command */
14038     if (appData.icsActive) {
14039         SendToICS(ics_prefix);
14040         SendToICS("rematch\n");
14041     }
14042 }
14043
14044 void
14045 CallFlagEvent ()
14046 {
14047     /* Call your opponent's flag (claim a win on time) */
14048     if (appData.icsActive) {
14049         SendToICS(ics_prefix);
14050         SendToICS("flag\n");
14051     } else {
14052         switch (gameMode) {
14053           default:
14054             return;
14055           case MachinePlaysWhite:
14056             if (whiteFlag) {
14057                 if (blackFlag)
14058                   GameEnds(GameIsDrawn, "Both players ran out of time",
14059                            GE_PLAYER);
14060                 else
14061                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14062             } else {
14063                 DisplayError(_("Your opponent is not out of time"), 0);
14064             }
14065             break;
14066           case MachinePlaysBlack:
14067             if (blackFlag) {
14068                 if (whiteFlag)
14069                   GameEnds(GameIsDrawn, "Both players ran out of time",
14070                            GE_PLAYER);
14071                 else
14072                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14073             } else {
14074                 DisplayError(_("Your opponent is not out of time"), 0);
14075             }
14076             break;
14077         }
14078     }
14079 }
14080
14081 void
14082 ClockClick (int which)
14083 {       // [HGM] code moved to back-end from winboard.c
14084         if(which) { // black clock
14085           if (gameMode == EditPosition || gameMode == IcsExamining) {
14086             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14087             SetBlackToPlayEvent();
14088           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14089           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14090           } else if (shiftKey) {
14091             AdjustClock(which, -1);
14092           } else if (gameMode == IcsPlayingWhite ||
14093                      gameMode == MachinePlaysBlack) {
14094             CallFlagEvent();
14095           }
14096         } else { // white clock
14097           if (gameMode == EditPosition || gameMode == IcsExamining) {
14098             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14099             SetWhiteToPlayEvent();
14100           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14101           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14102           } else if (shiftKey) {
14103             AdjustClock(which, -1);
14104           } else if (gameMode == IcsPlayingBlack ||
14105                    gameMode == MachinePlaysWhite) {
14106             CallFlagEvent();
14107           }
14108         }
14109 }
14110
14111 void
14112 DrawEvent ()
14113 {
14114     /* Offer draw or accept pending draw offer from opponent */
14115
14116     if (appData.icsActive) {
14117         /* Note: tournament rules require draw offers to be
14118            made after you make your move but before you punch
14119            your clock.  Currently ICS doesn't let you do that;
14120            instead, you immediately punch your clock after making
14121            a move, but you can offer a draw at any time. */
14122
14123         SendToICS(ics_prefix);
14124         SendToICS("draw\n");
14125         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14126     } else if (cmailMsgLoaded) {
14127         if (currentMove == cmailOldMove &&
14128             commentList[cmailOldMove] != NULL &&
14129             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14130                    "Black offers a draw" : "White offers a draw")) {
14131             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14133         } else if (currentMove == cmailOldMove + 1) {
14134             char *offer = WhiteOnMove(cmailOldMove) ?
14135               "White offers a draw" : "Black offers a draw";
14136             AppendComment(currentMove, offer, TRUE);
14137             DisplayComment(currentMove - 1, offer);
14138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14139         } else {
14140             DisplayError(_("You must make your move before offering a draw"), 0);
14141             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14142         }
14143     } else if (first.offeredDraw) {
14144         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14145     } else {
14146         if (first.sendDrawOffers) {
14147             SendToProgram("draw\n", &first);
14148             userOfferedDraw = TRUE;
14149         }
14150     }
14151 }
14152
14153 void
14154 AdjournEvent ()
14155 {
14156     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14157
14158     if (appData.icsActive) {
14159         SendToICS(ics_prefix);
14160         SendToICS("adjourn\n");
14161     } else {
14162         /* Currently GNU Chess doesn't offer or accept Adjourns */
14163     }
14164 }
14165
14166
14167 void
14168 AbortEvent ()
14169 {
14170     /* Offer Abort or accept pending Abort offer from opponent */
14171
14172     if (appData.icsActive) {
14173         SendToICS(ics_prefix);
14174         SendToICS("abort\n");
14175     } else {
14176         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14177     }
14178 }
14179
14180 void
14181 ResignEvent ()
14182 {
14183     /* Resign.  You can do this even if it's not your turn. */
14184
14185     if (appData.icsActive) {
14186         SendToICS(ics_prefix);
14187         SendToICS("resign\n");
14188     } else {
14189         switch (gameMode) {
14190           case MachinePlaysWhite:
14191             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14192             break;
14193           case MachinePlaysBlack:
14194             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14195             break;
14196           case EditGame:
14197             if (cmailMsgLoaded) {
14198                 TruncateGame();
14199                 if (WhiteOnMove(cmailOldMove)) {
14200                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14201                 } else {
14202                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14203                 }
14204                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14205             }
14206             break;
14207           default:
14208             break;
14209         }
14210     }
14211 }
14212
14213
14214 void
14215 StopObservingEvent ()
14216 {
14217     /* Stop observing current games */
14218     SendToICS(ics_prefix);
14219     SendToICS("unobserve\n");
14220 }
14221
14222 void
14223 StopExaminingEvent ()
14224 {
14225     /* Stop observing current game */
14226     SendToICS(ics_prefix);
14227     SendToICS("unexamine\n");
14228 }
14229
14230 void
14231 ForwardInner (int target)
14232 {
14233     int limit; int oldSeekGraphUp = seekGraphUp;
14234
14235     if (appData.debugMode)
14236         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14237                 target, currentMove, forwardMostMove);
14238
14239     if (gameMode == EditPosition)
14240       return;
14241
14242     seekGraphUp = FALSE;
14243     MarkTargetSquares(1);
14244
14245     if (gameMode == PlayFromGameFile && !pausing)
14246       PauseEvent();
14247
14248     if (gameMode == IcsExamining && pausing)
14249       limit = pauseExamForwardMostMove;
14250     else
14251       limit = forwardMostMove;
14252
14253     if (target > limit) target = limit;
14254
14255     if (target > 0 && moveList[target - 1][0]) {
14256         int fromX, fromY, toX, toY;
14257         toX = moveList[target - 1][2] - AAA;
14258         toY = moveList[target - 1][3] - ONE;
14259         if (moveList[target - 1][1] == '@') {
14260             if (appData.highlightLastMove) {
14261                 SetHighlights(-1, -1, toX, toY);
14262             }
14263         } else {
14264             fromX = moveList[target - 1][0] - AAA;
14265             fromY = moveList[target - 1][1] - ONE;
14266             if (target == currentMove + 1) {
14267                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14268             }
14269             if (appData.highlightLastMove) {
14270                 SetHighlights(fromX, fromY, toX, toY);
14271             }
14272         }
14273     }
14274     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14275         gameMode == Training || gameMode == PlayFromGameFile ||
14276         gameMode == AnalyzeFile) {
14277         while (currentMove < target) {
14278             SendMoveToProgram(currentMove++, &first);
14279         }
14280     } else {
14281         currentMove = target;
14282     }
14283
14284     if (gameMode == EditGame || gameMode == EndOfGame) {
14285         whiteTimeRemaining = timeRemaining[0][currentMove];
14286         blackTimeRemaining = timeRemaining[1][currentMove];
14287     }
14288     DisplayBothClocks();
14289     DisplayMove(currentMove - 1);
14290     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14291     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14292     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14293         DisplayComment(currentMove - 1, commentList[currentMove]);
14294     }
14295 }
14296
14297
14298 void
14299 ForwardEvent ()
14300 {
14301     if (gameMode == IcsExamining && !pausing) {
14302         SendToICS(ics_prefix);
14303         SendToICS("forward\n");
14304     } else {
14305         ForwardInner(currentMove + 1);
14306     }
14307 }
14308
14309 void
14310 ToEndEvent ()
14311 {
14312     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14313         /* to optimze, we temporarily turn off analysis mode while we feed
14314          * the remaining moves to the engine. Otherwise we get analysis output
14315          * after each move.
14316          */
14317         if (first.analysisSupport) {
14318           SendToProgram("exit\nforce\n", &first);
14319           first.analyzing = FALSE;
14320         }
14321     }
14322
14323     if (gameMode == IcsExamining && !pausing) {
14324         SendToICS(ics_prefix);
14325         SendToICS("forward 999999\n");
14326     } else {
14327         ForwardInner(forwardMostMove);
14328     }
14329
14330     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14331         /* we have fed all the moves, so reactivate analysis mode */
14332         SendToProgram("analyze\n", &first);
14333         first.analyzing = TRUE;
14334         /*first.maybeThinking = TRUE;*/
14335         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14336     }
14337 }
14338
14339 void
14340 BackwardInner (int target)
14341 {
14342     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14343
14344     if (appData.debugMode)
14345         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14346                 target, currentMove, forwardMostMove);
14347
14348     if (gameMode == EditPosition) return;
14349     seekGraphUp = FALSE;
14350     MarkTargetSquares(1);
14351     if (currentMove <= backwardMostMove) {
14352         ClearHighlights();
14353         DrawPosition(full_redraw, boards[currentMove]);
14354         return;
14355     }
14356     if (gameMode == PlayFromGameFile && !pausing)
14357       PauseEvent();
14358
14359     if (moveList[target][0]) {
14360         int fromX, fromY, toX, toY;
14361         toX = moveList[target][2] - AAA;
14362         toY = moveList[target][3] - ONE;
14363         if (moveList[target][1] == '@') {
14364             if (appData.highlightLastMove) {
14365                 SetHighlights(-1, -1, toX, toY);
14366             }
14367         } else {
14368             fromX = moveList[target][0] - AAA;
14369             fromY = moveList[target][1] - ONE;
14370             if (target == currentMove - 1) {
14371                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14372             }
14373             if (appData.highlightLastMove) {
14374                 SetHighlights(fromX, fromY, toX, toY);
14375             }
14376         }
14377     }
14378     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14379         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14380         while (currentMove > target) {
14381             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14382                 // null move cannot be undone. Reload program with move history before it.
14383                 int i;
14384                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14385                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14386                 }
14387                 SendBoard(&first, i); 
14388                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14389                 break;
14390             }
14391             SendToProgram("undo\n", &first);
14392             currentMove--;
14393         }
14394     } else {
14395         currentMove = target;
14396     }
14397
14398     if (gameMode == EditGame || gameMode == EndOfGame) {
14399         whiteTimeRemaining = timeRemaining[0][currentMove];
14400         blackTimeRemaining = timeRemaining[1][currentMove];
14401     }
14402     DisplayBothClocks();
14403     DisplayMove(currentMove - 1);
14404     DrawPosition(full_redraw, boards[currentMove]);
14405     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14406     // [HGM] PV info: routine tests if comment empty
14407     DisplayComment(currentMove - 1, commentList[currentMove]);
14408 }
14409
14410 void
14411 BackwardEvent ()
14412 {
14413     if (gameMode == IcsExamining && !pausing) {
14414         SendToICS(ics_prefix);
14415         SendToICS("backward\n");
14416     } else {
14417         BackwardInner(currentMove - 1);
14418     }
14419 }
14420
14421 void
14422 ToStartEvent ()
14423 {
14424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14425         /* to optimize, we temporarily turn off analysis mode while we undo
14426          * all the moves. Otherwise we get analysis output after each undo.
14427          */
14428         if (first.analysisSupport) {
14429           SendToProgram("exit\nforce\n", &first);
14430           first.analyzing = FALSE;
14431         }
14432     }
14433
14434     if (gameMode == IcsExamining && !pausing) {
14435         SendToICS(ics_prefix);
14436         SendToICS("backward 999999\n");
14437     } else {
14438         BackwardInner(backwardMostMove);
14439     }
14440
14441     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14442         /* we have fed all the moves, so reactivate analysis mode */
14443         SendToProgram("analyze\n", &first);
14444         first.analyzing = TRUE;
14445         /*first.maybeThinking = TRUE;*/
14446         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14447     }
14448 }
14449
14450 void
14451 ToNrEvent (int to)
14452 {
14453   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14454   if (to >= forwardMostMove) to = forwardMostMove;
14455   if (to <= backwardMostMove) to = backwardMostMove;
14456   if (to < currentMove) {
14457     BackwardInner(to);
14458   } else {
14459     ForwardInner(to);
14460   }
14461 }
14462
14463 void
14464 RevertEvent (Boolean annotate)
14465 {
14466     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14467         return;
14468     }
14469     if (gameMode != IcsExamining) {
14470         DisplayError(_("You are not examining a game"), 0);
14471         return;
14472     }
14473     if (pausing) {
14474         DisplayError(_("You can't revert while pausing"), 0);
14475         return;
14476     }
14477     SendToICS(ics_prefix);
14478     SendToICS("revert\n");
14479 }
14480
14481 void
14482 RetractMoveEvent ()
14483 {
14484     switch (gameMode) {
14485       case MachinePlaysWhite:
14486       case MachinePlaysBlack:
14487         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14488             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14489             return;
14490         }
14491         if (forwardMostMove < 2) return;
14492         currentMove = forwardMostMove = forwardMostMove - 2;
14493         whiteTimeRemaining = timeRemaining[0][currentMove];
14494         blackTimeRemaining = timeRemaining[1][currentMove];
14495         DisplayBothClocks();
14496         DisplayMove(currentMove - 1);
14497         ClearHighlights();/*!! could figure this out*/
14498         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14499         SendToProgram("remove\n", &first);
14500         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14501         break;
14502
14503       case BeginningOfGame:
14504       default:
14505         break;
14506
14507       case IcsPlayingWhite:
14508       case IcsPlayingBlack:
14509         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14510             SendToICS(ics_prefix);
14511             SendToICS("takeback 2\n");
14512         } else {
14513             SendToICS(ics_prefix);
14514             SendToICS("takeback 1\n");
14515         }
14516         break;
14517     }
14518 }
14519
14520 void
14521 MoveNowEvent ()
14522 {
14523     ChessProgramState *cps;
14524
14525     switch (gameMode) {
14526       case MachinePlaysWhite:
14527         if (!WhiteOnMove(forwardMostMove)) {
14528             DisplayError(_("It is your turn"), 0);
14529             return;
14530         }
14531         cps = &first;
14532         break;
14533       case MachinePlaysBlack:
14534         if (WhiteOnMove(forwardMostMove)) {
14535             DisplayError(_("It is your turn"), 0);
14536             return;
14537         }
14538         cps = &first;
14539         break;
14540       case TwoMachinesPlay:
14541         if (WhiteOnMove(forwardMostMove) ==
14542             (first.twoMachinesColor[0] == 'w')) {
14543             cps = &first;
14544         } else {
14545             cps = &second;
14546         }
14547         break;
14548       case BeginningOfGame:
14549       default:
14550         return;
14551     }
14552     SendToProgram("?\n", cps);
14553 }
14554
14555 void
14556 TruncateGameEvent ()
14557 {
14558     EditGameEvent();
14559     if (gameMode != EditGame) return;
14560     TruncateGame();
14561 }
14562
14563 void
14564 TruncateGame ()
14565 {
14566     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14567     if (forwardMostMove > currentMove) {
14568         if (gameInfo.resultDetails != NULL) {
14569             free(gameInfo.resultDetails);
14570             gameInfo.resultDetails = NULL;
14571             gameInfo.result = GameUnfinished;
14572         }
14573         forwardMostMove = currentMove;
14574         HistorySet(parseList, backwardMostMove, forwardMostMove,
14575                    currentMove-1);
14576     }
14577 }
14578
14579 void
14580 HintEvent ()
14581 {
14582     if (appData.noChessProgram) return;
14583     switch (gameMode) {
14584       case MachinePlaysWhite:
14585         if (WhiteOnMove(forwardMostMove)) {
14586             DisplayError(_("Wait until your turn"), 0);
14587             return;
14588         }
14589         break;
14590       case BeginningOfGame:
14591       case MachinePlaysBlack:
14592         if (!WhiteOnMove(forwardMostMove)) {
14593             DisplayError(_("Wait until your turn"), 0);
14594             return;
14595         }
14596         break;
14597       default:
14598         DisplayError(_("No hint available"), 0);
14599         return;
14600     }
14601     SendToProgram("hint\n", &first);
14602     hintRequested = TRUE;
14603 }
14604
14605 void
14606 BookEvent ()
14607 {
14608     if (appData.noChessProgram) return;
14609     switch (gameMode) {
14610       case MachinePlaysWhite:
14611         if (WhiteOnMove(forwardMostMove)) {
14612             DisplayError(_("Wait until your turn"), 0);
14613             return;
14614         }
14615         break;
14616       case BeginningOfGame:
14617       case MachinePlaysBlack:
14618         if (!WhiteOnMove(forwardMostMove)) {
14619             DisplayError(_("Wait until your turn"), 0);
14620             return;
14621         }
14622         break;
14623       case EditPosition:
14624         EditPositionDone(TRUE);
14625         break;
14626       case TwoMachinesPlay:
14627         return;
14628       default:
14629         break;
14630     }
14631     SendToProgram("bk\n", &first);
14632     bookOutput[0] = NULLCHAR;
14633     bookRequested = TRUE;
14634 }
14635
14636 void
14637 AboutGameEvent ()
14638 {
14639     char *tags = PGNTags(&gameInfo);
14640     TagsPopUp(tags, CmailMsg());
14641     free(tags);
14642 }
14643
14644 /* end button procedures */
14645
14646 void
14647 PrintPosition (FILE *fp, int move)
14648 {
14649     int i, j;
14650
14651     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14652         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14653             char c = PieceToChar(boards[move][i][j]);
14654             fputc(c == 'x' ? '.' : c, fp);
14655             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14656         }
14657     }
14658     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14659       fprintf(fp, "white to play\n");
14660     else
14661       fprintf(fp, "black to play\n");
14662 }
14663
14664 void
14665 PrintOpponents (FILE *fp)
14666 {
14667     if (gameInfo.white != NULL) {
14668         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14669     } else {
14670         fprintf(fp, "\n");
14671     }
14672 }
14673
14674 /* Find last component of program's own name, using some heuristics */
14675 void
14676 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14677 {
14678     char *p, *q, c;
14679     int local = (strcmp(host, "localhost") == 0);
14680     while (!local && (p = strchr(prog, ';')) != NULL) {
14681         p++;
14682         while (*p == ' ') p++;
14683         prog = p;
14684     }
14685     if (*prog == '"' || *prog == '\'') {
14686         q = strchr(prog + 1, *prog);
14687     } else {
14688         q = strchr(prog, ' ');
14689     }
14690     if (q == NULL) q = prog + strlen(prog);
14691     p = q;
14692     while (p >= prog && *p != '/' && *p != '\\') p--;
14693     p++;
14694     if(p == prog && *p == '"') p++;
14695     c = *q; *q = 0;
14696     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14697     memcpy(buf, p, q - p);
14698     buf[q - p] = NULLCHAR;
14699     if (!local) {
14700         strcat(buf, "@");
14701         strcat(buf, host);
14702     }
14703 }
14704
14705 char *
14706 TimeControlTagValue ()
14707 {
14708     char buf[MSG_SIZ];
14709     if (!appData.clockMode) {
14710       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14711     } else if (movesPerSession > 0) {
14712       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14713     } else if (timeIncrement == 0) {
14714       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14715     } else {
14716       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14717     }
14718     return StrSave(buf);
14719 }
14720
14721 void
14722 SetGameInfo ()
14723 {
14724     /* This routine is used only for certain modes */
14725     VariantClass v = gameInfo.variant;
14726     ChessMove r = GameUnfinished;
14727     char *p = NULL;
14728
14729     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14730         r = gameInfo.result;
14731         p = gameInfo.resultDetails;
14732         gameInfo.resultDetails = NULL;
14733     }
14734     ClearGameInfo(&gameInfo);
14735     gameInfo.variant = v;
14736
14737     switch (gameMode) {
14738       case MachinePlaysWhite:
14739         gameInfo.event = StrSave( appData.pgnEventHeader );
14740         gameInfo.site = StrSave(HostName());
14741         gameInfo.date = PGNDate();
14742         gameInfo.round = StrSave("-");
14743         gameInfo.white = StrSave(first.tidy);
14744         gameInfo.black = StrSave(UserName());
14745         gameInfo.timeControl = TimeControlTagValue();
14746         break;
14747
14748       case MachinePlaysBlack:
14749         gameInfo.event = StrSave( appData.pgnEventHeader );
14750         gameInfo.site = StrSave(HostName());
14751         gameInfo.date = PGNDate();
14752         gameInfo.round = StrSave("-");
14753         gameInfo.white = StrSave(UserName());
14754         gameInfo.black = StrSave(first.tidy);
14755         gameInfo.timeControl = TimeControlTagValue();
14756         break;
14757
14758       case TwoMachinesPlay:
14759         gameInfo.event = StrSave( appData.pgnEventHeader );
14760         gameInfo.site = StrSave(HostName());
14761         gameInfo.date = PGNDate();
14762         if (roundNr > 0) {
14763             char buf[MSG_SIZ];
14764             snprintf(buf, MSG_SIZ, "%d", roundNr);
14765             gameInfo.round = StrSave(buf);
14766         } else {
14767             gameInfo.round = StrSave("-");
14768         }
14769         if (first.twoMachinesColor[0] == 'w') {
14770             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14771             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14772         } else {
14773             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14774             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14775         }
14776         gameInfo.timeControl = TimeControlTagValue();
14777         break;
14778
14779       case EditGame:
14780         gameInfo.event = StrSave("Edited game");
14781         gameInfo.site = StrSave(HostName());
14782         gameInfo.date = PGNDate();
14783         gameInfo.round = StrSave("-");
14784         gameInfo.white = StrSave("-");
14785         gameInfo.black = StrSave("-");
14786         gameInfo.result = r;
14787         gameInfo.resultDetails = p;
14788         break;
14789
14790       case EditPosition:
14791         gameInfo.event = StrSave("Edited position");
14792         gameInfo.site = StrSave(HostName());
14793         gameInfo.date = PGNDate();
14794         gameInfo.round = StrSave("-");
14795         gameInfo.white = StrSave("-");
14796         gameInfo.black = StrSave("-");
14797         break;
14798
14799       case IcsPlayingWhite:
14800       case IcsPlayingBlack:
14801       case IcsObserving:
14802       case IcsExamining:
14803         break;
14804
14805       case PlayFromGameFile:
14806         gameInfo.event = StrSave("Game from non-PGN file");
14807         gameInfo.site = StrSave(HostName());
14808         gameInfo.date = PGNDate();
14809         gameInfo.round = StrSave("-");
14810         gameInfo.white = StrSave("?");
14811         gameInfo.black = StrSave("?");
14812         break;
14813
14814       default:
14815         break;
14816     }
14817 }
14818
14819 void
14820 ReplaceComment (int index, char *text)
14821 {
14822     int len;
14823     char *p;
14824     float score;
14825
14826     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14827        pvInfoList[index-1].depth == len &&
14828        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14829        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14830     while (*text == '\n') text++;
14831     len = strlen(text);
14832     while (len > 0 && text[len - 1] == '\n') len--;
14833
14834     if (commentList[index] != NULL)
14835       free(commentList[index]);
14836
14837     if (len == 0) {
14838         commentList[index] = NULL;
14839         return;
14840     }
14841   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14842       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14843       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14844     commentList[index] = (char *) malloc(len + 2);
14845     strncpy(commentList[index], text, len);
14846     commentList[index][len] = '\n';
14847     commentList[index][len + 1] = NULLCHAR;
14848   } else {
14849     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14850     char *p;
14851     commentList[index] = (char *) malloc(len + 7);
14852     safeStrCpy(commentList[index], "{\n", 3);
14853     safeStrCpy(commentList[index]+2, text, len+1);
14854     commentList[index][len+2] = NULLCHAR;
14855     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14856     strcat(commentList[index], "\n}\n");
14857   }
14858 }
14859
14860 void
14861 CrushCRs (char *text)
14862 {
14863   char *p = text;
14864   char *q = text;
14865   char ch;
14866
14867   do {
14868     ch = *p++;
14869     if (ch == '\r') continue;
14870     *q++ = ch;
14871   } while (ch != '\0');
14872 }
14873
14874 void
14875 AppendComment (int index, char *text, Boolean addBraces)
14876 /* addBraces  tells if we should add {} */
14877 {
14878     int oldlen, len;
14879     char *old;
14880
14881 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14882     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14883
14884     CrushCRs(text);
14885     while (*text == '\n') text++;
14886     len = strlen(text);
14887     while (len > 0 && text[len - 1] == '\n') len--;
14888     text[len] = NULLCHAR;
14889
14890     if (len == 0) return;
14891
14892     if (commentList[index] != NULL) {
14893       Boolean addClosingBrace = addBraces;
14894         old = commentList[index];
14895         oldlen = strlen(old);
14896         while(commentList[index][oldlen-1] ==  '\n')
14897           commentList[index][--oldlen] = NULLCHAR;
14898         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14899         safeStrCpy(commentList[index], old, oldlen + len + 6);
14900         free(old);
14901         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14902         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14903           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14904           while (*text == '\n') { text++; len--; }
14905           commentList[index][--oldlen] = NULLCHAR;
14906       }
14907         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14908         else          strcat(commentList[index], "\n");
14909         strcat(commentList[index], text);
14910         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14911         else          strcat(commentList[index], "\n");
14912     } else {
14913         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14914         if(addBraces)
14915           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14916         else commentList[index][0] = NULLCHAR;
14917         strcat(commentList[index], text);
14918         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14919         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14920     }
14921 }
14922
14923 static char *
14924 FindStr (char * text, char * sub_text)
14925 {
14926     char * result = strstr( text, sub_text );
14927
14928     if( result != NULL ) {
14929         result += strlen( sub_text );
14930     }
14931
14932     return result;
14933 }
14934
14935 /* [AS] Try to extract PV info from PGN comment */
14936 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14937 char *
14938 GetInfoFromComment (int index, char * text)
14939 {
14940     char * sep = text, *p;
14941
14942     if( text != NULL && index > 0 ) {
14943         int score = 0;
14944         int depth = 0;
14945         int time = -1, sec = 0, deci;
14946         char * s_eval = FindStr( text, "[%eval " );
14947         char * s_emt = FindStr( text, "[%emt " );
14948
14949         if( s_eval != NULL || s_emt != NULL ) {
14950             /* New style */
14951             char delim;
14952
14953             if( s_eval != NULL ) {
14954                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14955                     return text;
14956                 }
14957
14958                 if( delim != ']' ) {
14959                     return text;
14960                 }
14961             }
14962
14963             if( s_emt != NULL ) {
14964             }
14965                 return text;
14966         }
14967         else {
14968             /* We expect something like: [+|-]nnn.nn/dd */
14969             int score_lo = 0;
14970
14971             if(*text != '{') return text; // [HGM] braces: must be normal comment
14972
14973             sep = strchr( text, '/' );
14974             if( sep == NULL || sep < (text+4) ) {
14975                 return text;
14976             }
14977
14978             p = text;
14979             if(p[1] == '(') { // comment starts with PV
14980                p = strchr(p, ')'); // locate end of PV
14981                if(p == NULL || sep < p+5) return text;
14982                // at this point we have something like "{(.*) +0.23/6 ..."
14983                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14984                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14985                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14986             }
14987             time = -1; sec = -1; deci = -1;
14988             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14989                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14990                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14991                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14992                 return text;
14993             }
14994
14995             if( score_lo < 0 || score_lo >= 100 ) {
14996                 return text;
14997             }
14998
14999             if(sec >= 0) time = 600*time + 10*sec; else
15000             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15001
15002             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15003
15004             /* [HGM] PV time: now locate end of PV info */
15005             while( *++sep >= '0' && *sep <= '9'); // strip depth
15006             if(time >= 0)
15007             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15008             if(sec >= 0)
15009             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15010             if(deci >= 0)
15011             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15012             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15013         }
15014
15015         if( depth <= 0 ) {
15016             return text;
15017         }
15018
15019         if( time < 0 ) {
15020             time = -1;
15021         }
15022
15023         pvInfoList[index-1].depth = depth;
15024         pvInfoList[index-1].score = score;
15025         pvInfoList[index-1].time  = 10*time; // centi-sec
15026         if(*sep == '}') *sep = 0; else *--sep = '{';
15027         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15028     }
15029     return sep;
15030 }
15031
15032 void
15033 SendToProgram (char *message, ChessProgramState *cps)
15034 {
15035     int count, outCount, error;
15036     char buf[MSG_SIZ];
15037
15038     if (cps->pr == NoProc) return;
15039     Attention(cps);
15040
15041     if (appData.debugMode) {
15042         TimeMark now;
15043         GetTimeMark(&now);
15044         fprintf(debugFP, "%ld >%-6s: %s",
15045                 SubtractTimeMarks(&now, &programStartTime),
15046                 cps->which, message);
15047         if(serverFP)
15048             fprintf(serverFP, "%ld >%-6s: %s",
15049                 SubtractTimeMarks(&now, &programStartTime),
15050                 cps->which, message), fflush(serverFP);
15051     }
15052
15053     count = strlen(message);
15054     outCount = OutputToProcess(cps->pr, message, count, &error);
15055     if (outCount < count && !exiting
15056                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15057       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15058       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15060             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15061                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15062                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15063                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15064             } else {
15065                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15066                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15067                 gameInfo.result = res;
15068             }
15069             gameInfo.resultDetails = StrSave(buf);
15070         }
15071         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15072         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15073     }
15074 }
15075
15076 void
15077 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15078 {
15079     char *end_str;
15080     char buf[MSG_SIZ];
15081     ChessProgramState *cps = (ChessProgramState *)closure;
15082
15083     if (isr != cps->isr) return; /* Killed intentionally */
15084     if (count <= 0) {
15085         if (count == 0) {
15086             RemoveInputSource(cps->isr);
15087             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15088             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15089                     _(cps->which), cps->program);
15090         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15091                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15092                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15093                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15094                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15095                 } else {
15096                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15097                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15098                     gameInfo.result = res;
15099                 }
15100                 gameInfo.resultDetails = StrSave(buf);
15101             }
15102             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15103             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15104         } else {
15105             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15106                     _(cps->which), cps->program);
15107             RemoveInputSource(cps->isr);
15108
15109             /* [AS] Program is misbehaving badly... kill it */
15110             if( count == -2 ) {
15111                 DestroyChildProcess( cps->pr, 9 );
15112                 cps->pr = NoProc;
15113             }
15114
15115             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15116         }
15117         return;
15118     }
15119
15120     if ((end_str = strchr(message, '\r')) != NULL)
15121       *end_str = NULLCHAR;
15122     if ((end_str = strchr(message, '\n')) != NULL)
15123       *end_str = NULLCHAR;
15124
15125     if (appData.debugMode) {
15126         TimeMark now; int print = 1;
15127         char *quote = ""; char c; int i;
15128
15129         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15130                 char start = message[0];
15131                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15132                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15133                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15134                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15135                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15136                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15137                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15138                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15139                    sscanf(message, "hint: %c", &c)!=1 && 
15140                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15141                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15142                     print = (appData.engineComments >= 2);
15143                 }
15144                 message[0] = start; // restore original message
15145         }
15146         if(print) {
15147                 GetTimeMark(&now);
15148                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15149                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15150                         quote,
15151                         message);
15152                 if(serverFP)
15153                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15154                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15155                         quote,
15156                         message), fflush(serverFP);
15157         }
15158     }
15159
15160     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15161     if (appData.icsEngineAnalyze) {
15162         if (strstr(message, "whisper") != NULL ||
15163              strstr(message, "kibitz") != NULL ||
15164             strstr(message, "tellics") != NULL) return;
15165     }
15166
15167     HandleMachineMove(message, cps);
15168 }
15169
15170
15171 void
15172 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15173 {
15174     char buf[MSG_SIZ];
15175     int seconds;
15176
15177     if( timeControl_2 > 0 ) {
15178         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15179             tc = timeControl_2;
15180         }
15181     }
15182     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15183     inc /= cps->timeOdds;
15184     st  /= cps->timeOdds;
15185
15186     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15187
15188     if (st > 0) {
15189       /* Set exact time per move, normally using st command */
15190       if (cps->stKludge) {
15191         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15192         seconds = st % 60;
15193         if (seconds == 0) {
15194           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15195         } else {
15196           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15197         }
15198       } else {
15199         snprintf(buf, MSG_SIZ, "st %d\n", st);
15200       }
15201     } else {
15202       /* Set conventional or incremental time control, using level command */
15203       if (seconds == 0) {
15204         /* Note old gnuchess bug -- minutes:seconds used to not work.
15205            Fixed in later versions, but still avoid :seconds
15206            when seconds is 0. */
15207         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15208       } else {
15209         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15210                  seconds, inc/1000.);
15211       }
15212     }
15213     SendToProgram(buf, cps);
15214
15215     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15216     /* Orthogonally, limit search to given depth */
15217     if (sd > 0) {
15218       if (cps->sdKludge) {
15219         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15220       } else {
15221         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15222       }
15223       SendToProgram(buf, cps);
15224     }
15225
15226     if(cps->nps >= 0) { /* [HGM] nps */
15227         if(cps->supportsNPS == FALSE)
15228           cps->nps = -1; // don't use if engine explicitly says not supported!
15229         else {
15230           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15231           SendToProgram(buf, cps);
15232         }
15233     }
15234 }
15235
15236 ChessProgramState *
15237 WhitePlayer ()
15238 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15239 {
15240     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15241        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15242         return &second;
15243     return &first;
15244 }
15245
15246 void
15247 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15248 {
15249     char message[MSG_SIZ];
15250     long time, otime;
15251
15252     /* Note: this routine must be called when the clocks are stopped
15253        or when they have *just* been set or switched; otherwise
15254        it will be off by the time since the current tick started.
15255     */
15256     if (machineWhite) {
15257         time = whiteTimeRemaining / 10;
15258         otime = blackTimeRemaining / 10;
15259     } else {
15260         time = blackTimeRemaining / 10;
15261         otime = whiteTimeRemaining / 10;
15262     }
15263     /* [HGM] translate opponent's time by time-odds factor */
15264     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15265
15266     if (time <= 0) time = 1;
15267     if (otime <= 0) otime = 1;
15268
15269     snprintf(message, MSG_SIZ, "time %ld\n", time);
15270     SendToProgram(message, cps);
15271
15272     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15273     SendToProgram(message, cps);
15274 }
15275
15276 int
15277 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15278 {
15279   char buf[MSG_SIZ];
15280   int len = strlen(name);
15281   int val;
15282
15283   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15284     (*p) += len + 1;
15285     sscanf(*p, "%d", &val);
15286     *loc = (val != 0);
15287     while (**p && **p != ' ')
15288       (*p)++;
15289     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15290     SendToProgram(buf, cps);
15291     return TRUE;
15292   }
15293   return FALSE;
15294 }
15295
15296 int
15297 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15298 {
15299   char buf[MSG_SIZ];
15300   int len = strlen(name);
15301   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15302     (*p) += len + 1;
15303     sscanf(*p, "%d", loc);
15304     while (**p && **p != ' ') (*p)++;
15305     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15306     SendToProgram(buf, cps);
15307     return TRUE;
15308   }
15309   return FALSE;
15310 }
15311
15312 int
15313 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15314 {
15315   char buf[MSG_SIZ];
15316   int len = strlen(name);
15317   if (strncmp((*p), name, len) == 0
15318       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15319     (*p) += len + 2;
15320     sscanf(*p, "%[^\"]", loc);
15321     while (**p && **p != '\"') (*p)++;
15322     if (**p == '\"') (*p)++;
15323     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15324     SendToProgram(buf, cps);
15325     return TRUE;
15326   }
15327   return FALSE;
15328 }
15329
15330 int
15331 ParseOption (Option *opt, ChessProgramState *cps)
15332 // [HGM] options: process the string that defines an engine option, and determine
15333 // name, type, default value, and allowed value range
15334 {
15335         char *p, *q, buf[MSG_SIZ];
15336         int n, min = (-1)<<31, max = 1<<31, def;
15337
15338         if(p = strstr(opt->name, " -spin ")) {
15339             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15340             if(max < min) max = min; // enforce consistency
15341             if(def < min) def = min;
15342             if(def > max) def = max;
15343             opt->value = def;
15344             opt->min = min;
15345             opt->max = max;
15346             opt->type = Spin;
15347         } else if((p = strstr(opt->name, " -slider "))) {
15348             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15349             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15350             if(max < min) max = min; // enforce consistency
15351             if(def < min) def = min;
15352             if(def > max) def = max;
15353             opt->value = def;
15354             opt->min = min;
15355             opt->max = max;
15356             opt->type = Spin; // Slider;
15357         } else if((p = strstr(opt->name, " -string "))) {
15358             opt->textValue = p+9;
15359             opt->type = TextBox;
15360         } else if((p = strstr(opt->name, " -file "))) {
15361             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15362             opt->textValue = p+7;
15363             opt->type = FileName; // FileName;
15364         } else if((p = strstr(opt->name, " -path "))) {
15365             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15366             opt->textValue = p+7;
15367             opt->type = PathName; // PathName;
15368         } else if(p = strstr(opt->name, " -check ")) {
15369             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15370             opt->value = (def != 0);
15371             opt->type = CheckBox;
15372         } else if(p = strstr(opt->name, " -combo ")) {
15373             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15374             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15375             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15376             opt->value = n = 0;
15377             while(q = StrStr(q, " /// ")) {
15378                 n++; *q = 0;    // count choices, and null-terminate each of them
15379                 q += 5;
15380                 if(*q == '*') { // remember default, which is marked with * prefix
15381                     q++;
15382                     opt->value = n;
15383                 }
15384                 cps->comboList[cps->comboCnt++] = q;
15385             }
15386             cps->comboList[cps->comboCnt++] = NULL;
15387             opt->max = n + 1;
15388             opt->type = ComboBox;
15389         } else if(p = strstr(opt->name, " -button")) {
15390             opt->type = Button;
15391         } else if(p = strstr(opt->name, " -save")) {
15392             opt->type = SaveButton;
15393         } else return FALSE;
15394         *p = 0; // terminate option name
15395         // now look if the command-line options define a setting for this engine option.
15396         if(cps->optionSettings && cps->optionSettings[0])
15397             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15398         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15399           snprintf(buf, MSG_SIZ, "option %s", p);
15400                 if(p = strstr(buf, ",")) *p = 0;
15401                 if(q = strchr(buf, '=')) switch(opt->type) {
15402                     case ComboBox:
15403                         for(n=0; n<opt->max; n++)
15404                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15405                         break;
15406                     case TextBox:
15407                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15408                         break;
15409                     case Spin:
15410                     case CheckBox:
15411                         opt->value = atoi(q+1);
15412                     default:
15413                         break;
15414                 }
15415                 strcat(buf, "\n");
15416                 SendToProgram(buf, cps);
15417         }
15418         return TRUE;
15419 }
15420
15421 void
15422 FeatureDone (ChessProgramState *cps, int val)
15423 {
15424   DelayedEventCallback cb = GetDelayedEvent();
15425   if ((cb == InitBackEnd3 && cps == &first) ||
15426       (cb == SettingsMenuIfReady && cps == &second) ||
15427       (cb == LoadEngine) ||
15428       (cb == TwoMachinesEventIfReady)) {
15429     CancelDelayedEvent();
15430     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15431   }
15432   cps->initDone = val;
15433 }
15434
15435 /* Parse feature command from engine */
15436 void
15437 ParseFeatures (char *args, ChessProgramState *cps)
15438 {
15439   char *p = args;
15440   char *q;
15441   int val;
15442   char buf[MSG_SIZ];
15443
15444   for (;;) {
15445     while (*p == ' ') p++;
15446     if (*p == NULLCHAR) return;
15447
15448     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15449     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15450     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15451     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15452     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15453     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15454     if (BoolFeature(&p, "reuse", &val, cps)) {
15455       /* Engine can disable reuse, but can't enable it if user said no */
15456       if (!val) cps->reuse = FALSE;
15457       continue;
15458     }
15459     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15460     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15461       if (gameMode == TwoMachinesPlay) {
15462         DisplayTwoMachinesTitle();
15463       } else {
15464         DisplayTitle("");
15465       }
15466       continue;
15467     }
15468     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15469     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15470     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15471     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15472     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15473     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15474     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15475     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15476     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15477     if (IntFeature(&p, "done", &val, cps)) {
15478       FeatureDone(cps, val);
15479       continue;
15480     }
15481     /* Added by Tord: */
15482     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15483     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15484     /* End of additions by Tord */
15485
15486     /* [HGM] added features: */
15487     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15488     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15489     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15490     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15491     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15492     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15493     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15494         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15495           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15496             SendToProgram(buf, cps);
15497             continue;
15498         }
15499         if(cps->nrOptions >= MAX_OPTIONS) {
15500             cps->nrOptions--;
15501             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15502             DisplayError(buf, 0);
15503         }
15504         continue;
15505     }
15506     /* End of additions by HGM */
15507
15508     /* unknown feature: complain and skip */
15509     q = p;
15510     while (*q && *q != '=') q++;
15511     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15512     SendToProgram(buf, cps);
15513     p = q;
15514     if (*p == '=') {
15515       p++;
15516       if (*p == '\"') {
15517         p++;
15518         while (*p && *p != '\"') p++;
15519         if (*p == '\"') p++;
15520       } else {
15521         while (*p && *p != ' ') p++;
15522       }
15523     }
15524   }
15525
15526 }
15527
15528 void
15529 PeriodicUpdatesEvent (int newState)
15530 {
15531     if (newState == appData.periodicUpdates)
15532       return;
15533
15534     appData.periodicUpdates=newState;
15535
15536     /* Display type changes, so update it now */
15537 //    DisplayAnalysis();
15538
15539     /* Get the ball rolling again... */
15540     if (newState) {
15541         AnalysisPeriodicEvent(1);
15542         StartAnalysisClock();
15543     }
15544 }
15545
15546 void
15547 PonderNextMoveEvent (int newState)
15548 {
15549     if (newState == appData.ponderNextMove) return;
15550     if (gameMode == EditPosition) EditPositionDone(TRUE);
15551     if (newState) {
15552         SendToProgram("hard\n", &first);
15553         if (gameMode == TwoMachinesPlay) {
15554             SendToProgram("hard\n", &second);
15555         }
15556     } else {
15557         SendToProgram("easy\n", &first);
15558         thinkOutput[0] = NULLCHAR;
15559         if (gameMode == TwoMachinesPlay) {
15560             SendToProgram("easy\n", &second);
15561         }
15562     }
15563     appData.ponderNextMove = newState;
15564 }
15565
15566 void
15567 NewSettingEvent (int option, int *feature, char *command, int value)
15568 {
15569     char buf[MSG_SIZ];
15570
15571     if (gameMode == EditPosition) EditPositionDone(TRUE);
15572     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15573     if(feature == NULL || *feature) SendToProgram(buf, &first);
15574     if (gameMode == TwoMachinesPlay) {
15575         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15576     }
15577 }
15578
15579 void
15580 ShowThinkingEvent ()
15581 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15582 {
15583     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15584     int newState = appData.showThinking
15585         // [HGM] thinking: other features now need thinking output as well
15586         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15587
15588     if (oldState == newState) return;
15589     oldState = newState;
15590     if (gameMode == EditPosition) EditPositionDone(TRUE);
15591     if (oldState) {
15592         SendToProgram("post\n", &first);
15593         if (gameMode == TwoMachinesPlay) {
15594             SendToProgram("post\n", &second);
15595         }
15596     } else {
15597         SendToProgram("nopost\n", &first);
15598         thinkOutput[0] = NULLCHAR;
15599         if (gameMode == TwoMachinesPlay) {
15600             SendToProgram("nopost\n", &second);
15601         }
15602     }
15603 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15604 }
15605
15606 void
15607 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15608 {
15609   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15610   if (pr == NoProc) return;
15611   AskQuestion(title, question, replyPrefix, pr);
15612 }
15613
15614 void
15615 TypeInEvent (char firstChar)
15616 {
15617     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15618         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15619         gameMode == AnalyzeMode || gameMode == EditGame || 
15620         gameMode == EditPosition || gameMode == IcsExamining ||
15621         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15622         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15623                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15624                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15625         gameMode == Training) PopUpMoveDialog(firstChar);
15626 }
15627
15628 void
15629 TypeInDoneEvent (char *move)
15630 {
15631         Board board;
15632         int n, fromX, fromY, toX, toY;
15633         char promoChar;
15634         ChessMove moveType;
15635
15636         // [HGM] FENedit
15637         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15638                 EditPositionPasteFEN(move);
15639                 return;
15640         }
15641         // [HGM] movenum: allow move number to be typed in any mode
15642         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15643           ToNrEvent(2*n-1);
15644           return;
15645         }
15646         // undocumented kludge: allow command-line option to be typed in!
15647         // (potentially fatal, and does not implement the effect of the option.)
15648         // should only be used for options that are values on which future decisions will be made,
15649         // and definitely not on options that would be used during initialization.
15650         if(strstr(move, "!!! -") == move) {
15651             ParseArgsFromString(move+4);
15652             return;
15653         }
15654
15655       if (gameMode != EditGame && currentMove != forwardMostMove && 
15656         gameMode != Training) {
15657         DisplayMoveError(_("Displayed move is not current"));
15658       } else {
15659         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15660           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15661         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15662         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15663           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15664           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15665         } else {
15666           DisplayMoveError(_("Could not parse move"));
15667         }
15668       }
15669 }
15670
15671 void
15672 DisplayMove (int moveNumber)
15673 {
15674     char message[MSG_SIZ];
15675     char res[MSG_SIZ];
15676     char cpThinkOutput[MSG_SIZ];
15677
15678     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15679
15680     if (moveNumber == forwardMostMove - 1 ||
15681         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15682
15683         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15684
15685         if (strchr(cpThinkOutput, '\n')) {
15686             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15687         }
15688     } else {
15689         *cpThinkOutput = NULLCHAR;
15690     }
15691
15692     /* [AS] Hide thinking from human user */
15693     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15694         *cpThinkOutput = NULLCHAR;
15695         if( thinkOutput[0] != NULLCHAR ) {
15696             int i;
15697
15698             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15699                 cpThinkOutput[i] = '.';
15700             }
15701             cpThinkOutput[i] = NULLCHAR;
15702             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15703         }
15704     }
15705
15706     if (moveNumber == forwardMostMove - 1 &&
15707         gameInfo.resultDetails != NULL) {
15708         if (gameInfo.resultDetails[0] == NULLCHAR) {
15709           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15710         } else {
15711           snprintf(res, MSG_SIZ, " {%s} %s",
15712                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15713         }
15714     } else {
15715         res[0] = NULLCHAR;
15716     }
15717
15718     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15719         DisplayMessage(res, cpThinkOutput);
15720     } else {
15721       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15722                 WhiteOnMove(moveNumber) ? " " : ".. ",
15723                 parseList[moveNumber], res);
15724         DisplayMessage(message, cpThinkOutput);
15725     }
15726 }
15727
15728 void
15729 DisplayComment (int moveNumber, char *text)
15730 {
15731     char title[MSG_SIZ];
15732
15733     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15734       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15735     } else {
15736       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15737               WhiteOnMove(moveNumber) ? " " : ".. ",
15738               parseList[moveNumber]);
15739     }
15740     if (text != NULL && (appData.autoDisplayComment || commentUp))
15741         CommentPopUp(title, text);
15742 }
15743
15744 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15745  * might be busy thinking or pondering.  It can be omitted if your
15746  * gnuchess is configured to stop thinking immediately on any user
15747  * input.  However, that gnuchess feature depends on the FIONREAD
15748  * ioctl, which does not work properly on some flavors of Unix.
15749  */
15750 void
15751 Attention (ChessProgramState *cps)
15752 {
15753 #if ATTENTION
15754     if (!cps->useSigint) return;
15755     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15756     switch (gameMode) {
15757       case MachinePlaysWhite:
15758       case MachinePlaysBlack:
15759       case TwoMachinesPlay:
15760       case IcsPlayingWhite:
15761       case IcsPlayingBlack:
15762       case AnalyzeMode:
15763       case AnalyzeFile:
15764         /* Skip if we know it isn't thinking */
15765         if (!cps->maybeThinking) return;
15766         if (appData.debugMode)
15767           fprintf(debugFP, "Interrupting %s\n", cps->which);
15768         InterruptChildProcess(cps->pr);
15769         cps->maybeThinking = FALSE;
15770         break;
15771       default:
15772         break;
15773     }
15774 #endif /*ATTENTION*/
15775 }
15776
15777 int
15778 CheckFlags ()
15779 {
15780     if (whiteTimeRemaining <= 0) {
15781         if (!whiteFlag) {
15782             whiteFlag = TRUE;
15783             if (appData.icsActive) {
15784                 if (appData.autoCallFlag &&
15785                     gameMode == IcsPlayingBlack && !blackFlag) {
15786                   SendToICS(ics_prefix);
15787                   SendToICS("flag\n");
15788                 }
15789             } else {
15790                 if (blackFlag) {
15791                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15792                 } else {
15793                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15794                     if (appData.autoCallFlag) {
15795                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15796                         return TRUE;
15797                     }
15798                 }
15799             }
15800         }
15801     }
15802     if (blackTimeRemaining <= 0) {
15803         if (!blackFlag) {
15804             blackFlag = TRUE;
15805             if (appData.icsActive) {
15806                 if (appData.autoCallFlag &&
15807                     gameMode == IcsPlayingWhite && !whiteFlag) {
15808                   SendToICS(ics_prefix);
15809                   SendToICS("flag\n");
15810                 }
15811             } else {
15812                 if (whiteFlag) {
15813                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15814                 } else {
15815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15816                     if (appData.autoCallFlag) {
15817                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15818                         return TRUE;
15819                     }
15820                 }
15821             }
15822         }
15823     }
15824     return FALSE;
15825 }
15826
15827 void
15828 CheckTimeControl ()
15829 {
15830     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15831         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15832
15833     /*
15834      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15835      */
15836     if ( !WhiteOnMove(forwardMostMove) ) {
15837         /* White made time control */
15838         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15839         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15840         /* [HGM] time odds: correct new time quota for time odds! */
15841                                             / WhitePlayer()->timeOdds;
15842         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15843     } else {
15844         lastBlack -= blackTimeRemaining;
15845         /* Black made time control */
15846         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15847                                             / WhitePlayer()->other->timeOdds;
15848         lastWhite = whiteTimeRemaining;
15849     }
15850 }
15851
15852 void
15853 DisplayBothClocks ()
15854 {
15855     int wom = gameMode == EditPosition ?
15856       !blackPlaysFirst : WhiteOnMove(currentMove);
15857     DisplayWhiteClock(whiteTimeRemaining, wom);
15858     DisplayBlackClock(blackTimeRemaining, !wom);
15859 }
15860
15861
15862 /* Timekeeping seems to be a portability nightmare.  I think everyone
15863    has ftime(), but I'm really not sure, so I'm including some ifdefs
15864    to use other calls if you don't.  Clocks will be less accurate if
15865    you have neither ftime nor gettimeofday.
15866 */
15867
15868 /* VS 2008 requires the #include outside of the function */
15869 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15870 #include <sys/timeb.h>
15871 #endif
15872
15873 /* Get the current time as a TimeMark */
15874 void
15875 GetTimeMark (TimeMark *tm)
15876 {
15877 #if HAVE_GETTIMEOFDAY
15878
15879     struct timeval timeVal;
15880     struct timezone timeZone;
15881
15882     gettimeofday(&timeVal, &timeZone);
15883     tm->sec = (long) timeVal.tv_sec;
15884     tm->ms = (int) (timeVal.tv_usec / 1000L);
15885
15886 #else /*!HAVE_GETTIMEOFDAY*/
15887 #if HAVE_FTIME
15888
15889 // include <sys/timeb.h> / moved to just above start of function
15890     struct timeb timeB;
15891
15892     ftime(&timeB);
15893     tm->sec = (long) timeB.time;
15894     tm->ms = (int) timeB.millitm;
15895
15896 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15897     tm->sec = (long) time(NULL);
15898     tm->ms = 0;
15899 #endif
15900 #endif
15901 }
15902
15903 /* Return the difference in milliseconds between two
15904    time marks.  We assume the difference will fit in a long!
15905 */
15906 long
15907 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15908 {
15909     return 1000L*(tm2->sec - tm1->sec) +
15910            (long) (tm2->ms - tm1->ms);
15911 }
15912
15913
15914 /*
15915  * Code to manage the game clocks.
15916  *
15917  * In tournament play, black starts the clock and then white makes a move.
15918  * We give the human user a slight advantage if he is playing white---the
15919  * clocks don't run until he makes his first move, so it takes zero time.
15920  * Also, we don't account for network lag, so we could get out of sync
15921  * with GNU Chess's clock -- but then, referees are always right.
15922  */
15923
15924 static TimeMark tickStartTM;
15925 static long intendedTickLength;
15926
15927 long
15928 NextTickLength (long timeRemaining)
15929 {
15930     long nominalTickLength, nextTickLength;
15931
15932     if (timeRemaining > 0L && timeRemaining <= 10000L)
15933       nominalTickLength = 100L;
15934     else
15935       nominalTickLength = 1000L;
15936     nextTickLength = timeRemaining % nominalTickLength;
15937     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15938
15939     return nextTickLength;
15940 }
15941
15942 /* Adjust clock one minute up or down */
15943 void
15944 AdjustClock (Boolean which, int dir)
15945 {
15946     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15947     if(which) blackTimeRemaining += 60000*dir;
15948     else      whiteTimeRemaining += 60000*dir;
15949     DisplayBothClocks();
15950     adjustedClock = TRUE;
15951 }
15952
15953 /* Stop clocks and reset to a fresh time control */
15954 void
15955 ResetClocks ()
15956 {
15957     (void) StopClockTimer();
15958     if (appData.icsActive) {
15959         whiteTimeRemaining = blackTimeRemaining = 0;
15960     } else if (searchTime) {
15961         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15962         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15963     } else { /* [HGM] correct new time quote for time odds */
15964         whiteTC = blackTC = fullTimeControlString;
15965         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15966         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15967     }
15968     if (whiteFlag || blackFlag) {
15969         DisplayTitle("");
15970         whiteFlag = blackFlag = FALSE;
15971     }
15972     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15973     DisplayBothClocks();
15974     adjustedClock = FALSE;
15975 }
15976
15977 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15978
15979 /* Decrement running clock by amount of time that has passed */
15980 void
15981 DecrementClocks ()
15982 {
15983     long timeRemaining;
15984     long lastTickLength, fudge;
15985     TimeMark now;
15986
15987     if (!appData.clockMode) return;
15988     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15989
15990     GetTimeMark(&now);
15991
15992     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15993
15994     /* Fudge if we woke up a little too soon */
15995     fudge = intendedTickLength - lastTickLength;
15996     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15997
15998     if (WhiteOnMove(forwardMostMove)) {
15999         if(whiteNPS >= 0) lastTickLength = 0;
16000         timeRemaining = whiteTimeRemaining -= lastTickLength;
16001         if(timeRemaining < 0 && !appData.icsActive) {
16002             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16003             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16004                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16005                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16006             }
16007         }
16008         DisplayWhiteClock(whiteTimeRemaining - fudge,
16009                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16010     } else {
16011         if(blackNPS >= 0) lastTickLength = 0;
16012         timeRemaining = blackTimeRemaining -= lastTickLength;
16013         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16014             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16015             if(suddenDeath) {
16016                 blackStartMove = forwardMostMove;
16017                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16018             }
16019         }
16020         DisplayBlackClock(blackTimeRemaining - fudge,
16021                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16022     }
16023     if (CheckFlags()) return;
16024
16025     tickStartTM = now;
16026     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16027     StartClockTimer(intendedTickLength);
16028
16029     /* if the time remaining has fallen below the alarm threshold, sound the
16030      * alarm. if the alarm has sounded and (due to a takeback or time control
16031      * with increment) the time remaining has increased to a level above the
16032      * threshold, reset the alarm so it can sound again.
16033      */
16034
16035     if (appData.icsActive && appData.icsAlarm) {
16036
16037         /* make sure we are dealing with the user's clock */
16038         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16039                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16040            )) return;
16041
16042         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16043             alarmSounded = FALSE;
16044         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16045             PlayAlarmSound();
16046             alarmSounded = TRUE;
16047         }
16048     }
16049 }
16050
16051
16052 /* A player has just moved, so stop the previously running
16053    clock and (if in clock mode) start the other one.
16054    We redisplay both clocks in case we're in ICS mode, because
16055    ICS gives us an update to both clocks after every move.
16056    Note that this routine is called *after* forwardMostMove
16057    is updated, so the last fractional tick must be subtracted
16058    from the color that is *not* on move now.
16059 */
16060 void
16061 SwitchClocks (int newMoveNr)
16062 {
16063     long lastTickLength;
16064     TimeMark now;
16065     int flagged = FALSE;
16066
16067     GetTimeMark(&now);
16068
16069     if (StopClockTimer() && appData.clockMode) {
16070         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16071         if (!WhiteOnMove(forwardMostMove)) {
16072             if(blackNPS >= 0) lastTickLength = 0;
16073             blackTimeRemaining -= lastTickLength;
16074            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16075 //         if(pvInfoList[forwardMostMove].time == -1)
16076                  pvInfoList[forwardMostMove].time =               // use GUI time
16077                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16078         } else {
16079            if(whiteNPS >= 0) lastTickLength = 0;
16080            whiteTimeRemaining -= lastTickLength;
16081            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16082 //         if(pvInfoList[forwardMostMove].time == -1)
16083                  pvInfoList[forwardMostMove].time =
16084                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16085         }
16086         flagged = CheckFlags();
16087     }
16088     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16089     CheckTimeControl();
16090
16091     if (flagged || !appData.clockMode) return;
16092
16093     switch (gameMode) {
16094       case MachinePlaysBlack:
16095       case MachinePlaysWhite:
16096       case BeginningOfGame:
16097         if (pausing) return;
16098         break;
16099
16100       case EditGame:
16101       case PlayFromGameFile:
16102       case IcsExamining:
16103         return;
16104
16105       default:
16106         break;
16107     }
16108
16109     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16110         if(WhiteOnMove(forwardMostMove))
16111              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16112         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16113     }
16114
16115     tickStartTM = now;
16116     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16117       whiteTimeRemaining : blackTimeRemaining);
16118     StartClockTimer(intendedTickLength);
16119 }
16120
16121
16122 /* Stop both clocks */
16123 void
16124 StopClocks ()
16125 {
16126     long lastTickLength;
16127     TimeMark now;
16128
16129     if (!StopClockTimer()) return;
16130     if (!appData.clockMode) return;
16131
16132     GetTimeMark(&now);
16133
16134     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16135     if (WhiteOnMove(forwardMostMove)) {
16136         if(whiteNPS >= 0) lastTickLength = 0;
16137         whiteTimeRemaining -= lastTickLength;
16138         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16139     } else {
16140         if(blackNPS >= 0) lastTickLength = 0;
16141         blackTimeRemaining -= lastTickLength;
16142         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16143     }
16144     CheckFlags();
16145 }
16146
16147 /* Start clock of player on move.  Time may have been reset, so
16148    if clock is already running, stop and restart it. */
16149 void
16150 StartClocks ()
16151 {
16152     (void) StopClockTimer(); /* in case it was running already */
16153     DisplayBothClocks();
16154     if (CheckFlags()) return;
16155
16156     if (!appData.clockMode) return;
16157     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16158
16159     GetTimeMark(&tickStartTM);
16160     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16161       whiteTimeRemaining : blackTimeRemaining);
16162
16163    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16164     whiteNPS = blackNPS = -1;
16165     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16166        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16167         whiteNPS = first.nps;
16168     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16169        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16170         blackNPS = first.nps;
16171     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16172         whiteNPS = second.nps;
16173     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16174         blackNPS = second.nps;
16175     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16176
16177     StartClockTimer(intendedTickLength);
16178 }
16179
16180 char *
16181 TimeString (long ms)
16182 {
16183     long second, minute, hour, day;
16184     char *sign = "";
16185     static char buf[32];
16186
16187     if (ms > 0 && ms <= 9900) {
16188       /* convert milliseconds to tenths, rounding up */
16189       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16190
16191       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16192       return buf;
16193     }
16194
16195     /* convert milliseconds to seconds, rounding up */
16196     /* use floating point to avoid strangeness of integer division
16197        with negative dividends on many machines */
16198     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16199
16200     if (second < 0) {
16201         sign = "-";
16202         second = -second;
16203     }
16204
16205     day = second / (60 * 60 * 24);
16206     second = second % (60 * 60 * 24);
16207     hour = second / (60 * 60);
16208     second = second % (60 * 60);
16209     minute = second / 60;
16210     second = second % 60;
16211
16212     if (day > 0)
16213       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16214               sign, day, hour, minute, second);
16215     else if (hour > 0)
16216       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16217     else
16218       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16219
16220     return buf;
16221 }
16222
16223
16224 /*
16225  * This is necessary because some C libraries aren't ANSI C compliant yet.
16226  */
16227 char *
16228 StrStr (char *string, char *match)
16229 {
16230     int i, length;
16231
16232     length = strlen(match);
16233
16234     for (i = strlen(string) - length; i >= 0; i--, string++)
16235       if (!strncmp(match, string, length))
16236         return string;
16237
16238     return NULL;
16239 }
16240
16241 char *
16242 StrCaseStr (char *string, char *match)
16243 {
16244     int i, j, length;
16245
16246     length = strlen(match);
16247
16248     for (i = strlen(string) - length; i >= 0; i--, string++) {
16249         for (j = 0; j < length; j++) {
16250             if (ToLower(match[j]) != ToLower(string[j]))
16251               break;
16252         }
16253         if (j == length) return string;
16254     }
16255
16256     return NULL;
16257 }
16258
16259 #ifndef _amigados
16260 int
16261 StrCaseCmp (char *s1, char *s2)
16262 {
16263     char c1, c2;
16264
16265     for (;;) {
16266         c1 = ToLower(*s1++);
16267         c2 = ToLower(*s2++);
16268         if (c1 > c2) return 1;
16269         if (c1 < c2) return -1;
16270         if (c1 == NULLCHAR) return 0;
16271     }
16272 }
16273
16274
16275 int
16276 ToLower (int c)
16277 {
16278     return isupper(c) ? tolower(c) : c;
16279 }
16280
16281
16282 int
16283 ToUpper (int c)
16284 {
16285     return islower(c) ? toupper(c) : c;
16286 }
16287 #endif /* !_amigados    */
16288
16289 char *
16290 StrSave (char *s)
16291 {
16292   char *ret;
16293
16294   if ((ret = (char *) malloc(strlen(s) + 1)))
16295     {
16296       safeStrCpy(ret, s, strlen(s)+1);
16297     }
16298   return ret;
16299 }
16300
16301 char *
16302 StrSavePtr (char *s, char **savePtr)
16303 {
16304     if (*savePtr) {
16305         free(*savePtr);
16306     }
16307     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16308       safeStrCpy(*savePtr, s, strlen(s)+1);
16309     }
16310     return(*savePtr);
16311 }
16312
16313 char *
16314 PGNDate ()
16315 {
16316     time_t clock;
16317     struct tm *tm;
16318     char buf[MSG_SIZ];
16319
16320     clock = time((time_t *)NULL);
16321     tm = localtime(&clock);
16322     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16323             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16324     return StrSave(buf);
16325 }
16326
16327
16328 char *
16329 PositionToFEN (int move, char *overrideCastling)
16330 {
16331     int i, j, fromX, fromY, toX, toY;
16332     int whiteToPlay;
16333     char buf[MSG_SIZ];
16334     char *p, *q;
16335     int emptycount;
16336     ChessSquare piece;
16337
16338     whiteToPlay = (gameMode == EditPosition) ?
16339       !blackPlaysFirst : (move % 2 == 0);
16340     p = buf;
16341
16342     /* Piece placement data */
16343     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16344         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16345         emptycount = 0;
16346         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16347             if (boards[move][i][j] == EmptySquare) {
16348                 emptycount++;
16349             } else { ChessSquare piece = boards[move][i][j];
16350                 if (emptycount > 0) {
16351                     if(emptycount<10) /* [HGM] can be >= 10 */
16352                         *p++ = '0' + emptycount;
16353                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16354                     emptycount = 0;
16355                 }
16356                 if(PieceToChar(piece) == '+') {
16357                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16358                     *p++ = '+';
16359                     piece = (ChessSquare)(DEMOTED piece);
16360                 }
16361                 *p++ = PieceToChar(piece);
16362                 if(p[-1] == '~') {
16363                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16364                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16365                     *p++ = '~';
16366                 }
16367             }
16368         }
16369         if (emptycount > 0) {
16370             if(emptycount<10) /* [HGM] can be >= 10 */
16371                 *p++ = '0' + emptycount;
16372             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16373             emptycount = 0;
16374         }
16375         *p++ = '/';
16376     }
16377     *(p - 1) = ' ';
16378
16379     /* [HGM] print Crazyhouse or Shogi holdings */
16380     if( gameInfo.holdingsWidth ) {
16381         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16382         q = p;
16383         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16384             piece = boards[move][i][BOARD_WIDTH-1];
16385             if( piece != EmptySquare )
16386               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16387                   *p++ = PieceToChar(piece);
16388         }
16389         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16390             piece = boards[move][BOARD_HEIGHT-i-1][0];
16391             if( piece != EmptySquare )
16392               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16393                   *p++ = PieceToChar(piece);
16394         }
16395
16396         if( q == p ) *p++ = '-';
16397         *p++ = ']';
16398         *p++ = ' ';
16399     }
16400
16401     /* Active color */
16402     *p++ = whiteToPlay ? 'w' : 'b';
16403     *p++ = ' ';
16404
16405   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16406     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16407   } else {
16408   if(nrCastlingRights) {
16409      q = p;
16410      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16411        /* [HGM] write directly from rights */
16412            if(boards[move][CASTLING][2] != NoRights &&
16413               boards[move][CASTLING][0] != NoRights   )
16414                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16415            if(boards[move][CASTLING][2] != NoRights &&
16416               boards[move][CASTLING][1] != NoRights   )
16417                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16418            if(boards[move][CASTLING][5] != NoRights &&
16419               boards[move][CASTLING][3] != NoRights   )
16420                 *p++ = boards[move][CASTLING][3] + AAA;
16421            if(boards[move][CASTLING][5] != NoRights &&
16422               boards[move][CASTLING][4] != NoRights   )
16423                 *p++ = boards[move][CASTLING][4] + AAA;
16424      } else {
16425
16426         /* [HGM] write true castling rights */
16427         if( nrCastlingRights == 6 ) {
16428             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16429                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16430             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16431                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16432             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16433                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16434             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16435                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16436         }
16437      }
16438      if (q == p) *p++ = '-'; /* No castling rights */
16439      *p++ = ' ';
16440   }
16441
16442   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16443      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16444     /* En passant target square */
16445     if (move > backwardMostMove) {
16446         fromX = moveList[move - 1][0] - AAA;
16447         fromY = moveList[move - 1][1] - ONE;
16448         toX = moveList[move - 1][2] - AAA;
16449         toY = moveList[move - 1][3] - ONE;
16450         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16451             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16452             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16453             fromX == toX) {
16454             /* 2-square pawn move just happened */
16455             *p++ = toX + AAA;
16456             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16457         } else {
16458             *p++ = '-';
16459         }
16460     } else if(move == backwardMostMove) {
16461         // [HGM] perhaps we should always do it like this, and forget the above?
16462         if((signed char)boards[move][EP_STATUS] >= 0) {
16463             *p++ = boards[move][EP_STATUS] + AAA;
16464             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16465         } else {
16466             *p++ = '-';
16467         }
16468     } else {
16469         *p++ = '-';
16470     }
16471     *p++ = ' ';
16472   }
16473   }
16474
16475     /* [HGM] find reversible plies */
16476     {   int i = 0, j=move;
16477
16478         if (appData.debugMode) { int k;
16479             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16480             for(k=backwardMostMove; k<=forwardMostMove; k++)
16481                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16482
16483         }
16484
16485         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16486         if( j == backwardMostMove ) i += initialRulePlies;
16487         sprintf(p, "%d ", i);
16488         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16489     }
16490     /* Fullmove number */
16491     sprintf(p, "%d", (move / 2) + 1);
16492
16493     return StrSave(buf);
16494 }
16495
16496 Boolean
16497 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16498 {
16499     int i, j;
16500     char *p, c;
16501     int emptycount;
16502     ChessSquare piece;
16503
16504     p = fen;
16505
16506     /* [HGM] by default clear Crazyhouse holdings, if present */
16507     if(gameInfo.holdingsWidth) {
16508        for(i=0; i<BOARD_HEIGHT; i++) {
16509            board[i][0]             = EmptySquare; /* black holdings */
16510            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16511            board[i][1]             = (ChessSquare) 0; /* black counts */
16512            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16513        }
16514     }
16515
16516     /* Piece placement data */
16517     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16518         j = 0;
16519         for (;;) {
16520             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16521                 if (*p == '/') p++;
16522                 emptycount = gameInfo.boardWidth - j;
16523                 while (emptycount--)
16524                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16525                 break;
16526 #if(BOARD_FILES >= 10)
16527             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16528                 p++; emptycount=10;
16529                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16530                 while (emptycount--)
16531                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16532 #endif
16533             } else if (isdigit(*p)) {
16534                 emptycount = *p++ - '0';
16535                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16536                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16537                 while (emptycount--)
16538                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16539             } else if (*p == '+' || isalpha(*p)) {
16540                 if (j >= gameInfo.boardWidth) return FALSE;
16541                 if(*p=='+') {
16542                     piece = CharToPiece(*++p);
16543                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16544                     piece = (ChessSquare) (PROMOTED piece ); p++;
16545                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16546                 } else piece = CharToPiece(*p++);
16547
16548                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16549                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16550                     piece = (ChessSquare) (PROMOTED piece);
16551                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16552                     p++;
16553                 }
16554                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16555             } else {
16556                 return FALSE;
16557             }
16558         }
16559     }
16560     while (*p == '/' || *p == ' ') p++;
16561
16562     /* [HGM] look for Crazyhouse holdings here */
16563     while(*p==' ') p++;
16564     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16565         if(*p == '[') p++;
16566         if(*p == '-' ) p++; /* empty holdings */ else {
16567             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16568             /* if we would allow FEN reading to set board size, we would   */
16569             /* have to add holdings and shift the board read so far here   */
16570             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16571                 p++;
16572                 if((int) piece >= (int) BlackPawn ) {
16573                     i = (int)piece - (int)BlackPawn;
16574                     i = PieceToNumber((ChessSquare)i);
16575                     if( i >= gameInfo.holdingsSize ) return FALSE;
16576                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16577                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16578                 } else {
16579                     i = (int)piece - (int)WhitePawn;
16580                     i = PieceToNumber((ChessSquare)i);
16581                     if( i >= gameInfo.holdingsSize ) return FALSE;
16582                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16583                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16584                 }
16585             }
16586         }
16587         if(*p == ']') p++;
16588     }
16589
16590     while(*p == ' ') p++;
16591
16592     /* Active color */
16593     c = *p++;
16594     if(appData.colorNickNames) {
16595       if( c == appData.colorNickNames[0] ) c = 'w'; else
16596       if( c == appData.colorNickNames[1] ) c = 'b';
16597     }
16598     switch (c) {
16599       case 'w':
16600         *blackPlaysFirst = FALSE;
16601         break;
16602       case 'b':
16603         *blackPlaysFirst = TRUE;
16604         break;
16605       default:
16606         return FALSE;
16607     }
16608
16609     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16610     /* return the extra info in global variiables             */
16611
16612     /* set defaults in case FEN is incomplete */
16613     board[EP_STATUS] = EP_UNKNOWN;
16614     for(i=0; i<nrCastlingRights; i++ ) {
16615         board[CASTLING][i] =
16616             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16617     }   /* assume possible unless obviously impossible */
16618     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16619     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16620     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16621                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16622     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16623     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16624     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16625                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16626     FENrulePlies = 0;
16627
16628     while(*p==' ') p++;
16629     if(nrCastlingRights) {
16630       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16631           /* castling indicator present, so default becomes no castlings */
16632           for(i=0; i<nrCastlingRights; i++ ) {
16633                  board[CASTLING][i] = NoRights;
16634           }
16635       }
16636       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16637              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16638              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16639              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16640         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16641
16642         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16643             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16644             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16645         }
16646         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16647             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16648         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16649                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16650         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16651                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16652         switch(c) {
16653           case'K':
16654               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16655               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16656               board[CASTLING][2] = whiteKingFile;
16657               break;
16658           case'Q':
16659               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16660               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16661               board[CASTLING][2] = whiteKingFile;
16662               break;
16663           case'k':
16664               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16665               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16666               board[CASTLING][5] = blackKingFile;
16667               break;
16668           case'q':
16669               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16670               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16671               board[CASTLING][5] = blackKingFile;
16672           case '-':
16673               break;
16674           default: /* FRC castlings */
16675               if(c >= 'a') { /* black rights */
16676                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16677                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16678                   if(i == BOARD_RGHT) break;
16679                   board[CASTLING][5] = i;
16680                   c -= AAA;
16681                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16682                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16683                   if(c > i)
16684                       board[CASTLING][3] = c;
16685                   else
16686                       board[CASTLING][4] = c;
16687               } else { /* white rights */
16688                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16689                     if(board[0][i] == WhiteKing) break;
16690                   if(i == BOARD_RGHT) break;
16691                   board[CASTLING][2] = i;
16692                   c -= AAA - 'a' + 'A';
16693                   if(board[0][c] >= WhiteKing) break;
16694                   if(c > i)
16695                       board[CASTLING][0] = c;
16696                   else
16697                       board[CASTLING][1] = c;
16698               }
16699         }
16700       }
16701       for(i=0; i<nrCastlingRights; i++)
16702         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16703     if (appData.debugMode) {
16704         fprintf(debugFP, "FEN castling rights:");
16705         for(i=0; i<nrCastlingRights; i++)
16706         fprintf(debugFP, " %d", board[CASTLING][i]);
16707         fprintf(debugFP, "\n");
16708     }
16709
16710       while(*p==' ') p++;
16711     }
16712
16713     /* read e.p. field in games that know e.p. capture */
16714     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16715        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16716       if(*p=='-') {
16717         p++; board[EP_STATUS] = EP_NONE;
16718       } else {
16719          char c = *p++ - AAA;
16720
16721          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16722          if(*p >= '0' && *p <='9') p++;
16723          board[EP_STATUS] = c;
16724       }
16725     }
16726
16727
16728     if(sscanf(p, "%d", &i) == 1) {
16729         FENrulePlies = i; /* 50-move ply counter */
16730         /* (The move number is still ignored)    */
16731     }
16732
16733     return TRUE;
16734 }
16735
16736 void
16737 EditPositionPasteFEN (char *fen)
16738 {
16739   if (fen != NULL) {
16740     Board initial_position;
16741
16742     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16743       DisplayError(_("Bad FEN position in clipboard"), 0);
16744       return ;
16745     } else {
16746       int savedBlackPlaysFirst = blackPlaysFirst;
16747       EditPositionEvent();
16748       blackPlaysFirst = savedBlackPlaysFirst;
16749       CopyBoard(boards[0], initial_position);
16750       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16751       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16752       DisplayBothClocks();
16753       DrawPosition(FALSE, boards[currentMove]);
16754     }
16755   }
16756 }
16757
16758 static char cseq[12] = "\\   ";
16759
16760 Boolean
16761 set_cont_sequence (char *new_seq)
16762 {
16763     int len;
16764     Boolean ret;
16765
16766     // handle bad attempts to set the sequence
16767         if (!new_seq)
16768                 return 0; // acceptable error - no debug
16769
16770     len = strlen(new_seq);
16771     ret = (len > 0) && (len < sizeof(cseq));
16772     if (ret)
16773       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16774     else if (appData.debugMode)
16775       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16776     return ret;
16777 }
16778
16779 /*
16780     reformat a source message so words don't cross the width boundary.  internal
16781     newlines are not removed.  returns the wrapped size (no null character unless
16782     included in source message).  If dest is NULL, only calculate the size required
16783     for the dest buffer.  lp argument indicats line position upon entry, and it's
16784     passed back upon exit.
16785 */
16786 int
16787 wrap (char *dest, char *src, int count, int width, int *lp)
16788 {
16789     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16790
16791     cseq_len = strlen(cseq);
16792     old_line = line = *lp;
16793     ansi = len = clen = 0;
16794
16795     for (i=0; i < count; i++)
16796     {
16797         if (src[i] == '\033')
16798             ansi = 1;
16799
16800         // if we hit the width, back up
16801         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16802         {
16803             // store i & len in case the word is too long
16804             old_i = i, old_len = len;
16805
16806             // find the end of the last word
16807             while (i && src[i] != ' ' && src[i] != '\n')
16808             {
16809                 i--;
16810                 len--;
16811             }
16812
16813             // word too long?  restore i & len before splitting it
16814             if ((old_i-i+clen) >= width)
16815             {
16816                 i = old_i;
16817                 len = old_len;
16818             }
16819
16820             // extra space?
16821             if (i && src[i-1] == ' ')
16822                 len--;
16823
16824             if (src[i] != ' ' && src[i] != '\n')
16825             {
16826                 i--;
16827                 if (len)
16828                     len--;
16829             }
16830
16831             // now append the newline and continuation sequence
16832             if (dest)
16833                 dest[len] = '\n';
16834             len++;
16835             if (dest)
16836                 strncpy(dest+len, cseq, cseq_len);
16837             len += cseq_len;
16838             line = cseq_len;
16839             clen = cseq_len;
16840             continue;
16841         }
16842
16843         if (dest)
16844             dest[len] = src[i];
16845         len++;
16846         if (!ansi)
16847             line++;
16848         if (src[i] == '\n')
16849             line = 0;
16850         if (src[i] == 'm')
16851             ansi = 0;
16852     }
16853     if (dest && appData.debugMode)
16854     {
16855         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16856             count, width, line, len, *lp);
16857         show_bytes(debugFP, src, count);
16858         fprintf(debugFP, "\ndest: ");
16859         show_bytes(debugFP, dest, len);
16860         fprintf(debugFP, "\n");
16861     }
16862     *lp = dest ? line : old_line;
16863
16864     return len;
16865 }
16866
16867 // [HGM] vari: routines for shelving variations
16868 Boolean modeRestore = FALSE;
16869
16870 void
16871 PushInner (int firstMove, int lastMove)
16872 {
16873         int i, j, nrMoves = lastMove - firstMove;
16874
16875         // push current tail of game on stack
16876         savedResult[storedGames] = gameInfo.result;
16877         savedDetails[storedGames] = gameInfo.resultDetails;
16878         gameInfo.resultDetails = NULL;
16879         savedFirst[storedGames] = firstMove;
16880         savedLast [storedGames] = lastMove;
16881         savedFramePtr[storedGames] = framePtr;
16882         framePtr -= nrMoves; // reserve space for the boards
16883         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16884             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16885             for(j=0; j<MOVE_LEN; j++)
16886                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16887             for(j=0; j<2*MOVE_LEN; j++)
16888                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16889             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16890             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16891             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16892             pvInfoList[firstMove+i-1].depth = 0;
16893             commentList[framePtr+i] = commentList[firstMove+i];
16894             commentList[firstMove+i] = NULL;
16895         }
16896
16897         storedGames++;
16898         forwardMostMove = firstMove; // truncate game so we can start variation
16899 }
16900
16901 void
16902 PushTail (int firstMove, int lastMove)
16903 {
16904         if(appData.icsActive) { // only in local mode
16905                 forwardMostMove = currentMove; // mimic old ICS behavior
16906                 return;
16907         }
16908         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16909
16910         PushInner(firstMove, lastMove);
16911         if(storedGames == 1) GreyRevert(FALSE);
16912         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16913 }
16914
16915 void
16916 PopInner (Boolean annotate)
16917 {
16918         int i, j, nrMoves;
16919         char buf[8000], moveBuf[20];
16920
16921         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16922         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16923         nrMoves = savedLast[storedGames] - currentMove;
16924         if(annotate) {
16925                 int cnt = 10;
16926                 if(!WhiteOnMove(currentMove))
16927                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16928                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16929                 for(i=currentMove; i<forwardMostMove; i++) {
16930                         if(WhiteOnMove(i))
16931                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16932                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16933                         strcat(buf, moveBuf);
16934                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16935                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16936                 }
16937                 strcat(buf, ")");
16938         }
16939         for(i=1; i<=nrMoves; i++) { // copy last variation back
16940             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16941             for(j=0; j<MOVE_LEN; j++)
16942                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16943             for(j=0; j<2*MOVE_LEN; j++)
16944                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16945             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16946             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16947             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16948             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16949             commentList[currentMove+i] = commentList[framePtr+i];
16950             commentList[framePtr+i] = NULL;
16951         }
16952         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16953         framePtr = savedFramePtr[storedGames];
16954         gameInfo.result = savedResult[storedGames];
16955         if(gameInfo.resultDetails != NULL) {
16956             free(gameInfo.resultDetails);
16957       }
16958         gameInfo.resultDetails = savedDetails[storedGames];
16959         forwardMostMove = currentMove + nrMoves;
16960 }
16961
16962 Boolean
16963 PopTail (Boolean annotate)
16964 {
16965         if(appData.icsActive) return FALSE; // only in local mode
16966         if(!storedGames) return FALSE; // sanity
16967         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16968
16969         PopInner(annotate);
16970         if(currentMove < forwardMostMove) ForwardEvent(); else
16971         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16972
16973         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16974         return TRUE;
16975 }
16976
16977 void
16978 CleanupTail ()
16979 {       // remove all shelved variations
16980         int i;
16981         for(i=0; i<storedGames; i++) {
16982             if(savedDetails[i])
16983                 free(savedDetails[i]);
16984             savedDetails[i] = NULL;
16985         }
16986         for(i=framePtr; i<MAX_MOVES; i++) {
16987                 if(commentList[i]) free(commentList[i]);
16988                 commentList[i] = NULL;
16989         }
16990         framePtr = MAX_MOVES-1;
16991         storedGames = 0;
16992 }
16993
16994 void
16995 LoadVariation (int index, char *text)
16996 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16997         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16998         int level = 0, move;
16999
17000         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17001         // first find outermost bracketing variation
17002         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17003             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17004                 if(*p == '{') wait = '}'; else
17005                 if(*p == '[') wait = ']'; else
17006                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17007                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17008             }
17009             if(*p == wait) wait = NULLCHAR; // closing ]} found
17010             p++;
17011         }
17012         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17013         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17014         end[1] = NULLCHAR; // clip off comment beyond variation
17015         ToNrEvent(currentMove-1);
17016         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17017         // kludge: use ParsePV() to append variation to game
17018         move = currentMove;
17019         ParsePV(start, TRUE, TRUE);
17020         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17021         ClearPremoveHighlights();
17022         CommentPopDown();
17023         ToNrEvent(currentMove+1);
17024 }
17025