d1f3b04b605528051fb06c69e3f63b2ada2fee73
[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 && clickType == Press) { // user starts something after premove has been entered: abort premove as side effect
6890         gotPremove = 0;
6891         ClearPremoveHighlights();
6892     }
6893
6894     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6895         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6896
6897     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6898         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6899                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6900         defaultPromoChoice = DefaultPromoChoice(side);
6901     }
6902
6903     autoQueen = appData.alwaysPromoteToQueen;
6904
6905     if (fromX == -1) {
6906       int originalY = y;
6907       gatingPiece = EmptySquare;
6908       if (clickType != Press) {
6909         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6910             DragPieceEnd(xPix, yPix); dragging = 0;
6911             DrawPosition(FALSE, NULL);
6912         }
6913         return;
6914       }
6915       fromX = x; fromY = y; toX = toY = -1;
6916       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6917          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6918          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6919             /* First square */
6920             if (OKToStartUserMove(fromX, fromY)) {
6921                 second = 0;
6922                 MarkTargetSquares(0);
6923                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6924                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6925                     promoSweep = defaultPromoChoice;
6926                     selectFlag = 0; lastX = xPix; lastY = yPix;
6927                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6928                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6929                 }
6930                 if (appData.highlightDragging) {
6931                     SetHighlights(fromX, fromY, -1, -1);
6932                 }
6933             } else fromX = fromY = -1;
6934             return;
6935         }
6936     }
6937
6938     /* fromX != -1 */
6939     if (clickType == Press && gameMode != EditPosition) {
6940         ChessSquare fromP;
6941         ChessSquare toP;
6942         int frc;
6943
6944         // ignore off-board to clicks
6945         if(y < 0 || x < 0) return;
6946
6947         /* Check if clicking again on the same color piece */
6948         fromP = boards[currentMove][fromY][fromX];
6949         toP = boards[currentMove][y][x];
6950         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6951         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6952              WhitePawn <= toP && toP <= WhiteKing &&
6953              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6954              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6955             (BlackPawn <= fromP && fromP <= BlackKing &&
6956              BlackPawn <= toP && toP <= BlackKing &&
6957              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6958              !(fromP == BlackKing && toP == BlackRook && frc))) {
6959             /* Clicked again on same color piece -- changed his mind */
6960             second = (x == fromX && y == fromY);
6961             promoDefaultAltered = FALSE;
6962             MarkTargetSquares(1);
6963            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6964             if (appData.highlightDragging) {
6965                 SetHighlights(x, y, -1, -1);
6966             } else {
6967                 ClearHighlights();
6968             }
6969             if (OKToStartUserMove(x, y)) {
6970                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6971                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6972                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6973                  gatingPiece = boards[currentMove][fromY][fromX];
6974                 else gatingPiece = EmptySquare;
6975                 fromX = x;
6976                 fromY = y; dragging = 1;
6977                 MarkTargetSquares(0);
6978                 DragPieceBegin(xPix, yPix, FALSE);
6979                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6980                     promoSweep = defaultPromoChoice;
6981                     selectFlag = 0; lastX = xPix; lastY = yPix;
6982                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6983                 }
6984             }
6985            }
6986            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6987            second = FALSE; 
6988         }
6989         // ignore clicks on holdings
6990         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6991     }
6992
6993     if (clickType == Release && x == fromX && y == fromY) {
6994         DragPieceEnd(xPix, yPix); dragging = 0;
6995         if(clearFlag) {
6996             // a deferred attempt to click-click move an empty square on top of a piece
6997             boards[currentMove][y][x] = EmptySquare;
6998             ClearHighlights();
6999             DrawPosition(FALSE, boards[currentMove]);
7000             fromX = fromY = -1; clearFlag = 0;
7001             return;
7002         }
7003         if (appData.animateDragging) {
7004             /* Undo animation damage if any */
7005             DrawPosition(FALSE, NULL);
7006         }
7007         if (second) {
7008             /* Second up/down in same square; just abort move */
7009             second = 0;
7010             fromX = fromY = -1;
7011             gatingPiece = EmptySquare;
7012             ClearHighlights();
7013             gotPremove = 0;
7014             ClearPremoveHighlights();
7015         } else {
7016             /* First upclick in same square; start click-click mode */
7017             SetHighlights(x, y, -1, -1);
7018         }
7019         return;
7020     }
7021
7022     clearFlag = 0;
7023
7024     /* we now have a different from- and (possibly off-board) to-square */
7025     /* Completed move */
7026     toX = x;
7027     toY = y;
7028     saveAnimate = appData.animate;
7029     MarkTargetSquares(1);
7030     if (clickType == Press) {
7031         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7032             // must be Edit Position mode with empty-square selected
7033             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7034             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7035             return;
7036         }
7037         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7038             ChessSquare piece = boards[currentMove][fromY][fromX];
7039             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7040             promoSweep = defaultPromoChoice;
7041             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7042             selectFlag = 0; lastX = xPix; lastY = yPix;
7043             Sweep(0); // Pawn that is going to promote: preview promotion piece
7044             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7045             DrawPosition(FALSE, boards[currentMove]);
7046             return;
7047         }
7048         /* Finish clickclick move */
7049         if (appData.animate || appData.highlightLastMove) {
7050             SetHighlights(fromX, fromY, toX, toY);
7051         } else {
7052             ClearHighlights();
7053         }
7054     } else {
7055         /* Finish drag move */
7056         if (appData.highlightLastMove) {
7057             SetHighlights(fromX, fromY, toX, toY);
7058         } else {
7059             ClearHighlights();
7060         }
7061         DragPieceEnd(xPix, yPix); dragging = 0;
7062         /* Don't animate move and drag both */
7063         appData.animate = FALSE;
7064     }
7065
7066     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7067     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7068         ChessSquare piece = boards[currentMove][fromY][fromX];
7069         if(gameMode == EditPosition && piece != EmptySquare &&
7070            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7071             int n;
7072
7073             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7074                 n = PieceToNumber(piece - (int)BlackPawn);
7075                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7076                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7077                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7078             } else
7079             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7080                 n = PieceToNumber(piece);
7081                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7082                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7083                 boards[currentMove][n][BOARD_WIDTH-2]++;
7084             }
7085             boards[currentMove][fromY][fromX] = EmptySquare;
7086         }
7087         ClearHighlights();
7088         fromX = fromY = -1;
7089         DrawPosition(TRUE, boards[currentMove]);
7090         return;
7091     }
7092
7093     // off-board moves should not be highlighted
7094     if(x < 0 || y < 0) ClearHighlights();
7095
7096     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7097
7098     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7099         SetHighlights(fromX, fromY, toX, toY);
7100         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7101             // [HGM] super: promotion to captured piece selected from holdings
7102             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7103             promotionChoice = TRUE;
7104             // kludge follows to temporarily execute move on display, without promoting yet
7105             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7106             boards[currentMove][toY][toX] = p;
7107             DrawPosition(FALSE, boards[currentMove]);
7108             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7109             boards[currentMove][toY][toX] = q;
7110             DisplayMessage("Click in holdings to choose piece", "");
7111             return;
7112         }
7113         PromotionPopUp();
7114     } else {
7115         int oldMove = currentMove;
7116         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7117         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7118         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7119         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7120            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7121             DrawPosition(TRUE, boards[currentMove]);
7122         fromX = fromY = -1;
7123     }
7124     appData.animate = saveAnimate;
7125     if (appData.animate || appData.animateDragging) {
7126         /* Undo animation damage if needed */
7127         DrawPosition(FALSE, NULL);
7128     }
7129 }
7130
7131 int
7132 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7133 {   // front-end-free part taken out of PieceMenuPopup
7134     int whichMenu; int xSqr, ySqr;
7135
7136     if(seekGraphUp) { // [HGM] seekgraph
7137         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7138         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7139         return -2;
7140     }
7141
7142     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7143          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7144         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7145         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7146         if(action == Press)   {
7147             originalFlip = flipView;
7148             flipView = !flipView; // temporarily flip board to see game from partners perspective
7149             DrawPosition(TRUE, partnerBoard);
7150             DisplayMessage(partnerStatus, "");
7151             partnerUp = TRUE;
7152         } else if(action == Release) {
7153             flipView = originalFlip;
7154             DrawPosition(TRUE, boards[currentMove]);
7155             partnerUp = FALSE;
7156         }
7157         return -2;
7158     }
7159
7160     xSqr = EventToSquare(x, BOARD_WIDTH);
7161     ySqr = EventToSquare(y, BOARD_HEIGHT);
7162     if (action == Release) {
7163         if(pieceSweep != EmptySquare) {
7164             EditPositionMenuEvent(pieceSweep, toX, toY);
7165             pieceSweep = EmptySquare;
7166         } else UnLoadPV(); // [HGM] pv
7167     }
7168     if (action != Press) return -2; // return code to be ignored
7169     switch (gameMode) {
7170       case IcsExamining:
7171         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7172       case EditPosition:
7173         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7174         if (xSqr < 0 || ySqr < 0) return -1;
7175         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7176         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7177         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7178         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7179         NextPiece(0);
7180         return 2; // grab
7181       case IcsObserving:
7182         if(!appData.icsEngineAnalyze) return -1;
7183       case IcsPlayingWhite:
7184       case IcsPlayingBlack:
7185         if(!appData.zippyPlay) goto noZip;
7186       case AnalyzeMode:
7187       case AnalyzeFile:
7188       case MachinePlaysWhite:
7189       case MachinePlaysBlack:
7190       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7191         if (!appData.dropMenu) {
7192           LoadPV(x, y);
7193           return 2; // flag front-end to grab mouse events
7194         }
7195         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7196            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7197       case EditGame:
7198       noZip:
7199         if (xSqr < 0 || ySqr < 0) return -1;
7200         if (!appData.dropMenu || appData.testLegality &&
7201             gameInfo.variant != VariantBughouse &&
7202             gameInfo.variant != VariantCrazyhouse) return -1;
7203         whichMenu = 1; // drop menu
7204         break;
7205       default:
7206         return -1;
7207     }
7208
7209     if (((*fromX = xSqr) < 0) ||
7210         ((*fromY = ySqr) < 0)) {
7211         *fromX = *fromY = -1;
7212         return -1;
7213     }
7214     if (flipView)
7215       *fromX = BOARD_WIDTH - 1 - *fromX;
7216     else
7217       *fromY = BOARD_HEIGHT - 1 - *fromY;
7218
7219     return whichMenu;
7220 }
7221
7222 void
7223 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7224 {
7225 //    char * hint = lastHint;
7226     FrontEndProgramStats stats;
7227
7228     stats.which = cps == &first ? 0 : 1;
7229     stats.depth = cpstats->depth;
7230     stats.nodes = cpstats->nodes;
7231     stats.score = cpstats->score;
7232     stats.time = cpstats->time;
7233     stats.pv = cpstats->movelist;
7234     stats.hint = lastHint;
7235     stats.an_move_index = 0;
7236     stats.an_move_count = 0;
7237
7238     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7239         stats.hint = cpstats->move_name;
7240         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7241         stats.an_move_count = cpstats->nr_moves;
7242     }
7243
7244     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
7245
7246     SetProgramStats( &stats );
7247 }
7248
7249 void
7250 ClearEngineOutputPane (int which)
7251 {
7252     static FrontEndProgramStats dummyStats;
7253     dummyStats.which = which;
7254     dummyStats.pv = "#";
7255     SetProgramStats( &dummyStats );
7256 }
7257
7258 #define MAXPLAYERS 500
7259
7260 char *
7261 TourneyStandings (int display)
7262 {
7263     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7264     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7265     char result, *p, *names[MAXPLAYERS];
7266
7267     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7268         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7269     names[0] = p = strdup(appData.participants);
7270     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7271
7272     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7273
7274     while(result = appData.results[nr]) {
7275         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7276         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7277         wScore = bScore = 0;
7278         switch(result) {
7279           case '+': wScore = 2; break;
7280           case '-': bScore = 2; break;
7281           case '=': wScore = bScore = 1; break;
7282           case ' ':
7283           case '*': return strdup("busy"); // tourney not finished
7284         }
7285         score[w] += wScore;
7286         score[b] += bScore;
7287         games[w]++;
7288         games[b]++;
7289         nr++;
7290     }
7291     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7292     for(w=0; w<nPlayers; w++) {
7293         bScore = -1;
7294         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7295         ranking[w] = b; points[w] = bScore; score[b] = -2;
7296     }
7297     p = malloc(nPlayers*34+1);
7298     for(w=0; w<nPlayers && w<display; w++)
7299         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7300     free(names[0]);
7301     return p;
7302 }
7303
7304 void
7305 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7306 {       // count all piece types
7307         int p, f, r;
7308         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7309         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7311                 p = board[r][f];
7312                 pCnt[p]++;
7313                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7314                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7315                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7316                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7317                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7318                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7319         }
7320 }
7321
7322 int
7323 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7324 {
7325         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7326         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7327
7328         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7329         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7330         if(myPawns == 2 && nMine == 3) // KPP
7331             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7332         if(myPawns == 1 && nMine == 2) // KP
7333             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7334         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7335             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7336         if(myPawns) return FALSE;
7337         if(pCnt[WhiteRook+side])
7338             return pCnt[BlackRook-side] ||
7339                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7340                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7341                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7342         if(pCnt[WhiteCannon+side]) {
7343             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7344             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7345         }
7346         if(pCnt[WhiteKnight+side])
7347             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7348         return FALSE;
7349 }
7350
7351 int
7352 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7353 {
7354         VariantClass v = gameInfo.variant;
7355
7356         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7357         if(v == VariantShatranj) return TRUE; // always winnable through baring
7358         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7359         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7360
7361         if(v == VariantXiangqi) {
7362                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7363
7364                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7365                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7366                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7367                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7368                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7369                 if(stale) // we have at least one last-rank P plus perhaps C
7370                     return majors // KPKX
7371                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7372                 else // KCA*E*
7373                     return pCnt[WhiteFerz+side] // KCAK
7374                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7375                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7376                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7377
7378         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7379                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7380
7381                 if(nMine == 1) return FALSE; // bare King
7382                 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
7383                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7384                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7385                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7386                 if(pCnt[WhiteKnight+side])
7387                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7388                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7389                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7390                 if(nBishops)
7391                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7392                 if(pCnt[WhiteAlfil+side])
7393                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7394                 if(pCnt[WhiteWazir+side])
7395                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7396         }
7397
7398         return TRUE;
7399 }
7400
7401 int
7402 CompareWithRights (Board b1, Board b2)
7403 {
7404     int rights = 0;
7405     if(!CompareBoards(b1, b2)) return FALSE;
7406     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7407     /* compare castling rights */
7408     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7409            rights++; /* King lost rights, while rook still had them */
7410     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7411         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7412            rights++; /* but at least one rook lost them */
7413     }
7414     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7415            rights++;
7416     if( b1[CASTLING][5] != NoRights ) {
7417         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7418            rights++;
7419     }
7420     return rights == 0;
7421 }
7422
7423 int
7424 Adjudicate (ChessProgramState *cps)
7425 {       // [HGM] some adjudications useful with buggy engines
7426         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7427         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7428         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7429         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7430         int k, count = 0; static int bare = 1;
7431         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7432         Boolean canAdjudicate = !appData.icsActive;
7433
7434         // most tests only when we understand the game, i.e. legality-checking on
7435             if( appData.testLegality )
7436             {   /* [HGM] Some more adjudications for obstinate engines */
7437                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7438                 static int moveCount = 6;
7439                 ChessMove result;
7440                 char *reason = NULL;
7441
7442                 /* Count what is on board. */
7443                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7444
7445                 /* Some material-based adjudications that have to be made before stalemate test */
7446                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7447                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7448                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7449                      if(canAdjudicate && appData.checkMates) {
7450                          if(engineOpponent)
7451                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7452                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7453                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7454                          return 1;
7455                      }
7456                 }
7457
7458                 /* Bare King in Shatranj (loses) or Losers (wins) */
7459                 if( nrW == 1 || nrB == 1) {
7460                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7461                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7462                      if(canAdjudicate && appData.checkMates) {
7463                          if(engineOpponent)
7464                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7465                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7466                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7467                          return 1;
7468                      }
7469                   } else
7470                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7471                   {    /* bare King */
7472                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7473                         if(canAdjudicate && appData.checkMates) {
7474                             /* but only adjudicate if adjudication enabled */
7475                             if(engineOpponent)
7476                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7477                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7478                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7479                             return 1;
7480                         }
7481                   }
7482                 } else bare = 1;
7483
7484
7485             // don't wait for engine to announce game end if we can judge ourselves
7486             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7487               case MT_CHECK:
7488                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7489                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7490                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7491                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7492                             checkCnt++;
7493                         if(checkCnt >= 2) {
7494                             reason = "Xboard adjudication: 3rd check";
7495                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7496                             break;
7497                         }
7498                     }
7499                 }
7500               case MT_NONE:
7501               default:
7502                 break;
7503               case MT_STALEMATE:
7504               case MT_STAINMATE:
7505                 reason = "Xboard adjudication: Stalemate";
7506                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7507                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7508                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7509                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7510                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7511                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7512                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7513                                                                         EP_CHECKMATE : EP_WINS);
7514                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7515                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7516                 }
7517                 break;
7518               case MT_CHECKMATE:
7519                 reason = "Xboard adjudication: Checkmate";
7520                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7521                 break;
7522             }
7523
7524                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7525                     case EP_STALEMATE:
7526                         result = GameIsDrawn; break;
7527                     case EP_CHECKMATE:
7528                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7529                     case EP_WINS:
7530                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7531                     default:
7532                         result = EndOfFile;
7533                 }
7534                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7535                     if(engineOpponent)
7536                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7537                     GameEnds( result, reason, GE_XBOARD );
7538                     return 1;
7539                 }
7540
7541                 /* Next absolutely insufficient mating material. */
7542                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7543                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7544                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7545
7546                      /* always flag draws, for judging claims */
7547                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7548
7549                      if(canAdjudicate && appData.materialDraws) {
7550                          /* but only adjudicate them if adjudication enabled */
7551                          if(engineOpponent) {
7552                            SendToProgram("force\n", engineOpponent); // suppress reply
7553                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7554                          }
7555                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7556                          return 1;
7557                      }
7558                 }
7559
7560                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7561                 if(gameInfo.variant == VariantXiangqi ?
7562                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7563                  : nrW + nrB == 4 &&
7564                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7565                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7566                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7567                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7568                    ) ) {
7569                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7570                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7571                           if(engineOpponent) {
7572                             SendToProgram("force\n", engineOpponent); // suppress reply
7573                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7574                           }
7575                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7576                           return 1;
7577                      }
7578                 } else moveCount = 6;
7579             }
7580
7581         // Repetition draws and 50-move rule can be applied independently of legality testing
7582
7583                 /* Check for rep-draws */
7584                 count = 0;
7585                 for(k = forwardMostMove-2;
7586                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7587                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7588                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7589                     k-=2)
7590                 {   int rights=0;
7591                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7592                         /* compare castling rights */
7593                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7594                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7595                                 rights++; /* King lost rights, while rook still had them */
7596                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7597                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7598                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7599                                    rights++; /* but at least one rook lost them */
7600                         }
7601                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7602                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7603                                 rights++;
7604                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7605                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7606                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7607                                    rights++;
7608                         }
7609                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7610                             && appData.drawRepeats > 1) {
7611                              /* adjudicate after user-specified nr of repeats */
7612                              int result = GameIsDrawn;
7613                              char *details = "XBoard adjudication: repetition draw";
7614                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7615                                 // [HGM] xiangqi: check for forbidden perpetuals
7616                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7617                                 for(m=forwardMostMove; m>k; m-=2) {
7618                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7619                                         ourPerpetual = 0; // the current mover did not always check
7620                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7621                                         hisPerpetual = 0; // the opponent did not always check
7622                                 }
7623                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7624                                                                         ourPerpetual, hisPerpetual);
7625                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7626                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7627                                     details = "Xboard adjudication: perpetual checking";
7628                                 } else
7629                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7630                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7631                                 } else
7632                                 // Now check for perpetual chases
7633                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7634                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7635                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7636                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7637                                         static char resdet[MSG_SIZ];
7638                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7639                                         details = resdet;
7640                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7641                                     } else
7642                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7643                                         break; // Abort repetition-checking loop.
7644                                 }
7645                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7646                              }
7647                              if(engineOpponent) {
7648                                SendToProgram("force\n", engineOpponent); // suppress reply
7649                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7650                              }
7651                              GameEnds( result, details, GE_XBOARD );
7652                              return 1;
7653                         }
7654                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7655                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7656                     }
7657                 }
7658
7659                 /* Now we test for 50-move draws. Determine ply count */
7660                 count = forwardMostMove;
7661                 /* look for last irreversble move */
7662                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7663                     count--;
7664                 /* if we hit starting position, add initial plies */
7665                 if( count == backwardMostMove )
7666                     count -= initialRulePlies;
7667                 count = forwardMostMove - count;
7668                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7669                         // adjust reversible move counter for checks in Xiangqi
7670                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7671                         if(i < backwardMostMove) i = backwardMostMove;
7672                         while(i <= forwardMostMove) {
7673                                 lastCheck = inCheck; // check evasion does not count
7674                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7675                                 if(inCheck || lastCheck) count--; // check does not count
7676                                 i++;
7677                         }
7678                 }
7679                 if( count >= 100)
7680                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7681                          /* this is used to judge if draw claims are legal */
7682                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7683                          if(engineOpponent) {
7684                            SendToProgram("force\n", engineOpponent); // suppress reply
7685                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7686                          }
7687                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7688                          return 1;
7689                 }
7690
7691                 /* if draw offer is pending, treat it as a draw claim
7692                  * when draw condition present, to allow engines a way to
7693                  * claim draws before making their move to avoid a race
7694                  * condition occurring after their move
7695                  */
7696                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7697                          char *p = NULL;
7698                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7699                              p = "Draw claim: 50-move rule";
7700                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7701                              p = "Draw claim: 3-fold repetition";
7702                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7703                              p = "Draw claim: insufficient mating material";
7704                          if( p != NULL && canAdjudicate) {
7705                              if(engineOpponent) {
7706                                SendToProgram("force\n", engineOpponent); // suppress reply
7707                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7708                              }
7709                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7710                              return 1;
7711                          }
7712                 }
7713
7714                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7715                     if(engineOpponent) {
7716                       SendToProgram("force\n", engineOpponent); // suppress reply
7717                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                     }
7719                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7720                     return 1;
7721                 }
7722         return 0;
7723 }
7724
7725 char *
7726 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7727 {   // [HGM] book: this routine intercepts moves to simulate book replies
7728     char *bookHit = NULL;
7729
7730     //first determine if the incoming move brings opponent into his book
7731     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7732         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7733     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7734     if(bookHit != NULL && !cps->bookSuspend) {
7735         // make sure opponent is not going to reply after receiving move to book position
7736         SendToProgram("force\n", cps);
7737         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7738     }
7739     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7740     // now arrange restart after book miss
7741     if(bookHit) {
7742         // after a book hit we never send 'go', and the code after the call to this routine
7743         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7744         char buf[MSG_SIZ], *move = bookHit;
7745         if(cps->useSAN) {
7746             int fromX, fromY, toX, toY;
7747             char promoChar;
7748             ChessMove moveType;
7749             move = buf + 30;
7750             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7751                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7752                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7753                                     PosFlags(forwardMostMove),
7754                                     fromY, fromX, toY, toX, promoChar, move);
7755             } else {
7756                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7757                 bookHit = NULL;
7758             }
7759         }
7760         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7761         SendToProgram(buf, cps);
7762         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7763     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7764         SendToProgram("go\n", cps);
7765         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7766     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7767         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7768             SendToProgram("go\n", cps);
7769         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7770     }
7771     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7772 }
7773
7774 char *savedMessage;
7775 ChessProgramState *savedState;
7776 void
7777 DeferredBookMove (void)
7778 {
7779         if(savedState->lastPing != savedState->lastPong)
7780                     ScheduleDelayedEvent(DeferredBookMove, 10);
7781         else
7782         HandleMachineMove(savedMessage, savedState);
7783 }
7784
7785 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7786
7787 void
7788 HandleMachineMove (char *message, ChessProgramState *cps)
7789 {
7790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7791     char realname[MSG_SIZ];
7792     int fromX, fromY, toX, toY;
7793     ChessMove moveType;
7794     char promoChar;
7795     char *p, *pv=buf1;
7796     int machineWhite;
7797     char *bookHit;
7798
7799     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7800         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7801         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7802             DisplayError(_("Invalid pairing from pairing engine"), 0);
7803             return;
7804         }
7805         pairingReceived = 1;
7806         NextMatchGame();
7807         return; // Skim the pairing messages here.
7808     }
7809
7810     cps->userError = 0;
7811
7812 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7813     /*
7814      * Kludge to ignore BEL characters
7815      */
7816     while (*message == '\007') message++;
7817
7818     /*
7819      * [HGM] engine debug message: ignore lines starting with '#' character
7820      */
7821     if(cps->debug && *message == '#') return;
7822
7823     /*
7824      * Look for book output
7825      */
7826     if (cps == &first && bookRequested) {
7827         if (message[0] == '\t' || message[0] == ' ') {
7828             /* Part of the book output is here; append it */
7829             strcat(bookOutput, message);
7830             strcat(bookOutput, "  \n");
7831             return;
7832         } else if (bookOutput[0] != NULLCHAR) {
7833             /* All of book output has arrived; display it */
7834             char *p = bookOutput;
7835             while (*p != NULLCHAR) {
7836                 if (*p == '\t') *p = ' ';
7837                 p++;
7838             }
7839             DisplayInformation(bookOutput);
7840             bookRequested = FALSE;
7841             /* Fall through to parse the current output */
7842         }
7843     }
7844
7845     /*
7846      * Look for machine move.
7847      */
7848     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7849         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7850     {
7851         /* This method is only useful on engines that support ping */
7852         if (cps->lastPing != cps->lastPong) {
7853           if (gameMode == BeginningOfGame) {
7854             /* Extra move from before last new; ignore */
7855             if (appData.debugMode) {
7856                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7857             }
7858           } else {
7859             if (appData.debugMode) {
7860                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7861                         cps->which, gameMode);
7862             }
7863
7864             SendToProgram("undo\n", cps);
7865           }
7866           return;
7867         }
7868
7869         switch (gameMode) {
7870           case BeginningOfGame:
7871             /* Extra move from before last reset; ignore */
7872             if (appData.debugMode) {
7873                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7874             }
7875             return;
7876
7877           case EndOfGame:
7878           case IcsIdle:
7879           default:
7880             /* Extra move after we tried to stop.  The mode test is
7881                not a reliable way of detecting this problem, but it's
7882                the best we can do on engines that don't support ping.
7883             */
7884             if (appData.debugMode) {
7885                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7886                         cps->which, gameMode);
7887             }
7888             SendToProgram("undo\n", cps);
7889             return;
7890
7891           case MachinePlaysWhite:
7892           case IcsPlayingWhite:
7893             machineWhite = TRUE;
7894             break;
7895
7896           case MachinePlaysBlack:
7897           case IcsPlayingBlack:
7898             machineWhite = FALSE;
7899             break;
7900
7901           case TwoMachinesPlay:
7902             machineWhite = (cps->twoMachinesColor[0] == 'w');
7903             break;
7904         }
7905         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7906             if (appData.debugMode) {
7907                 fprintf(debugFP,
7908                         "Ignoring move out of turn by %s, gameMode %d"
7909                         ", forwardMost %d\n",
7910                         cps->which, gameMode, forwardMostMove);
7911             }
7912             return;
7913         }
7914
7915         if(cps->alphaRank) AlphaRank(machineMove, 4);
7916         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7917                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7918             /* Machine move could not be parsed; ignore it. */
7919           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7920                     machineMove, _(cps->which));
7921             DisplayError(buf1, 0);
7922             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7923                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7924             if (gameMode == TwoMachinesPlay) {
7925               GameEnds(machineWhite ? BlackWins : WhiteWins,
7926                        buf1, GE_XBOARD);
7927             }
7928             return;
7929         }
7930
7931         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7932         /* So we have to redo legality test with true e.p. status here,  */
7933         /* to make sure an illegal e.p. capture does not slip through,   */
7934         /* to cause a forfeit on a justified illegal-move complaint      */
7935         /* of the opponent.                                              */
7936         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7937            ChessMove moveType;
7938            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7939                              fromY, fromX, toY, toX, promoChar);
7940             if(moveType == IllegalMove) {
7941               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7944                            buf1, GE_XBOARD);
7945                 return;
7946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7947            /* [HGM] Kludge to handle engines that send FRC-style castling
7948               when they shouldn't (like TSCP-Gothic) */
7949            switch(moveType) {
7950              case WhiteASideCastleFR:
7951              case BlackASideCastleFR:
7952                toX+=2;
7953                currentMoveString[2]++;
7954                break;
7955              case WhiteHSideCastleFR:
7956              case BlackHSideCastleFR:
7957                toX--;
7958                currentMoveString[2]--;
7959                break;
7960              default: ; // nothing to do, but suppresses warning of pedantic compilers
7961            }
7962         }
7963         hintRequested = FALSE;
7964         lastHint[0] = NULLCHAR;
7965         bookRequested = FALSE;
7966         /* Program may be pondering now */
7967         cps->maybeThinking = TRUE;
7968         if (cps->sendTime == 2) cps->sendTime = 1;
7969         if (cps->offeredDraw) cps->offeredDraw--;
7970
7971         /* [AS] Save move info*/
7972         pvInfoList[ forwardMostMove ].score = programStats.score;
7973         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7974         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7975
7976         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7977
7978         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7979         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7980             int count = 0;
7981
7982             while( count < adjudicateLossPlies ) {
7983                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7984
7985                 if( count & 1 ) {
7986                     score = -score; /* Flip score for winning side */
7987                 }
7988
7989                 if( score > adjudicateLossThreshold ) {
7990                     break;
7991                 }
7992
7993                 count++;
7994             }
7995
7996             if( count >= adjudicateLossPlies ) {
7997                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7998
7999                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8000                     "Xboard adjudication",
8001                     GE_XBOARD );
8002
8003                 return;
8004             }
8005         }
8006
8007         if(Adjudicate(cps)) {
8008             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8009             return; // [HGM] adjudicate: for all automatic game ends
8010         }
8011
8012 #if ZIPPY
8013         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8014             first.initDone) {
8015           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8016                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8017                 SendToICS("draw ");
8018                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8019           }
8020           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8021           ics_user_moved = 1;
8022           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8023                 char buf[3*MSG_SIZ];
8024
8025                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8026                         programStats.score / 100.,
8027                         programStats.depth,
8028                         programStats.time / 100.,
8029                         (unsigned int)programStats.nodes,
8030                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8031                         programStats.movelist);
8032                 SendToICS(buf);
8033 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8034           }
8035         }
8036 #endif
8037
8038         /* [AS] Clear stats for next move */
8039         ClearProgramStats();
8040         thinkOutput[0] = NULLCHAR;
8041         hiddenThinkOutputState = 0;
8042
8043         bookHit = NULL;
8044         if (gameMode == TwoMachinesPlay) {
8045             /* [HGM] relaying draw offers moved to after reception of move */
8046             /* and interpreting offer as claim if it brings draw condition */
8047             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8048                 SendToProgram("draw\n", cps->other);
8049             }
8050             if (cps->other->sendTime) {
8051                 SendTimeRemaining(cps->other,
8052                                   cps->other->twoMachinesColor[0] == 'w');
8053             }
8054             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8055             if (firstMove && !bookHit) {
8056                 firstMove = FALSE;
8057                 if (cps->other->useColors) {
8058                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8059                 }
8060                 SendToProgram("go\n", cps->other);
8061             }
8062             cps->other->maybeThinking = TRUE;
8063         }
8064
8065         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8066
8067         if (!pausing && appData.ringBellAfterMoves) {
8068             RingBell();
8069         }
8070
8071         /*
8072          * Reenable menu items that were disabled while
8073          * machine was thinking
8074          */
8075         if (gameMode != TwoMachinesPlay)
8076             SetUserThinkingEnables();
8077
8078         // [HGM] book: after book hit opponent has received move and is now in force mode
8079         // force the book reply into it, and then fake that it outputted this move by jumping
8080         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8081         if(bookHit) {
8082                 static char bookMove[MSG_SIZ]; // a bit generous?
8083
8084                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8085                 strcat(bookMove, bookHit);
8086                 message = bookMove;
8087                 cps = cps->other;
8088                 programStats.nodes = programStats.depth = programStats.time =
8089                 programStats.score = programStats.got_only_move = 0;
8090                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8091
8092                 if(cps->lastPing != cps->lastPong) {
8093                     savedMessage = message; // args for deferred call
8094                     savedState = cps;
8095                     ScheduleDelayedEvent(DeferredBookMove, 10);
8096                     return;
8097                 }
8098                 goto FakeBookMove;
8099         }
8100
8101         return;
8102     }
8103
8104     /* Set special modes for chess engines.  Later something general
8105      *  could be added here; for now there is just one kludge feature,
8106      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8107      *  when "xboard" is given as an interactive command.
8108      */
8109     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8110         cps->useSigint = FALSE;
8111         cps->useSigterm = FALSE;
8112     }
8113     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8114       ParseFeatures(message+8, cps);
8115       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8116     }
8117
8118     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8119                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8120       int dummy, s=6; char buf[MSG_SIZ];
8121       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8122       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8123       if(startedFromSetupPosition) return;
8124       ParseFEN(boards[0], &dummy, message+s);
8125       DrawPosition(TRUE, boards[0]);
8126       startedFromSetupPosition = TRUE;
8127       return;
8128     }
8129     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8130      * want this, I was asked to put it in, and obliged.
8131      */
8132     if (!strncmp(message, "setboard ", 9)) {
8133         Board initial_position;
8134
8135         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8136
8137         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8138             DisplayError(_("Bad FEN received from engine"), 0);
8139             return ;
8140         } else {
8141            Reset(TRUE, FALSE);
8142            CopyBoard(boards[0], initial_position);
8143            initialRulePlies = FENrulePlies;
8144            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8145            else gameMode = MachinePlaysBlack;
8146            DrawPosition(FALSE, boards[currentMove]);
8147         }
8148         return;
8149     }
8150
8151     /*
8152      * Look for communication commands
8153      */
8154     if (!strncmp(message, "telluser ", 9)) {
8155         if(message[9] == '\\' && message[10] == '\\')
8156             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8157         PlayTellSound();
8158         DisplayNote(message + 9);
8159         return;
8160     }
8161     if (!strncmp(message, "tellusererror ", 14)) {
8162         cps->userError = 1;
8163         if(message[14] == '\\' && message[15] == '\\')
8164             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8165         PlayTellSound();
8166         DisplayError(message + 14, 0);
8167         return;
8168     }
8169     if (!strncmp(message, "tellopponent ", 13)) {
8170       if (appData.icsActive) {
8171         if (loggedOn) {
8172           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8173           SendToICS(buf1);
8174         }
8175       } else {
8176         DisplayNote(message + 13);
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellothers ", 11)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8184           SendToICS(buf1);
8185         }
8186       }
8187       return;
8188     }
8189     if (!strncmp(message, "tellall ", 8)) {
8190       if (appData.icsActive) {
8191         if (loggedOn) {
8192           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8193           SendToICS(buf1);
8194         }
8195       } else {
8196         DisplayNote(message + 8);
8197       }
8198       return;
8199     }
8200     if (strncmp(message, "warning", 7) == 0) {
8201         /* Undocumented feature, use tellusererror in new code */
8202         DisplayError(message, 0);
8203         return;
8204     }
8205     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8206         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8207         strcat(realname, " query");
8208         AskQuestion(realname, buf2, buf1, cps->pr);
8209         return;
8210     }
8211     /* Commands from the engine directly to ICS.  We don't allow these to be
8212      *  sent until we are logged on. Crafty kibitzes have been known to
8213      *  interfere with the login process.
8214      */
8215     if (loggedOn) {
8216         if (!strncmp(message, "tellics ", 8)) {
8217             SendToICS(message + 8);
8218             SendToICS("\n");
8219             return;
8220         }
8221         if (!strncmp(message, "tellicsnoalias ", 15)) {
8222             SendToICS(ics_prefix);
8223             SendToICS(message + 15);
8224             SendToICS("\n");
8225             return;
8226         }
8227         /* The following are for backward compatibility only */
8228         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8229             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8230             SendToICS(ics_prefix);
8231             SendToICS(message);
8232             SendToICS("\n");
8233             return;
8234         }
8235     }
8236     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8237         return;
8238     }
8239     /*
8240      * If the move is illegal, cancel it and redraw the board.
8241      * Also deal with other error cases.  Matching is rather loose
8242      * here to accommodate engines written before the spec.
8243      */
8244     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8245         strncmp(message, "Error", 5) == 0) {
8246         if (StrStr(message, "name") ||
8247             StrStr(message, "rating") || StrStr(message, "?") ||
8248             StrStr(message, "result") || StrStr(message, "board") ||
8249             StrStr(message, "bk") || StrStr(message, "computer") ||
8250             StrStr(message, "variant") || StrStr(message, "hint") ||
8251             StrStr(message, "random") || StrStr(message, "depth") ||
8252             StrStr(message, "accepted")) {
8253             return;
8254         }
8255         if (StrStr(message, "protover")) {
8256           /* Program is responding to input, so it's apparently done
8257              initializing, and this error message indicates it is
8258              protocol version 1.  So we don't need to wait any longer
8259              for it to initialize and send feature commands. */
8260           FeatureDone(cps, 1);
8261           cps->protocolVersion = 1;
8262           return;
8263         }
8264         cps->maybeThinking = FALSE;
8265
8266         if (StrStr(message, "draw")) {
8267             /* Program doesn't have "draw" command */
8268             cps->sendDrawOffers = 0;
8269             return;
8270         }
8271         if (cps->sendTime != 1 &&
8272             (StrStr(message, "time") || StrStr(message, "otim"))) {
8273           /* Program apparently doesn't have "time" or "otim" command */
8274           cps->sendTime = 0;
8275           return;
8276         }
8277         if (StrStr(message, "analyze")) {
8278             cps->analysisSupport = FALSE;
8279             cps->analyzing = FALSE;
8280 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8281             EditGameEvent(); // [HGM] try to preserve loaded game
8282             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8283             DisplayError(buf2, 0);
8284             return;
8285         }
8286         if (StrStr(message, "(no matching move)st")) {
8287           /* Special kludge for GNU Chess 4 only */
8288           cps->stKludge = TRUE;
8289           SendTimeControl(cps, movesPerSession, timeControl,
8290                           timeIncrement, appData.searchDepth,
8291                           searchTime);
8292           return;
8293         }
8294         if (StrStr(message, "(no matching move)sd")) {
8295           /* Special kludge for GNU Chess 4 only */
8296           cps->sdKludge = TRUE;
8297           SendTimeControl(cps, movesPerSession, timeControl,
8298                           timeIncrement, appData.searchDepth,
8299                           searchTime);
8300           return;
8301         }
8302         if (!StrStr(message, "llegal")) {
8303             return;
8304         }
8305         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8306             gameMode == IcsIdle) return;
8307         if (forwardMostMove <= backwardMostMove) return;
8308         if (pausing) PauseEvent();
8309       if(appData.forceIllegal) {
8310             // [HGM] illegal: machine refused move; force position after move into it
8311           SendToProgram("force\n", cps);
8312           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8313                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8314                 // when black is to move, while there might be nothing on a2 or black
8315                 // might already have the move. So send the board as if white has the move.
8316                 // But first we must change the stm of the engine, as it refused the last move
8317                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8318                 if(WhiteOnMove(forwardMostMove)) {
8319                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8320                     SendBoard(cps, forwardMostMove); // kludgeless board
8321                 } else {
8322                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8323                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8324                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8325                 }
8326           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8327             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8328                  gameMode == TwoMachinesPlay)
8329               SendToProgram("go\n", cps);
8330             return;
8331       } else
8332         if (gameMode == PlayFromGameFile) {
8333             /* Stop reading this game file */
8334             gameMode = EditGame;
8335             ModeHighlight();
8336         }
8337         /* [HGM] illegal-move claim should forfeit game when Xboard */
8338         /* only passes fully legal moves                            */
8339         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8340             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8341                                 "False illegal-move claim", GE_XBOARD );
8342             return; // do not take back move we tested as valid
8343         }
8344         currentMove = forwardMostMove-1;
8345         DisplayMove(currentMove-1); /* before DisplayMoveError */
8346         SwitchClocks(forwardMostMove-1); // [HGM] race
8347         DisplayBothClocks();
8348         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8349                 parseList[currentMove], _(cps->which));
8350         DisplayMoveError(buf1);
8351         DrawPosition(FALSE, boards[currentMove]);
8352
8353         SetUserThinkingEnables();
8354         return;
8355     }
8356     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8357         /* Program has a broken "time" command that
8358            outputs a string not ending in newline.
8359            Don't use it. */
8360         cps->sendTime = 0;
8361     }
8362
8363     /*
8364      * If chess program startup fails, exit with an error message.
8365      * Attempts to recover here are futile.
8366      */
8367     if ((StrStr(message, "unknown host") != NULL)
8368         || (StrStr(message, "No remote directory") != NULL)
8369         || (StrStr(message, "not found") != NULL)
8370         || (StrStr(message, "No such file") != NULL)
8371         || (StrStr(message, "can't alloc") != NULL)
8372         || (StrStr(message, "Permission denied") != NULL)) {
8373
8374         cps->maybeThinking = FALSE;
8375         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8376                 _(cps->which), cps->program, cps->host, message);
8377         RemoveInputSource(cps->isr);
8378         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8379             if(cps == &first) appData.noChessProgram = TRUE;
8380             DisplayError(buf1, 0);
8381         }
8382         return;
8383     }
8384
8385     /*
8386      * Look for hint output
8387      */
8388     if (sscanf(message, "Hint: %s", buf1) == 1) {
8389         if (cps == &first && hintRequested) {
8390             hintRequested = FALSE;
8391             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8392                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8393                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8394                                     PosFlags(forwardMostMove),
8395                                     fromY, fromX, toY, toX, promoChar, buf1);
8396                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8397                 DisplayInformation(buf2);
8398             } else {
8399                 /* Hint move could not be parsed!? */
8400               snprintf(buf2, sizeof(buf2),
8401                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8402                         buf1, _(cps->which));
8403                 DisplayError(buf2, 0);
8404             }
8405         } else {
8406           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8407         }
8408         return;
8409     }
8410
8411     /*
8412      * Ignore other messages if game is not in progress
8413      */
8414     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8415         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8416
8417     /*
8418      * look for win, lose, draw, or draw offer
8419      */
8420     if (strncmp(message, "1-0", 3) == 0) {
8421         char *p, *q, *r = "";
8422         p = strchr(message, '{');
8423         if (p) {
8424             q = strchr(p, '}');
8425             if (q) {
8426                 *q = NULLCHAR;
8427                 r = p + 1;
8428             }
8429         }
8430         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8431         return;
8432     } else if (strncmp(message, "0-1", 3) == 0) {
8433         char *p, *q, *r = "";
8434         p = strchr(message, '{');
8435         if (p) {
8436             q = strchr(p, '}');
8437             if (q) {
8438                 *q = NULLCHAR;
8439                 r = p + 1;
8440             }
8441         }
8442         /* Kludge for Arasan 4.1 bug */
8443         if (strcmp(r, "Black resigns") == 0) {
8444             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8445             return;
8446         }
8447         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8448         return;
8449     } else if (strncmp(message, "1/2", 3) == 0) {
8450         char *p, *q, *r = "";
8451         p = strchr(message, '{');
8452         if (p) {
8453             q = strchr(p, '}');
8454             if (q) {
8455                 *q = NULLCHAR;
8456                 r = p + 1;
8457             }
8458         }
8459
8460         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8461         return;
8462
8463     } else if (strncmp(message, "White resign", 12) == 0) {
8464         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8465         return;
8466     } else if (strncmp(message, "Black resign", 12) == 0) {
8467         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "White matches", 13) == 0 ||
8470                strncmp(message, "Black matches", 13) == 0   ) {
8471         /* [HGM] ignore GNUShogi noises */
8472         return;
8473     } else if (strncmp(message, "White", 5) == 0 &&
8474                message[5] != '(' &&
8475                StrStr(message, "Black") == NULL) {
8476         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strncmp(message, "Black", 5) == 0 &&
8479                message[5] != '(') {
8480         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8481         return;
8482     } else if (strcmp(message, "resign") == 0 ||
8483                strcmp(message, "computer resigns") == 0) {
8484         switch (gameMode) {
8485           case MachinePlaysBlack:
8486           case IcsPlayingBlack:
8487             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8488             break;
8489           case MachinePlaysWhite:
8490           case IcsPlayingWhite:
8491             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8492             break;
8493           case TwoMachinesPlay:
8494             if (cps->twoMachinesColor[0] == 'w')
8495               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8496             else
8497               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8498             break;
8499           default:
8500             /* can't happen */
8501             break;
8502         }
8503         return;
8504     } else if (strncmp(message, "opponent mates", 14) == 0) {
8505         switch (gameMode) {
8506           case MachinePlaysBlack:
8507           case IcsPlayingBlack:
8508             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8509             break;
8510           case MachinePlaysWhite:
8511           case IcsPlayingWhite:
8512             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8513             break;
8514           case TwoMachinesPlay:
8515             if (cps->twoMachinesColor[0] == 'w')
8516               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8517             else
8518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8519             break;
8520           default:
8521             /* can't happen */
8522             break;
8523         }
8524         return;
8525     } else if (strncmp(message, "computer mates", 14) == 0) {
8526         switch (gameMode) {
8527           case MachinePlaysBlack:
8528           case IcsPlayingBlack:
8529             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8530             break;
8531           case MachinePlaysWhite:
8532           case IcsPlayingWhite:
8533             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8534             break;
8535           case TwoMachinesPlay:
8536             if (cps->twoMachinesColor[0] == 'w')
8537               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8538             else
8539               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8540             break;
8541           default:
8542             /* can't happen */
8543             break;
8544         }
8545         return;
8546     } else if (strncmp(message, "checkmate", 9) == 0) {
8547         if (WhiteOnMove(forwardMostMove)) {
8548             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8549         } else {
8550             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8551         }
8552         return;
8553     } else if (strstr(message, "Draw") != NULL ||
8554                strstr(message, "game is a draw") != NULL) {
8555         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8556         return;
8557     } else if (strstr(message, "offer") != NULL &&
8558                strstr(message, "draw") != NULL) {
8559 #if ZIPPY
8560         if (appData.zippyPlay && first.initDone) {
8561             /* Relay offer to ICS */
8562             SendToICS(ics_prefix);
8563             SendToICS("draw\n");
8564         }
8565 #endif
8566         cps->offeredDraw = 2; /* valid until this engine moves twice */
8567         if (gameMode == TwoMachinesPlay) {
8568             if (cps->other->offeredDraw) {
8569                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8570             /* [HGM] in two-machine mode we delay relaying draw offer      */
8571             /* until after we also have move, to see if it is really claim */
8572             }
8573         } else if (gameMode == MachinePlaysWhite ||
8574                    gameMode == MachinePlaysBlack) {
8575           if (userOfferedDraw) {
8576             DisplayInformation(_("Machine accepts your draw offer"));
8577             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8578           } else {
8579             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8580           }
8581         }
8582     }
8583
8584
8585     /*
8586      * Look for thinking output
8587      */
8588     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8589           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8590                                 ) {
8591         int plylev, mvleft, mvtot, curscore, time;
8592         char mvname[MOVE_LEN];
8593         u64 nodes; // [DM]
8594         char plyext;
8595         int ignore = FALSE;
8596         int prefixHint = FALSE;
8597         mvname[0] = NULLCHAR;
8598
8599         switch (gameMode) {
8600           case MachinePlaysBlack:
8601           case IcsPlayingBlack:
8602             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8603             break;
8604           case MachinePlaysWhite:
8605           case IcsPlayingWhite:
8606             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8607             break;
8608           case AnalyzeMode:
8609           case AnalyzeFile:
8610             break;
8611           case IcsObserving: /* [DM] icsEngineAnalyze */
8612             if (!appData.icsEngineAnalyze) ignore = TRUE;
8613             break;
8614           case TwoMachinesPlay:
8615             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8616                 ignore = TRUE;
8617             }
8618             break;
8619           default:
8620             ignore = TRUE;
8621             break;
8622         }
8623
8624         if (!ignore) {
8625             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8626             buf1[0] = NULLCHAR;
8627             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8628                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8629
8630                 if (plyext != ' ' && plyext != '\t') {
8631                     time *= 100;
8632                 }
8633
8634                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8635                 if( cps->scoreIsAbsolute &&
8636                     ( gameMode == MachinePlaysBlack ||
8637                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8638                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8639                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8640                      !WhiteOnMove(currentMove)
8641                     ) )
8642                 {
8643                     curscore = -curscore;
8644                 }
8645
8646                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8647
8648                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8649                         char buf[MSG_SIZ];
8650                         FILE *f;
8651                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8652                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8653                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8654                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8655                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8656                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8657                                 fclose(f);
8658                         } else DisplayError(_("failed writing PV"), 0);
8659                 }
8660
8661                 tempStats.depth = plylev;
8662                 tempStats.nodes = nodes;
8663                 tempStats.time = time;
8664                 tempStats.score = curscore;
8665                 tempStats.got_only_move = 0;
8666
8667                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8668                         int ticklen;
8669
8670                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8671                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8672                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8673                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8674                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8675                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8676                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8677                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8678                 }
8679
8680                 /* Buffer overflow protection */
8681                 if (pv[0] != NULLCHAR) {
8682                     if (strlen(pv) >= sizeof(tempStats.movelist)
8683                         && appData.debugMode) {
8684                         fprintf(debugFP,
8685                                 "PV is too long; using the first %u bytes.\n",
8686                                 (unsigned) sizeof(tempStats.movelist) - 1);
8687                     }
8688
8689                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8690                 } else {
8691                     sprintf(tempStats.movelist, " no PV\n");
8692                 }
8693
8694                 if (tempStats.seen_stat) {
8695                     tempStats.ok_to_send = 1;
8696                 }
8697
8698                 if (strchr(tempStats.movelist, '(') != NULL) {
8699                     tempStats.line_is_book = 1;
8700                     tempStats.nr_moves = 0;
8701                     tempStats.moves_left = 0;
8702                 } else {
8703                     tempStats.line_is_book = 0;
8704                 }
8705
8706                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8707                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8708
8709                 SendProgramStatsToFrontend( cps, &tempStats );
8710
8711                 /*
8712                     [AS] Protect the thinkOutput buffer from overflow... this
8713                     is only useful if buf1 hasn't overflowed first!
8714                 */
8715                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8716                          plylev,
8717                          (gameMode == TwoMachinesPlay ?
8718                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8719                          ((double) curscore) / 100.0,
8720                          prefixHint ? lastHint : "",
8721                          prefixHint ? " " : "" );
8722
8723                 if( buf1[0] != NULLCHAR ) {
8724                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8725
8726                     if( strlen(pv) > max_len ) {
8727                         if( appData.debugMode) {
8728                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8729                         }
8730                         pv[max_len+1] = '\0';
8731                     }
8732
8733                     strcat( thinkOutput, pv);
8734                 }
8735
8736                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8737                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8738                     DisplayMove(currentMove - 1);
8739                 }
8740                 return;
8741
8742             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8743                 /* crafty (9.25+) says "(only move) <move>"
8744                  * if there is only 1 legal move
8745                  */
8746                 sscanf(p, "(only move) %s", buf1);
8747                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8748                 sprintf(programStats.movelist, "%s (only move)", buf1);
8749                 programStats.depth = 1;
8750                 programStats.nr_moves = 1;
8751                 programStats.moves_left = 1;
8752                 programStats.nodes = 1;
8753                 programStats.time = 1;
8754                 programStats.got_only_move = 1;
8755
8756                 /* Not really, but we also use this member to
8757                    mean "line isn't going to change" (Crafty
8758                    isn't searching, so stats won't change) */
8759                 programStats.line_is_book = 1;
8760
8761                 SendProgramStatsToFrontend( cps, &programStats );
8762
8763                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8764                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8765                     DisplayMove(currentMove - 1);
8766                 }
8767                 return;
8768             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8769                               &time, &nodes, &plylev, &mvleft,
8770                               &mvtot, mvname) >= 5) {
8771                 /* The stat01: line is from Crafty (9.29+) in response
8772                    to the "." command */
8773                 programStats.seen_stat = 1;
8774                 cps->maybeThinking = TRUE;
8775
8776                 if (programStats.got_only_move || !appData.periodicUpdates)
8777                   return;
8778
8779                 programStats.depth = plylev;
8780                 programStats.time = time;
8781                 programStats.nodes = nodes;
8782                 programStats.moves_left = mvleft;
8783                 programStats.nr_moves = mvtot;
8784                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8785                 programStats.ok_to_send = 1;
8786                 programStats.movelist[0] = '\0';
8787
8788                 SendProgramStatsToFrontend( cps, &programStats );
8789
8790                 return;
8791
8792             } else if (strncmp(message,"++",2) == 0) {
8793                 /* Crafty 9.29+ outputs this */
8794                 programStats.got_fail = 2;
8795                 return;
8796
8797             } else if (strncmp(message,"--",2) == 0) {
8798                 /* Crafty 9.29+ outputs this */
8799                 programStats.got_fail = 1;
8800                 return;
8801
8802             } else if (thinkOutput[0] != NULLCHAR &&
8803                        strncmp(message, "    ", 4) == 0) {
8804                 unsigned message_len;
8805
8806                 p = message;
8807                 while (*p && *p == ' ') p++;
8808
8809                 message_len = strlen( p );
8810
8811                 /* [AS] Avoid buffer overflow */
8812                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8813                     strcat(thinkOutput, " ");
8814                     strcat(thinkOutput, p);
8815                 }
8816
8817                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8818                     strcat(programStats.movelist, " ");
8819                     strcat(programStats.movelist, p);
8820                 }
8821
8822                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8823                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8824                     DisplayMove(currentMove - 1);
8825                 }
8826                 return;
8827             }
8828         }
8829         else {
8830             buf1[0] = NULLCHAR;
8831
8832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8834             {
8835                 ChessProgramStats cpstats;
8836
8837                 if (plyext != ' ' && plyext != '\t') {
8838                     time *= 100;
8839                 }
8840
8841                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8842                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8843                     curscore = -curscore;
8844                 }
8845
8846                 cpstats.depth = plylev;
8847                 cpstats.nodes = nodes;
8848                 cpstats.time = time;
8849                 cpstats.score = curscore;
8850                 cpstats.got_only_move = 0;
8851                 cpstats.movelist[0] = '\0';
8852
8853                 if (buf1[0] != NULLCHAR) {
8854                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8855                 }
8856
8857                 cpstats.ok_to_send = 0;
8858                 cpstats.line_is_book = 0;
8859                 cpstats.nr_moves = 0;
8860                 cpstats.moves_left = 0;
8861
8862                 SendProgramStatsToFrontend( cps, &cpstats );
8863             }
8864         }
8865     }
8866 }
8867
8868
8869 /* Parse a game score from the character string "game", and
8870    record it as the history of the current game.  The game
8871    score is NOT assumed to start from the standard position.
8872    The display is not updated in any way.
8873    */
8874 void
8875 ParseGameHistory (char *game)
8876 {
8877     ChessMove moveType;
8878     int fromX, fromY, toX, toY, boardIndex;
8879     char promoChar;
8880     char *p, *q;
8881     char buf[MSG_SIZ];
8882
8883     if (appData.debugMode)
8884       fprintf(debugFP, "Parsing game history: %s\n", game);
8885
8886     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8887     gameInfo.site = StrSave(appData.icsHost);
8888     gameInfo.date = PGNDate();
8889     gameInfo.round = StrSave("-");
8890
8891     /* Parse out names of players */
8892     while (*game == ' ') game++;
8893     p = buf;
8894     while (*game != ' ') *p++ = *game++;
8895     *p = NULLCHAR;
8896     gameInfo.white = StrSave(buf);
8897     while (*game == ' ') game++;
8898     p = buf;
8899     while (*game != ' ' && *game != '\n') *p++ = *game++;
8900     *p = NULLCHAR;
8901     gameInfo.black = StrSave(buf);
8902
8903     /* Parse moves */
8904     boardIndex = blackPlaysFirst ? 1 : 0;
8905     yynewstr(game);
8906     for (;;) {
8907         yyboardindex = boardIndex;
8908         moveType = (ChessMove) Myylex();
8909         switch (moveType) {
8910           case IllegalMove:             /* maybe suicide chess, etc. */
8911   if (appData.debugMode) {
8912     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8913     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8914     setbuf(debugFP, NULL);
8915   }
8916           case WhitePromotion:
8917           case BlackPromotion:
8918           case WhiteNonPromotion:
8919           case BlackNonPromotion:
8920           case NormalMove:
8921           case WhiteCapturesEnPassant:
8922           case BlackCapturesEnPassant:
8923           case WhiteKingSideCastle:
8924           case WhiteQueenSideCastle:
8925           case BlackKingSideCastle:
8926           case BlackQueenSideCastle:
8927           case WhiteKingSideCastleWild:
8928           case WhiteQueenSideCastleWild:
8929           case BlackKingSideCastleWild:
8930           case BlackQueenSideCastleWild:
8931           /* PUSH Fabien */
8932           case WhiteHSideCastleFR:
8933           case WhiteASideCastleFR:
8934           case BlackHSideCastleFR:
8935           case BlackASideCastleFR:
8936           /* POP Fabien */
8937             fromX = currentMoveString[0] - AAA;
8938             fromY = currentMoveString[1] - ONE;
8939             toX = currentMoveString[2] - AAA;
8940             toY = currentMoveString[3] - ONE;
8941             promoChar = currentMoveString[4];
8942             break;
8943           case WhiteDrop:
8944           case BlackDrop:
8945             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8946             fromX = moveType == WhiteDrop ?
8947               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8948             (int) CharToPiece(ToLower(currentMoveString[0]));
8949             fromY = DROP_RANK;
8950             toX = currentMoveString[2] - AAA;
8951             toY = currentMoveString[3] - ONE;
8952             promoChar = NULLCHAR;
8953             break;
8954           case AmbiguousMove:
8955             /* bug? */
8956             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8957   if (appData.debugMode) {
8958     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8959     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8960     setbuf(debugFP, NULL);
8961   }
8962             DisplayError(buf, 0);
8963             return;
8964           case ImpossibleMove:
8965             /* bug? */
8966             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8967   if (appData.debugMode) {
8968     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8970     setbuf(debugFP, NULL);
8971   }
8972             DisplayError(buf, 0);
8973             return;
8974           case EndOfFile:
8975             if (boardIndex < backwardMostMove) {
8976                 /* Oops, gap.  How did that happen? */
8977                 DisplayError(_("Gap in move list"), 0);
8978                 return;
8979             }
8980             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8981             if (boardIndex > forwardMostMove) {
8982                 forwardMostMove = boardIndex;
8983             }
8984             return;
8985           case ElapsedTime:
8986             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8987                 strcat(parseList[boardIndex-1], " ");
8988                 strcat(parseList[boardIndex-1], yy_text);
8989             }
8990             continue;
8991           case Comment:
8992           case PGNTag:
8993           case NAG:
8994           default:
8995             /* ignore */
8996             continue;
8997           case WhiteWins:
8998           case BlackWins:
8999           case GameIsDrawn:
9000           case GameUnfinished:
9001             if (gameMode == IcsExamining) {
9002                 if (boardIndex < backwardMostMove) {
9003                     /* Oops, gap.  How did that happen? */
9004                     return;
9005                 }
9006                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9007                 return;
9008             }
9009             gameInfo.result = moveType;
9010             p = strchr(yy_text, '{');
9011             if (p == NULL) p = strchr(yy_text, '(');
9012             if (p == NULL) {
9013                 p = yy_text;
9014                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9015             } else {
9016                 q = strchr(p, *p == '{' ? '}' : ')');
9017                 if (q != NULL) *q = NULLCHAR;
9018                 p++;
9019             }
9020             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9021             gameInfo.resultDetails = StrSave(p);
9022             continue;
9023         }
9024         if (boardIndex >= forwardMostMove &&
9025             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9026             backwardMostMove = blackPlaysFirst ? 1 : 0;
9027             return;
9028         }
9029         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9030                                  fromY, fromX, toY, toX, promoChar,
9031                                  parseList[boardIndex]);
9032         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9033         /* currentMoveString is set as a side-effect of yylex */
9034         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9035         strcat(moveList[boardIndex], "\n");
9036         boardIndex++;
9037         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9038         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9039           case MT_NONE:
9040           case MT_STALEMATE:
9041           default:
9042             break;
9043           case MT_CHECK:
9044             if(gameInfo.variant != VariantShogi)
9045                 strcat(parseList[boardIndex - 1], "+");
9046             break;
9047           case MT_CHECKMATE:
9048           case MT_STAINMATE:
9049             strcat(parseList[boardIndex - 1], "#");
9050             break;
9051         }
9052     }
9053 }
9054
9055
9056 /* Apply a move to the given board  */
9057 void
9058 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9059 {
9060   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9061   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9062
9063     /* [HGM] compute & store e.p. status and castling rights for new position */
9064     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9065
9066       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9067       oldEP = (signed char)board[EP_STATUS];
9068       board[EP_STATUS] = EP_NONE;
9069
9070   if (fromY == DROP_RANK) {
9071         /* must be first */
9072         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9073             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9074             return;
9075         }
9076         piece = board[toY][toX] = (ChessSquare) fromX;
9077   } else {
9078       int i;
9079
9080       if( board[toY][toX] != EmptySquare )
9081            board[EP_STATUS] = EP_CAPTURE;
9082
9083       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9084            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9085                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9086       } else
9087       if( board[fromY][fromX] == WhitePawn ) {
9088            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9089                board[EP_STATUS] = EP_PAWN_MOVE;
9090            if( toY-fromY==2) {
9091                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9092                         gameInfo.variant != VariantBerolina || toX < fromX)
9093                       board[EP_STATUS] = toX | berolina;
9094                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX > fromX)
9096                       board[EP_STATUS] = toX;
9097            }
9098       } else
9099       if( board[fromY][fromX] == BlackPawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY== -2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110        }
9111
9112        for(i=0; i<nrCastlingRights; i++) {
9113            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9114               board[CASTLING][i] == toX   && castlingRank[i] == toY
9115              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9116        }
9117
9118      if (fromX == toX && fromY == toY) return;
9119
9120      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9121      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9122      if(gameInfo.variant == VariantKnightmate)
9123          king += (int) WhiteUnicorn - (int) WhiteKing;
9124
9125     /* Code added by Tord: */
9126     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9127     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9128         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9129       board[fromY][fromX] = EmptySquare;
9130       board[toY][toX] = EmptySquare;
9131       if((toX > fromX) != (piece == WhiteRook)) {
9132         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9133       } else {
9134         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9135       }
9136     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9137                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9138       board[fromY][fromX] = EmptySquare;
9139       board[toY][toX] = EmptySquare;
9140       if((toX > fromX) != (piece == BlackRook)) {
9141         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9142       } else {
9143         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9144       }
9145     /* End of code added by Tord */
9146
9147     } else if (board[fromY][fromX] == king
9148         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9149         && toY == fromY && toX > fromX+1) {
9150         board[fromY][fromX] = EmptySquare;
9151         board[toY][toX] = king;
9152         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9153         board[fromY][BOARD_RGHT-1] = EmptySquare;
9154     } else if (board[fromY][fromX] == king
9155         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9156                && toY == fromY && toX < fromX-1) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = king;
9159         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9160         board[fromY][BOARD_LEFT] = EmptySquare;
9161     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9162                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9163                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9164                ) {
9165         /* white pawn promotion */
9166         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9167         if(gameInfo.variant==VariantBughouse ||
9168            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9169             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9170         board[fromY][fromX] = EmptySquare;
9171     } else if ((fromY >= BOARD_HEIGHT>>1)
9172                && (toX != fromX)
9173                && gameInfo.variant != VariantXiangqi
9174                && gameInfo.variant != VariantBerolina
9175                && (board[fromY][fromX] == WhitePawn)
9176                && (board[toY][toX] == EmptySquare)) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = WhitePawn;
9179         captured = board[toY - 1][toX];
9180         board[toY - 1][toX] = EmptySquare;
9181     } else if ((fromY == BOARD_HEIGHT-4)
9182                && (toX == fromX)
9183                && gameInfo.variant == VariantBerolina
9184                && (board[fromY][fromX] == WhitePawn)
9185                && (board[toY][toX] == EmptySquare)) {
9186         board[fromY][fromX] = EmptySquare;
9187         board[toY][toX] = WhitePawn;
9188         if(oldEP & EP_BEROLIN_A) {
9189                 captured = board[fromY][fromX-1];
9190                 board[fromY][fromX-1] = EmptySquare;
9191         }else{  captured = board[fromY][fromX+1];
9192                 board[fromY][fromX+1] = EmptySquare;
9193         }
9194     } else if (board[fromY][fromX] == king
9195         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9196                && toY == fromY && toX > fromX+1) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = king;
9199         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9200         board[fromY][BOARD_RGHT-1] = EmptySquare;
9201     } else if (board[fromY][fromX] == king
9202         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9203                && toY == fromY && toX < fromX-1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = king;
9206         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9207         board[fromY][BOARD_LEFT] = EmptySquare;
9208     } else if (fromY == 7 && fromX == 3
9209                && board[fromY][fromX] == BlackKing
9210                && toY == 7 && toX == 5) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = BlackKing;
9213         board[fromY][7] = EmptySquare;
9214         board[toY][4] = BlackRook;
9215     } else if (fromY == 7 && fromX == 3
9216                && board[fromY][fromX] == BlackKing
9217                && toY == 7 && toX == 1) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackKing;
9220         board[fromY][0] = EmptySquare;
9221         board[toY][2] = BlackRook;
9222     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9223                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9224                && toY < promoRank && promoChar
9225                ) {
9226         /* black pawn promotion */
9227         board[toY][toX] = CharToPiece(ToLower(promoChar));
9228         if(gameInfo.variant==VariantBughouse ||
9229            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9230             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9231         board[fromY][fromX] = EmptySquare;
9232     } else if ((fromY < BOARD_HEIGHT>>1)
9233                && (toX != fromX)
9234                && gameInfo.variant != VariantXiangqi
9235                && gameInfo.variant != VariantBerolina
9236                && (board[fromY][fromX] == BlackPawn)
9237                && (board[toY][toX] == EmptySquare)) {
9238         board[fromY][fromX] = EmptySquare;
9239         board[toY][toX] = BlackPawn;
9240         captured = board[toY + 1][toX];
9241         board[toY + 1][toX] = EmptySquare;
9242     } else if ((fromY == 3)
9243                && (toX == fromX)
9244                && gameInfo.variant == VariantBerolina
9245                && (board[fromY][fromX] == BlackPawn)
9246                && (board[toY][toX] == EmptySquare)) {
9247         board[fromY][fromX] = EmptySquare;
9248         board[toY][toX] = BlackPawn;
9249         if(oldEP & EP_BEROLIN_A) {
9250                 captured = board[fromY][fromX-1];
9251                 board[fromY][fromX-1] = EmptySquare;
9252         }else{  captured = board[fromY][fromX+1];
9253                 board[fromY][fromX+1] = EmptySquare;
9254         }
9255     } else {
9256         board[toY][toX] = board[fromY][fromX];
9257         board[fromY][fromX] = EmptySquare;
9258     }
9259   }
9260
9261     if (gameInfo.holdingsWidth != 0) {
9262
9263       /* !!A lot more code needs to be written to support holdings  */
9264       /* [HGM] OK, so I have written it. Holdings are stored in the */
9265       /* penultimate board files, so they are automaticlly stored   */
9266       /* in the game history.                                       */
9267       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9268                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9269         /* Delete from holdings, by decreasing count */
9270         /* and erasing image if necessary            */
9271         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9272         if(p < (int) BlackPawn) { /* white drop */
9273              p -= (int)WhitePawn;
9274                  p = PieceToNumber((ChessSquare)p);
9275              if(p >= gameInfo.holdingsSize) p = 0;
9276              if(--board[p][BOARD_WIDTH-2] <= 0)
9277                   board[p][BOARD_WIDTH-1] = EmptySquare;
9278              if((int)board[p][BOARD_WIDTH-2] < 0)
9279                         board[p][BOARD_WIDTH-2] = 0;
9280         } else {                  /* black drop */
9281              p -= (int)BlackPawn;
9282                  p = PieceToNumber((ChessSquare)p);
9283              if(p >= gameInfo.holdingsSize) p = 0;
9284              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9285                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9286              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9287                         board[BOARD_HEIGHT-1-p][1] = 0;
9288         }
9289       }
9290       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9291           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9292         /* [HGM] holdings: Add to holdings, if holdings exist */
9293         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9294                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9295                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9296         }
9297         p = (int) captured;
9298         if (p >= (int) BlackPawn) {
9299           p -= (int)BlackPawn;
9300           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9301                   /* in Shogi restore piece to its original  first */
9302                   captured = (ChessSquare) (DEMOTED captured);
9303                   p = DEMOTED p;
9304           }
9305           p = PieceToNumber((ChessSquare)p);
9306           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9307           board[p][BOARD_WIDTH-2]++;
9308           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9309         } else {
9310           p -= (int)WhitePawn;
9311           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9312                   captured = (ChessSquare) (DEMOTED captured);
9313                   p = DEMOTED p;
9314           }
9315           p = PieceToNumber((ChessSquare)p);
9316           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9317           board[BOARD_HEIGHT-1-p][1]++;
9318           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9319         }
9320       }
9321     } else if (gameInfo.variant == VariantAtomic) {
9322       if (captured != EmptySquare) {
9323         int y, x;
9324         for (y = toY-1; y <= toY+1; y++) {
9325           for (x = toX-1; x <= toX+1; x++) {
9326             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9327                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9328               board[y][x] = EmptySquare;
9329             }
9330           }
9331         }
9332         board[toY][toX] = EmptySquare;
9333       }
9334     }
9335     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9336         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9337     } else
9338     if(promoChar == '+') {
9339         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9340         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9341     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9342         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9343     }
9344     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9345                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9346         // [HGM] superchess: take promotion piece out of holdings
9347         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9348         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9349             if(!--board[k][BOARD_WIDTH-2])
9350                 board[k][BOARD_WIDTH-1] = EmptySquare;
9351         } else {
9352             if(!--board[BOARD_HEIGHT-1-k][1])
9353                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9354         }
9355     }
9356
9357 }
9358
9359 /* Updates forwardMostMove */
9360 void
9361 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9362 {
9363 //    forwardMostMove++; // [HGM] bare: moved downstream
9364
9365     (void) CoordsToAlgebraic(boards[forwardMostMove],
9366                              PosFlags(forwardMostMove),
9367                              fromY, fromX, toY, toX, promoChar,
9368                              parseList[forwardMostMove]);
9369
9370     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9371         int timeLeft; static int lastLoadFlag=0; int king, piece;
9372         piece = boards[forwardMostMove][fromY][fromX];
9373         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9374         if(gameInfo.variant == VariantKnightmate)
9375             king += (int) WhiteUnicorn - (int) WhiteKing;
9376         if(forwardMostMove == 0) {
9377             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9378                 fprintf(serverMoves, "%s;", UserName());
9379             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9380                 fprintf(serverMoves, "%s;", second.tidy);
9381             fprintf(serverMoves, "%s;", first.tidy);
9382             if(gameMode == MachinePlaysWhite)
9383                 fprintf(serverMoves, "%s;", UserName());
9384             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9385                 fprintf(serverMoves, "%s;", second.tidy);
9386         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9387         lastLoadFlag = loadFlag;
9388         // print base move
9389         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9390         // print castling suffix
9391         if( toY == fromY && piece == king ) {
9392             if(toX-fromX > 1)
9393                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9394             if(fromX-toX >1)
9395                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9396         }
9397         // e.p. suffix
9398         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9399              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9400              boards[forwardMostMove][toY][toX] == EmptySquare
9401              && fromX != toX && fromY != toY)
9402                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9403         // promotion suffix
9404         if(promoChar != NULLCHAR)
9405                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9406         if(!loadFlag) {
9407                 char buf[MOVE_LEN*2], *p; int len;
9408             fprintf(serverMoves, "/%d/%d",
9409                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9410             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9411             else                      timeLeft = blackTimeRemaining/1000;
9412             fprintf(serverMoves, "/%d", timeLeft);
9413                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9414                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9415                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9416             fprintf(serverMoves, "/%s", buf);
9417         }
9418         fflush(serverMoves);
9419     }
9420
9421     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9422         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9423       return;
9424     }
9425     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9426     if (commentList[forwardMostMove+1] != NULL) {
9427         free(commentList[forwardMostMove+1]);
9428         commentList[forwardMostMove+1] = NULL;
9429     }
9430     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9431     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9432     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9433     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9434     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9435     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9436     adjustedClock = FALSE;
9437     gameInfo.result = GameUnfinished;
9438     if (gameInfo.resultDetails != NULL) {
9439         free(gameInfo.resultDetails);
9440         gameInfo.resultDetails = NULL;
9441     }
9442     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9443                               moveList[forwardMostMove - 1]);
9444     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9445       case MT_NONE:
9446       case MT_STALEMATE:
9447       default:
9448         break;
9449       case MT_CHECK:
9450         if(gameInfo.variant != VariantShogi)
9451             strcat(parseList[forwardMostMove - 1], "+");
9452         break;
9453       case MT_CHECKMATE:
9454       case MT_STAINMATE:
9455         strcat(parseList[forwardMostMove - 1], "#");
9456         break;
9457     }
9458
9459 }
9460
9461 /* Updates currentMove if not pausing */
9462 void
9463 ShowMove (int fromX, int fromY, int toX, int toY)
9464 {
9465     int instant = (gameMode == PlayFromGameFile) ?
9466         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9467     if(appData.noGUI) return;
9468     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9469         if (!instant) {
9470             if (forwardMostMove == currentMove + 1) {
9471                 AnimateMove(boards[forwardMostMove - 1],
9472                             fromX, fromY, toX, toY);
9473             }
9474             if (appData.highlightLastMove) {
9475                 SetHighlights(fromX, fromY, toX, toY);
9476             }
9477         }
9478         currentMove = forwardMostMove;
9479     }
9480
9481     if (instant) return;
9482
9483     DisplayMove(currentMove - 1);
9484     DrawPosition(FALSE, boards[currentMove]);
9485     DisplayBothClocks();
9486     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9487 }
9488
9489 void
9490 SendEgtPath (ChessProgramState *cps)
9491 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9492         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9493
9494         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9495
9496         while(*p) {
9497             char c, *q = name+1, *r, *s;
9498
9499             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9500             while(*p && *p != ',') *q++ = *p++;
9501             *q++ = ':'; *q = 0;
9502             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9503                 strcmp(name, ",nalimov:") == 0 ) {
9504                 // take nalimov path from the menu-changeable option first, if it is defined
9505               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9506                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9507             } else
9508             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9509                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9510                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9511                 s = r = StrStr(s, ":") + 1; // beginning of path info
9512                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9513                 c = *r; *r = 0;             // temporarily null-terminate path info
9514                     *--q = 0;               // strip of trailig ':' from name
9515                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9516                 *r = c;
9517                 SendToProgram(buf,cps);     // send egtbpath command for this format
9518             }
9519             if(*p == ',') p++; // read away comma to position for next format name
9520         }
9521 }
9522
9523 void
9524 InitChessProgram (ChessProgramState *cps, int setup)
9525 /* setup needed to setup FRC opening position */
9526 {
9527     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9528     if (appData.noChessProgram) return;
9529     hintRequested = FALSE;
9530     bookRequested = FALSE;
9531
9532     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9533     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9534     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9535     if(cps->memSize) { /* [HGM] memory */
9536       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9537         SendToProgram(buf, cps);
9538     }
9539     SendEgtPath(cps); /* [HGM] EGT */
9540     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9541       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9542         SendToProgram(buf, cps);
9543     }
9544
9545     SendToProgram(cps->initString, cps);
9546     if (gameInfo.variant != VariantNormal &&
9547         gameInfo.variant != VariantLoadable
9548         /* [HGM] also send variant if board size non-standard */
9549         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9550                                             ) {
9551       char *v = VariantName(gameInfo.variant);
9552       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9553         /* [HGM] in protocol 1 we have to assume all variants valid */
9554         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9555         DisplayFatalError(buf, 0, 1);
9556         return;
9557       }
9558
9559       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9560       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9561       if( gameInfo.variant == VariantXiangqi )
9562            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9563       if( gameInfo.variant == VariantShogi )
9564            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9565       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9566            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9567       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9568           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9569            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9570       if( gameInfo.variant == VariantCourier )
9571            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9572       if( gameInfo.variant == VariantSuper )
9573            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9574       if( gameInfo.variant == VariantGreat )
9575            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9576       if( gameInfo.variant == VariantSChess )
9577            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9578       if( gameInfo.variant == VariantGrand )
9579            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9580
9581       if(overruled) {
9582         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9583                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9584            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9585            if(StrStr(cps->variants, b) == NULL) {
9586                // specific sized variant not known, check if general sizing allowed
9587                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9588                    if(StrStr(cps->variants, "boardsize") == NULL) {
9589                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9590                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9591                        DisplayFatalError(buf, 0, 1);
9592                        return;
9593                    }
9594                    /* [HGM] here we really should compare with the maximum supported board size */
9595                }
9596            }
9597       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9598       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9599       SendToProgram(buf, cps);
9600     }
9601     currentlyInitializedVariant = gameInfo.variant;
9602
9603     /* [HGM] send opening position in FRC to first engine */
9604     if(setup) {
9605           SendToProgram("force\n", cps);
9606           SendBoard(cps, 0);
9607           /* engine is now in force mode! Set flag to wake it up after first move. */
9608           setboardSpoiledMachineBlack = 1;
9609     }
9610
9611     if (cps->sendICS) {
9612       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9613       SendToProgram(buf, cps);
9614     }
9615     cps->maybeThinking = FALSE;
9616     cps->offeredDraw = 0;
9617     if (!appData.icsActive) {
9618         SendTimeControl(cps, movesPerSession, timeControl,
9619                         timeIncrement, appData.searchDepth,
9620                         searchTime);
9621     }
9622     if (appData.showThinking
9623         // [HGM] thinking: four options require thinking output to be sent
9624         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9625                                 ) {
9626         SendToProgram("post\n", cps);
9627     }
9628     SendToProgram("hard\n", cps);
9629     if (!appData.ponderNextMove) {
9630         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9631            it without being sure what state we are in first.  "hard"
9632            is not a toggle, so that one is OK.
9633          */
9634         SendToProgram("easy\n", cps);
9635     }
9636     if (cps->usePing) {
9637       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9638       SendToProgram(buf, cps);
9639     }
9640     cps->initDone = TRUE;
9641     ClearEngineOutputPane(cps == &second);
9642 }
9643
9644
9645 void
9646 StartChessProgram (ChessProgramState *cps)
9647 {
9648     char buf[MSG_SIZ];
9649     int err;
9650
9651     if (appData.noChessProgram) return;
9652     cps->initDone = FALSE;
9653
9654     if (strcmp(cps->host, "localhost") == 0) {
9655         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9656     } else if (*appData.remoteShell == NULLCHAR) {
9657         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9658     } else {
9659         if (*appData.remoteUser == NULLCHAR) {
9660           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9661                     cps->program);
9662         } else {
9663           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9664                     cps->host, appData.remoteUser, cps->program);
9665         }
9666         err = StartChildProcess(buf, "", &cps->pr);
9667     }
9668
9669     if (err != 0) {
9670       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9671         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9672         if(cps != &first) return;
9673         appData.noChessProgram = TRUE;
9674         ThawUI();
9675         SetNCPMode();
9676 //      DisplayFatalError(buf, err, 1);
9677 //      cps->pr = NoProc;
9678 //      cps->isr = NULL;
9679         return;
9680     }
9681
9682     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9683     if (cps->protocolVersion > 1) {
9684       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9685       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9686       cps->comboCnt = 0;  //                and values of combo boxes
9687       SendToProgram(buf, cps);
9688     } else {
9689       SendToProgram("xboard\n", cps);
9690     }
9691 }
9692
9693 void
9694 TwoMachinesEventIfReady P((void))
9695 {
9696   static int curMess = 0;
9697   if (first.lastPing != first.lastPong) {
9698     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9699     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9700     return;
9701   }
9702   if (second.lastPing != second.lastPong) {
9703     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9704     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9705     return;
9706   }
9707   DisplayMessage("", ""); curMess = 0;
9708   ThawUI();
9709   TwoMachinesEvent();
9710 }
9711
9712 char *
9713 MakeName (char *template)
9714 {
9715     time_t clock;
9716     struct tm *tm;
9717     static char buf[MSG_SIZ];
9718     char *p = buf;
9719     int i;
9720
9721     clock = time((time_t *)NULL);
9722     tm = localtime(&clock);
9723
9724     while(*p++ = *template++) if(p[-1] == '%') {
9725         switch(*template++) {
9726           case 0:   *p = 0; return buf;
9727           case 'Y': i = tm->tm_year+1900; break;
9728           case 'y': i = tm->tm_year-100; break;
9729           case 'M': i = tm->tm_mon+1; break;
9730           case 'd': i = tm->tm_mday; break;
9731           case 'h': i = tm->tm_hour; break;
9732           case 'm': i = tm->tm_min; break;
9733           case 's': i = tm->tm_sec; break;
9734           default:  i = 0;
9735         }
9736         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9737     }
9738     return buf;
9739 }
9740
9741 int
9742 CountPlayers (char *p)
9743 {
9744     int n = 0;
9745     while(p = strchr(p, '\n')) p++, n++; // count participants
9746     return n;
9747 }
9748
9749 FILE *
9750 WriteTourneyFile (char *results, FILE *f)
9751 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9752     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9753     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9754         // create a file with tournament description
9755         fprintf(f, "-participants {%s}\n", appData.participants);
9756         fprintf(f, "-seedBase %d\n", appData.seedBase);
9757         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9758         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9759         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9760         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9761         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9762         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9763         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9764         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9765         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9766         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9767         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9768         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9769         if(searchTime > 0)
9770                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9771         else {
9772                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9773                 fprintf(f, "-tc %s\n", appData.timeControl);
9774                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9775         }
9776         fprintf(f, "-results \"%s\"\n", results);
9777     }
9778     return f;
9779 }
9780
9781 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9782
9783 void
9784 Substitute (char *participants, int expunge)
9785 {
9786     int i, changed, changes=0, nPlayers=0;
9787     char *p, *q, *r, buf[MSG_SIZ];
9788     if(participants == NULL) return;
9789     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9790     r = p = participants; q = appData.participants;
9791     while(*p && *p == *q) {
9792         if(*p == '\n') r = p+1, nPlayers++;
9793         p++; q++;
9794     }
9795     if(*p) { // difference
9796         while(*p && *p++ != '\n');
9797         while(*q && *q++ != '\n');
9798       changed = nPlayers;
9799         changes = 1 + (strcmp(p, q) != 0);
9800     }
9801     if(changes == 1) { // a single engine mnemonic was changed
9802         q = r; while(*q) nPlayers += (*q++ == '\n');
9803         p = buf; while(*r && (*p = *r++) != '\n') p++;
9804         *p = NULLCHAR;
9805         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9806         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9807         if(mnemonic[i]) { // The substitute is valid
9808             FILE *f;
9809             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9810                 flock(fileno(f), LOCK_EX);
9811                 ParseArgsFromFile(f);
9812                 fseek(f, 0, SEEK_SET);
9813                 FREE(appData.participants); appData.participants = participants;
9814                 if(expunge) { // erase results of replaced engine
9815                     int len = strlen(appData.results), w, b, dummy;
9816                     for(i=0; i<len; i++) {
9817                         Pairing(i, nPlayers, &w, &b, &dummy);
9818                         if((w == changed || b == changed) && appData.results[i] == '*') {
9819                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9820                             fclose(f);
9821                             return;
9822                         }
9823                     }
9824                     for(i=0; i<len; i++) {
9825                         Pairing(i, nPlayers, &w, &b, &dummy);
9826                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9827                     }
9828                 }
9829                 WriteTourneyFile(appData.results, f);
9830                 fclose(f); // release lock
9831                 return;
9832             }
9833         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9834     }
9835     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9836     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9837     free(participants);
9838     return;
9839 }
9840
9841 int
9842 CreateTourney (char *name)
9843 {
9844         FILE *f;
9845         if(matchMode && strcmp(name, appData.tourneyFile)) {
9846              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9847         }
9848         if(name[0] == NULLCHAR) {
9849             if(appData.participants[0])
9850                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9851             return 0;
9852         }
9853         f = fopen(name, "r");
9854         if(f) { // file exists
9855             ASSIGN(appData.tourneyFile, name);
9856             ParseArgsFromFile(f); // parse it
9857         } else {
9858             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9859             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9860                 DisplayError(_("Not enough participants"), 0);
9861                 return 0;
9862             }
9863             ASSIGN(appData.tourneyFile, name);
9864             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9865             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9866         }
9867         fclose(f);
9868         appData.noChessProgram = FALSE;
9869         appData.clockMode = TRUE;
9870         SetGNUMode();
9871         return 1;
9872 }
9873
9874 int
9875 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9876 {
9877     char buf[MSG_SIZ], *p, *q;
9878     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9879     skip = !all && group[0]; // if group requested, we start in skip mode
9880     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9881         p = names; q = buf; header = 0;
9882         while(*p && *p != '\n') *q++ = *p++;
9883         *q = 0;
9884         if(*p == '\n') p++;
9885         if(buf[0] == '#') {
9886             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9887             depth++; // we must be entering a new group
9888             if(all) continue; // suppress printing group headers when complete list requested
9889             header = 1;
9890             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9891         }
9892         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9893         if(engineList[i]) free(engineList[i]);
9894         engineList[i] = strdup(buf);
9895         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9896         if(engineMnemonic[i]) free(engineMnemonic[i]);
9897         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9898             strcat(buf, " (");
9899             sscanf(q + 8, "%s", buf + strlen(buf));
9900             strcat(buf, ")");
9901         }
9902         engineMnemonic[i] = strdup(buf);
9903         i++;
9904     }
9905     engineList[i] = engineMnemonic[i] = NULL;
9906     return i;
9907 }
9908
9909 // following implemented as macro to avoid type limitations
9910 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9911
9912 void
9913 SwapEngines (int n)
9914 {   // swap settings for first engine and other engine (so far only some selected options)
9915     int h;
9916     char *p;
9917     if(n == 0) return;
9918     SWAP(directory, p)
9919     SWAP(chessProgram, p)
9920     SWAP(isUCI, h)
9921     SWAP(hasOwnBookUCI, h)
9922     SWAP(protocolVersion, h)
9923     SWAP(reuse, h)
9924     SWAP(scoreIsAbsolute, h)
9925     SWAP(timeOdds, h)
9926     SWAP(logo, p)
9927     SWAP(pgnName, p)
9928     SWAP(pvSAN, h)
9929     SWAP(engOptions, p)
9930 }
9931
9932 int
9933 SetPlayer (int player, char *p)
9934 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9935     int i;
9936     char buf[MSG_SIZ], *engineName;
9937     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9938     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9939     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9940     if(mnemonic[i]) {
9941         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9942         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9943         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9944         ParseArgsFromString(buf);
9945     }
9946     free(engineName);
9947     return i;
9948 }
9949
9950 char *recentEngines;
9951
9952 void
9953 RecentEngineEvent (int nr)
9954 {
9955     int n;
9956 //    SwapEngines(1); // bump first to second
9957 //    ReplaceEngine(&second, 1); // and load it there
9958     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9959     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9960     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9961         ReplaceEngine(&first, 0);
9962         FloatToFront(&appData.recentEngineList, command[n]);
9963     }
9964 }
9965
9966 int
9967 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9968 {   // determine players from game number
9969     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9970
9971     if(appData.tourneyType == 0) {
9972         roundsPerCycle = (nPlayers - 1) | 1;
9973         pairingsPerRound = nPlayers / 2;
9974     } else if(appData.tourneyType > 0) {
9975         roundsPerCycle = nPlayers - appData.tourneyType;
9976         pairingsPerRound = appData.tourneyType;
9977     }
9978     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9979     gamesPerCycle = gamesPerRound * roundsPerCycle;
9980     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9981     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9982     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9983     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9984     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9985     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9986
9987     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9988     if(appData.roundSync) *syncInterval = gamesPerRound;
9989
9990     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9991
9992     if(appData.tourneyType == 0) {
9993         if(curPairing == (nPlayers-1)/2 ) {
9994             *whitePlayer = curRound;
9995             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9996         } else {
9997             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9998             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9999             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10000             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10001         }
10002     } else if(appData.tourneyType > 1) {
10003         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10004         *whitePlayer = curRound + appData.tourneyType;
10005     } else if(appData.tourneyType > 0) {
10006         *whitePlayer = curPairing;
10007         *blackPlayer = curRound + appData.tourneyType;
10008     }
10009
10010     // take care of white/black alternation per round. 
10011     // For cycles and games this is already taken care of by default, derived from matchGame!
10012     return curRound & 1;
10013 }
10014
10015 int
10016 NextTourneyGame (int nr, int *swapColors)
10017 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10018     char *p, *q;
10019     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10020     FILE *tf;
10021     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10022     tf = fopen(appData.tourneyFile, "r");
10023     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10024     ParseArgsFromFile(tf); fclose(tf);
10025     InitTimeControls(); // TC might be altered from tourney file
10026
10027     nPlayers = CountPlayers(appData.participants); // count participants
10028     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10029     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10030
10031     if(syncInterval) {
10032         p = q = appData.results;
10033         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10034         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10035             DisplayMessage(_("Waiting for other game(s)"),"");
10036             waitingForGame = TRUE;
10037             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10038             return 0;
10039         }
10040         waitingForGame = FALSE;
10041     }
10042
10043     if(appData.tourneyType < 0) {
10044         if(nr>=0 && !pairingReceived) {
10045             char buf[1<<16];
10046             if(pairing.pr == NoProc) {
10047                 if(!appData.pairingEngine[0]) {
10048                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10049                     return 0;
10050                 }
10051                 StartChessProgram(&pairing); // starts the pairing engine
10052             }
10053             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10054             SendToProgram(buf, &pairing);
10055             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10056             SendToProgram(buf, &pairing);
10057             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10058         }
10059         pairingReceived = 0;                              // ... so we continue here 
10060         *swapColors = 0;
10061         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10062         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10063         matchGame = 1; roundNr = nr / syncInterval + 1;
10064     }
10065
10066     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10067
10068     // redefine engines, engine dir, etc.
10069     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10070     if(first.pr == NoProc) {
10071       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10072       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10073     }
10074     if(second.pr == NoProc) {
10075       SwapEngines(1);
10076       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10077       SwapEngines(1);         // and make that valid for second engine by swapping
10078       InitEngine(&second, 1);
10079     }
10080     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10081     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10082     return 1;
10083 }
10084
10085 void
10086 NextMatchGame ()
10087 {   // performs game initialization that does not invoke engines, and then tries to start the game
10088     int res, firstWhite, swapColors = 0;
10089     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10090     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
10091         char buf[MSG_SIZ];
10092         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10093         if(strcmp(buf, currentDebugFile)) { // name has changed
10094             FILE *f = fopen(buf, "w");
10095             if(f) { // if opening the new file failed, just keep using the old one
10096                 ASSIGN(currentDebugFile, buf);
10097                 fclose(debugFP);
10098                 debugFP = f;
10099             }
10100             if(appData.serverFileName) {
10101                 if(serverFP) fclose(serverFP);
10102                 serverFP = fopen(appData.serverFileName, "w");
10103                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10104                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10105             }
10106         }
10107     }
10108     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10109     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10110     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10111     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10112     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10113     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10114     Reset(FALSE, first.pr != NoProc);
10115     res = LoadGameOrPosition(matchGame); // setup game
10116     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10117     if(!res) return; // abort when bad game/pos file
10118     TwoMachinesEvent();
10119 }
10120
10121 void
10122 UserAdjudicationEvent (int result)
10123 {
10124     ChessMove gameResult = GameIsDrawn;
10125
10126     if( result > 0 ) {
10127         gameResult = WhiteWins;
10128     }
10129     else if( result < 0 ) {
10130         gameResult = BlackWins;
10131     }
10132
10133     if( gameMode == TwoMachinesPlay ) {
10134         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10135     }
10136 }
10137
10138
10139 // [HGM] save: calculate checksum of game to make games easily identifiable
10140 int
10141 StringCheckSum (char *s)
10142 {
10143         int i = 0;
10144         if(s==NULL) return 0;
10145         while(*s) i = i*259 + *s++;
10146         return i;
10147 }
10148
10149 int
10150 GameCheckSum ()
10151 {
10152         int i, sum=0;
10153         for(i=backwardMostMove; i<forwardMostMove; i++) {
10154                 sum += pvInfoList[i].depth;
10155                 sum += StringCheckSum(parseList[i]);
10156                 sum += StringCheckSum(commentList[i]);
10157                 sum *= 261;
10158         }
10159         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10160         return sum + StringCheckSum(commentList[i]);
10161 } // end of save patch
10162
10163 void
10164 GameEnds (ChessMove result, char *resultDetails, int whosays)
10165 {
10166     GameMode nextGameMode;
10167     int isIcsGame;
10168     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10169
10170     if(endingGame) return; /* [HGM] crash: forbid recursion */
10171     endingGame = 1;
10172     if(twoBoards) { // [HGM] dual: switch back to one board
10173         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10174         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10175     }
10176     if (appData.debugMode) {
10177       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10178               result, resultDetails ? resultDetails : "(null)", whosays);
10179     }
10180
10181     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10182
10183     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10184         /* If we are playing on ICS, the server decides when the
10185            game is over, but the engine can offer to draw, claim
10186            a draw, or resign.
10187          */
10188 #if ZIPPY
10189         if (appData.zippyPlay && first.initDone) {
10190             if (result == GameIsDrawn) {
10191                 /* In case draw still needs to be claimed */
10192                 SendToICS(ics_prefix);
10193                 SendToICS("draw\n");
10194             } else if (StrCaseStr(resultDetails, "resign")) {
10195                 SendToICS(ics_prefix);
10196                 SendToICS("resign\n");
10197             }
10198         }
10199 #endif
10200         endingGame = 0; /* [HGM] crash */
10201         return;
10202     }
10203
10204     /* If we're loading the game from a file, stop */
10205     if (whosays == GE_FILE) {
10206       (void) StopLoadGameTimer();
10207       gameFileFP = NULL;
10208     }
10209
10210     /* Cancel draw offers */
10211     first.offeredDraw = second.offeredDraw = 0;
10212
10213     /* If this is an ICS game, only ICS can really say it's done;
10214        if not, anyone can. */
10215     isIcsGame = (gameMode == IcsPlayingWhite ||
10216                  gameMode == IcsPlayingBlack ||
10217                  gameMode == IcsObserving    ||
10218                  gameMode == IcsExamining);
10219
10220     if (!isIcsGame || whosays == GE_ICS) {
10221         /* OK -- not an ICS game, or ICS said it was done */
10222         StopClocks();
10223         if (!isIcsGame && !appData.noChessProgram)
10224           SetUserThinkingEnables();
10225
10226         /* [HGM] if a machine claims the game end we verify this claim */
10227         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10228             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10229                 char claimer;
10230                 ChessMove trueResult = (ChessMove) -1;
10231
10232                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10233                                             first.twoMachinesColor[0] :
10234                                             second.twoMachinesColor[0] ;
10235
10236                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10237                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10238                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10239                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10240                 } else
10241                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10242                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10243                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10244                 } else
10245                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10246                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10247                 }
10248
10249                 // now verify win claims, but not in drop games, as we don't understand those yet
10250                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10251                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10252                     (result == WhiteWins && claimer == 'w' ||
10253                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10254                       if (appData.debugMode) {
10255                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10256                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10257                       }
10258                       if(result != trueResult) {
10259                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10260                               result = claimer == 'w' ? BlackWins : WhiteWins;
10261                               resultDetails = buf;
10262                       }
10263                 } else
10264                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10265                     && (forwardMostMove <= backwardMostMove ||
10266                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10267                         (claimer=='b')==(forwardMostMove&1))
10268                                                                                   ) {
10269                       /* [HGM] verify: draws that were not flagged are false claims */
10270                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10271                       result = claimer == 'w' ? BlackWins : WhiteWins;
10272                       resultDetails = buf;
10273                 }
10274                 /* (Claiming a loss is accepted no questions asked!) */
10275             }
10276             /* [HGM] bare: don't allow bare King to win */
10277             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10278                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10279                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10280                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10281                && result != GameIsDrawn)
10282             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10283                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10284                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10285                         if(p >= 0 && p <= (int)WhiteKing) k++;
10286                 }
10287                 if (appData.debugMode) {
10288                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10289                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10290                 }
10291                 if(k <= 1) {
10292                         result = GameIsDrawn;
10293                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10294                         resultDetails = buf;
10295                 }
10296             }
10297         }
10298
10299
10300         if(serverMoves != NULL && !loadFlag) { char c = '=';
10301             if(result==WhiteWins) c = '+';
10302             if(result==BlackWins) c = '-';
10303             if(resultDetails != NULL)
10304                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10305         }
10306         if (resultDetails != NULL) {
10307             gameInfo.result = result;
10308             gameInfo.resultDetails = StrSave(resultDetails);
10309
10310             /* display last move only if game was not loaded from file */
10311             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10312                 DisplayMove(currentMove - 1);
10313
10314             if (forwardMostMove != 0) {
10315                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10316                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10317                                                                 ) {
10318                     if (*appData.saveGameFile != NULLCHAR) {
10319                         SaveGameToFile(appData.saveGameFile, TRUE);
10320                     } else if (appData.autoSaveGames) {
10321                         AutoSaveGame();
10322                     }
10323                     if (*appData.savePositionFile != NULLCHAR) {
10324                         SavePositionToFile(appData.savePositionFile);
10325                     }
10326                 }
10327             }
10328
10329             /* Tell program how game ended in case it is learning */
10330             /* [HGM] Moved this to after saving the PGN, just in case */
10331             /* engine died and we got here through time loss. In that */
10332             /* case we will get a fatal error writing the pipe, which */
10333             /* would otherwise lose us the PGN.                       */
10334             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10335             /* output during GameEnds should never be fatal anymore   */
10336             if (gameMode == MachinePlaysWhite ||
10337                 gameMode == MachinePlaysBlack ||
10338                 gameMode == TwoMachinesPlay ||
10339                 gameMode == IcsPlayingWhite ||
10340                 gameMode == IcsPlayingBlack ||
10341                 gameMode == BeginningOfGame) {
10342                 char buf[MSG_SIZ];
10343                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10344                         resultDetails);
10345                 if (first.pr != NoProc) {
10346                     SendToProgram(buf, &first);
10347                 }
10348                 if (second.pr != NoProc &&
10349                     gameMode == TwoMachinesPlay) {
10350                     SendToProgram(buf, &second);
10351                 }
10352             }
10353         }
10354
10355         if (appData.icsActive) {
10356             if (appData.quietPlay &&
10357                 (gameMode == IcsPlayingWhite ||
10358                  gameMode == IcsPlayingBlack)) {
10359                 SendToICS(ics_prefix);
10360                 SendToICS("set shout 1\n");
10361             }
10362             nextGameMode = IcsIdle;
10363             ics_user_moved = FALSE;
10364             /* clean up premove.  It's ugly when the game has ended and the
10365              * premove highlights are still on the board.
10366              */
10367             if (gotPremove) {
10368               gotPremove = FALSE;
10369               ClearPremoveHighlights();
10370               DrawPosition(FALSE, boards[currentMove]);
10371             }
10372             if (whosays == GE_ICS) {
10373                 switch (result) {
10374                 case WhiteWins:
10375                     if (gameMode == IcsPlayingWhite)
10376                         PlayIcsWinSound();
10377                     else if(gameMode == IcsPlayingBlack)
10378                         PlayIcsLossSound();
10379                     break;
10380                 case BlackWins:
10381                     if (gameMode == IcsPlayingBlack)
10382                         PlayIcsWinSound();
10383                     else if(gameMode == IcsPlayingWhite)
10384                         PlayIcsLossSound();
10385                     break;
10386                 case GameIsDrawn:
10387                     PlayIcsDrawSound();
10388                     break;
10389                 default:
10390                     PlayIcsUnfinishedSound();
10391                 }
10392             }
10393         } else if (gameMode == EditGame ||
10394                    gameMode == PlayFromGameFile ||
10395                    gameMode == AnalyzeMode ||
10396                    gameMode == AnalyzeFile) {
10397             nextGameMode = gameMode;
10398         } else {
10399             nextGameMode = EndOfGame;
10400         }
10401         pausing = FALSE;
10402         ModeHighlight();
10403     } else {
10404         nextGameMode = gameMode;
10405     }
10406
10407     if (appData.noChessProgram) {
10408         gameMode = nextGameMode;
10409         ModeHighlight();
10410         endingGame = 0; /* [HGM] crash */
10411         return;
10412     }
10413
10414     if (first.reuse) {
10415         /* Put first chess program into idle state */
10416         if (first.pr != NoProc &&
10417             (gameMode == MachinePlaysWhite ||
10418              gameMode == MachinePlaysBlack ||
10419              gameMode == TwoMachinesPlay ||
10420              gameMode == IcsPlayingWhite ||
10421              gameMode == IcsPlayingBlack ||
10422              gameMode == BeginningOfGame)) {
10423             SendToProgram("force\n", &first);
10424             if (first.usePing) {
10425               char buf[MSG_SIZ];
10426               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10427               SendToProgram(buf, &first);
10428             }
10429         }
10430     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10431         /* Kill off first chess program */
10432         if (first.isr != NULL)
10433           RemoveInputSource(first.isr);
10434         first.isr = NULL;
10435
10436         if (first.pr != NoProc) {
10437             ExitAnalyzeMode();
10438             DoSleep( appData.delayBeforeQuit );
10439             SendToProgram("quit\n", &first);
10440             DoSleep( appData.delayAfterQuit );
10441             DestroyChildProcess(first.pr, first.useSigterm);
10442         }
10443         first.pr = NoProc;
10444     }
10445     if (second.reuse) {
10446         /* Put second chess program into idle state */
10447         if (second.pr != NoProc &&
10448             gameMode == TwoMachinesPlay) {
10449             SendToProgram("force\n", &second);
10450             if (second.usePing) {
10451               char buf[MSG_SIZ];
10452               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10453               SendToProgram(buf, &second);
10454             }
10455         }
10456     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10457         /* Kill off second chess program */
10458         if (second.isr != NULL)
10459           RemoveInputSource(second.isr);
10460         second.isr = NULL;
10461
10462         if (second.pr != NoProc) {
10463             DoSleep( appData.delayBeforeQuit );
10464             SendToProgram("quit\n", &second);
10465             DoSleep( appData.delayAfterQuit );
10466             DestroyChildProcess(second.pr, second.useSigterm);
10467         }
10468         second.pr = NoProc;
10469     }
10470
10471     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10472         char resChar = '=';
10473         switch (result) {
10474         case WhiteWins:
10475           resChar = '+';
10476           if (first.twoMachinesColor[0] == 'w') {
10477             first.matchWins++;
10478           } else {
10479             second.matchWins++;
10480           }
10481           break;
10482         case BlackWins:
10483           resChar = '-';
10484           if (first.twoMachinesColor[0] == 'b') {
10485             first.matchWins++;
10486           } else {
10487             second.matchWins++;
10488           }
10489           break;
10490         case GameUnfinished:
10491           resChar = ' ';
10492         default:
10493           break;
10494         }
10495
10496         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10497         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10498             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10499             ReserveGame(nextGame, resChar); // sets nextGame
10500             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10501             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10502         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10503
10504         if (nextGame <= appData.matchGames && !abortMatch) {
10505             gameMode = nextGameMode;
10506             matchGame = nextGame; // this will be overruled in tourney mode!
10507             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10508             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10509             endingGame = 0; /* [HGM] crash */
10510             return;
10511         } else {
10512             gameMode = nextGameMode;
10513             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10514                      first.tidy, second.tidy,
10515                      first.matchWins, second.matchWins,
10516                      appData.matchGames - (first.matchWins + second.matchWins));
10517             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10518             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10519             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10520             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10521                 first.twoMachinesColor = "black\n";
10522                 second.twoMachinesColor = "white\n";
10523             } else {
10524                 first.twoMachinesColor = "white\n";
10525                 second.twoMachinesColor = "black\n";
10526             }
10527         }
10528     }
10529     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10530         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10531       ExitAnalyzeMode();
10532     gameMode = nextGameMode;
10533     ModeHighlight();
10534     endingGame = 0;  /* [HGM] crash */
10535     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10536         if(matchMode == TRUE) { // match through command line: exit with or without popup
10537             if(ranking) {
10538                 ToNrEvent(forwardMostMove);
10539                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10540                 else ExitEvent(0);
10541             } else DisplayFatalError(buf, 0, 0);
10542         } else { // match through menu; just stop, with or without popup
10543             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10544             ModeHighlight();
10545             if(ranking){
10546                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10547             } else DisplayNote(buf);
10548       }
10549       if(ranking) free(ranking);
10550     }
10551 }
10552
10553 /* Assumes program was just initialized (initString sent).
10554    Leaves program in force mode. */
10555 void
10556 FeedMovesToProgram (ChessProgramState *cps, int upto)
10557 {
10558     int i;
10559
10560     if (appData.debugMode)
10561       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10562               startedFromSetupPosition ? "position and " : "",
10563               backwardMostMove, upto, cps->which);
10564     if(currentlyInitializedVariant != gameInfo.variant) {
10565       char buf[MSG_SIZ];
10566         // [HGM] variantswitch: make engine aware of new variant
10567         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10568                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10569         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10570         SendToProgram(buf, cps);
10571         currentlyInitializedVariant = gameInfo.variant;
10572     }
10573     SendToProgram("force\n", cps);
10574     if (startedFromSetupPosition) {
10575         SendBoard(cps, backwardMostMove);
10576     if (appData.debugMode) {
10577         fprintf(debugFP, "feedMoves\n");
10578     }
10579     }
10580     for (i = backwardMostMove; i < upto; i++) {
10581         SendMoveToProgram(i, cps);
10582     }
10583 }
10584
10585
10586 int
10587 ResurrectChessProgram ()
10588 {
10589      /* The chess program may have exited.
10590         If so, restart it and feed it all the moves made so far. */
10591     static int doInit = 0;
10592
10593     if (appData.noChessProgram) return 1;
10594
10595     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10596         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10597         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10598         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10599     } else {
10600         if (first.pr != NoProc) return 1;
10601         StartChessProgram(&first);
10602     }
10603     InitChessProgram(&first, FALSE);
10604     FeedMovesToProgram(&first, currentMove);
10605
10606     if (!first.sendTime) {
10607         /* can't tell gnuchess what its clock should read,
10608            so we bow to its notion. */
10609         ResetClocks();
10610         timeRemaining[0][currentMove] = whiteTimeRemaining;
10611         timeRemaining[1][currentMove] = blackTimeRemaining;
10612     }
10613
10614     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10615                 appData.icsEngineAnalyze) && first.analysisSupport) {
10616       SendToProgram("analyze\n", &first);
10617       first.analyzing = TRUE;
10618     }
10619     return 1;
10620 }
10621
10622 /*
10623  * Button procedures
10624  */
10625 void
10626 Reset (int redraw, int init)
10627 {
10628     int i;
10629
10630     if (appData.debugMode) {
10631         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10632                 redraw, init, gameMode);
10633     }
10634     CleanupTail(); // [HGM] vari: delete any stored variations
10635     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10636     pausing = pauseExamInvalid = FALSE;
10637     startedFromSetupPosition = blackPlaysFirst = FALSE;
10638     firstMove = TRUE;
10639     whiteFlag = blackFlag = FALSE;
10640     userOfferedDraw = FALSE;
10641     hintRequested = bookRequested = FALSE;
10642     first.maybeThinking = FALSE;
10643     second.maybeThinking = FALSE;
10644     first.bookSuspend = FALSE; // [HGM] book
10645     second.bookSuspend = FALSE;
10646     thinkOutput[0] = NULLCHAR;
10647     lastHint[0] = NULLCHAR;
10648     ClearGameInfo(&gameInfo);
10649     gameInfo.variant = StringToVariant(appData.variant);
10650     ics_user_moved = ics_clock_paused = FALSE;
10651     ics_getting_history = H_FALSE;
10652     ics_gamenum = -1;
10653     white_holding[0] = black_holding[0] = NULLCHAR;
10654     ClearProgramStats();
10655     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10656
10657     ResetFrontEnd();
10658     ClearHighlights();
10659     flipView = appData.flipView;
10660     ClearPremoveHighlights();
10661     gotPremove = FALSE;
10662     alarmSounded = FALSE;
10663
10664     GameEnds(EndOfFile, NULL, GE_PLAYER);
10665     if(appData.serverMovesName != NULL) {
10666         /* [HGM] prepare to make moves file for broadcasting */
10667         clock_t t = clock();
10668         if(serverMoves != NULL) fclose(serverMoves);
10669         serverMoves = fopen(appData.serverMovesName, "r");
10670         if(serverMoves != NULL) {
10671             fclose(serverMoves);
10672             /* delay 15 sec before overwriting, so all clients can see end */
10673             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10674         }
10675         serverMoves = fopen(appData.serverMovesName, "w");
10676     }
10677
10678     ExitAnalyzeMode();
10679     gameMode = BeginningOfGame;
10680     ModeHighlight();
10681     if(appData.icsActive) gameInfo.variant = VariantNormal;
10682     currentMove = forwardMostMove = backwardMostMove = 0;
10683     MarkTargetSquares(1);
10684     InitPosition(redraw);
10685     for (i = 0; i < MAX_MOVES; i++) {
10686         if (commentList[i] != NULL) {
10687             free(commentList[i]);
10688             commentList[i] = NULL;
10689         }
10690     }
10691     ResetClocks();
10692     timeRemaining[0][0] = whiteTimeRemaining;
10693     timeRemaining[1][0] = blackTimeRemaining;
10694
10695     if (first.pr == NoProc) {
10696         StartChessProgram(&first);
10697     }
10698     if (init) {
10699             InitChessProgram(&first, startedFromSetupPosition);
10700     }
10701     DisplayTitle("");
10702     DisplayMessage("", "");
10703     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10704     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10705 }
10706
10707 void
10708 AutoPlayGameLoop ()
10709 {
10710     for (;;) {
10711         if (!AutoPlayOneMove())
10712           return;
10713         if (matchMode || appData.timeDelay == 0)
10714           continue;
10715         if (appData.timeDelay < 0)
10716           return;
10717         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10718         break;
10719     }
10720 }
10721
10722
10723 int
10724 AutoPlayOneMove ()
10725 {
10726     int fromX, fromY, toX, toY;
10727
10728     if (appData.debugMode) {
10729       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10730     }
10731
10732     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10733       return FALSE;
10734
10735     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10736       pvInfoList[currentMove].depth = programStats.depth;
10737       pvInfoList[currentMove].score = programStats.score;
10738       pvInfoList[currentMove].time  = 0;
10739       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10740     }
10741
10742     if (currentMove >= forwardMostMove) {
10743       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10744 //      gameMode = EndOfGame;
10745 //      ModeHighlight();
10746
10747       /* [AS] Clear current move marker at the end of a game */
10748       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10749
10750       return FALSE;
10751     }
10752
10753     toX = moveList[currentMove][2] - AAA;
10754     toY = moveList[currentMove][3] - ONE;
10755
10756     if (moveList[currentMove][1] == '@') {
10757         if (appData.highlightLastMove) {
10758             SetHighlights(-1, -1, toX, toY);
10759         }
10760     } else {
10761         fromX = moveList[currentMove][0] - AAA;
10762         fromY = moveList[currentMove][1] - ONE;
10763
10764         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10765
10766         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10767
10768         if (appData.highlightLastMove) {
10769             SetHighlights(fromX, fromY, toX, toY);
10770         }
10771     }
10772     DisplayMove(currentMove);
10773     SendMoveToProgram(currentMove++, &first);
10774     DisplayBothClocks();
10775     DrawPosition(FALSE, boards[currentMove]);
10776     // [HGM] PV info: always display, routine tests if empty
10777     DisplayComment(currentMove - 1, commentList[currentMove]);
10778     return TRUE;
10779 }
10780
10781
10782 int
10783 LoadGameOneMove (ChessMove readAhead)
10784 {
10785     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10786     char promoChar = NULLCHAR;
10787     ChessMove moveType;
10788     char move[MSG_SIZ];
10789     char *p, *q;
10790
10791     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10792         gameMode != AnalyzeMode && gameMode != Training) {
10793         gameFileFP = NULL;
10794         return FALSE;
10795     }
10796
10797     yyboardindex = forwardMostMove;
10798     if (readAhead != EndOfFile) {
10799       moveType = readAhead;
10800     } else {
10801       if (gameFileFP == NULL)
10802           return FALSE;
10803       moveType = (ChessMove) Myylex();
10804     }
10805
10806     done = FALSE;
10807     switch (moveType) {
10808       case Comment:
10809         if (appData.debugMode)
10810           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10811         p = yy_text;
10812
10813         /* append the comment but don't display it */
10814         AppendComment(currentMove, p, FALSE);
10815         return TRUE;
10816
10817       case WhiteCapturesEnPassant:
10818       case BlackCapturesEnPassant:
10819       case WhitePromotion:
10820       case BlackPromotion:
10821       case WhiteNonPromotion:
10822       case BlackNonPromotion:
10823       case NormalMove:
10824       case WhiteKingSideCastle:
10825       case WhiteQueenSideCastle:
10826       case BlackKingSideCastle:
10827       case BlackQueenSideCastle:
10828       case WhiteKingSideCastleWild:
10829       case WhiteQueenSideCastleWild:
10830       case BlackKingSideCastleWild:
10831       case BlackQueenSideCastleWild:
10832       /* PUSH Fabien */
10833       case WhiteHSideCastleFR:
10834       case WhiteASideCastleFR:
10835       case BlackHSideCastleFR:
10836       case BlackASideCastleFR:
10837       /* POP Fabien */
10838         if (appData.debugMode)
10839           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10840         fromX = currentMoveString[0] - AAA;
10841         fromY = currentMoveString[1] - ONE;
10842         toX = currentMoveString[2] - AAA;
10843         toY = currentMoveString[3] - ONE;
10844         promoChar = currentMoveString[4];
10845         break;
10846
10847       case WhiteDrop:
10848       case BlackDrop:
10849         if (appData.debugMode)
10850           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10851         fromX = moveType == WhiteDrop ?
10852           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10853         (int) CharToPiece(ToLower(currentMoveString[0]));
10854         fromY = DROP_RANK;
10855         toX = currentMoveString[2] - AAA;
10856         toY = currentMoveString[3] - ONE;
10857         break;
10858
10859       case WhiteWins:
10860       case BlackWins:
10861       case GameIsDrawn:
10862       case GameUnfinished:
10863         if (appData.debugMode)
10864           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10865         p = strchr(yy_text, '{');
10866         if (p == NULL) p = strchr(yy_text, '(');
10867         if (p == NULL) {
10868             p = yy_text;
10869             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10870         } else {
10871             q = strchr(p, *p == '{' ? '}' : ')');
10872             if (q != NULL) *q = NULLCHAR;
10873             p++;
10874         }
10875         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10876         GameEnds(moveType, p, GE_FILE);
10877         done = TRUE;
10878         if (cmailMsgLoaded) {
10879             ClearHighlights();
10880             flipView = WhiteOnMove(currentMove);
10881             if (moveType == GameUnfinished) flipView = !flipView;
10882             if (appData.debugMode)
10883               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10884         }
10885         break;
10886
10887       case EndOfFile:
10888         if (appData.debugMode)
10889           fprintf(debugFP, "Parser hit end of file\n");
10890         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10891           case MT_NONE:
10892           case MT_CHECK:
10893             break;
10894           case MT_CHECKMATE:
10895           case MT_STAINMATE:
10896             if (WhiteOnMove(currentMove)) {
10897                 GameEnds(BlackWins, "Black mates", GE_FILE);
10898             } else {
10899                 GameEnds(WhiteWins, "White mates", GE_FILE);
10900             }
10901             break;
10902           case MT_STALEMATE:
10903             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10904             break;
10905         }
10906         done = TRUE;
10907         break;
10908
10909       case MoveNumberOne:
10910         if (lastLoadGameStart == GNUChessGame) {
10911             /* GNUChessGames have numbers, but they aren't move numbers */
10912             if (appData.debugMode)
10913               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10914                       yy_text, (int) moveType);
10915             return LoadGameOneMove(EndOfFile); /* tail recursion */
10916         }
10917         /* else fall thru */
10918
10919       case XBoardGame:
10920       case GNUChessGame:
10921       case PGNTag:
10922         /* Reached start of next game in file */
10923         if (appData.debugMode)
10924           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10925         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10926           case MT_NONE:
10927           case MT_CHECK:
10928             break;
10929           case MT_CHECKMATE:
10930           case MT_STAINMATE:
10931             if (WhiteOnMove(currentMove)) {
10932                 GameEnds(BlackWins, "Black mates", GE_FILE);
10933             } else {
10934                 GameEnds(WhiteWins, "White mates", GE_FILE);
10935             }
10936             break;
10937           case MT_STALEMATE:
10938             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10939             break;
10940         }
10941         done = TRUE;
10942         break;
10943
10944       case PositionDiagram:     /* should not happen; ignore */
10945       case ElapsedTime:         /* ignore */
10946       case NAG:                 /* ignore */
10947         if (appData.debugMode)
10948           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10949                   yy_text, (int) moveType);
10950         return LoadGameOneMove(EndOfFile); /* tail recursion */
10951
10952       case IllegalMove:
10953         if (appData.testLegality) {
10954             if (appData.debugMode)
10955               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10956             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10957                     (forwardMostMove / 2) + 1,
10958                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10959             DisplayError(move, 0);
10960             done = TRUE;
10961         } else {
10962             if (appData.debugMode)
10963               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10964                       yy_text, currentMoveString);
10965             fromX = currentMoveString[0] - AAA;
10966             fromY = currentMoveString[1] - ONE;
10967             toX = currentMoveString[2] - AAA;
10968             toY = currentMoveString[3] - ONE;
10969             promoChar = currentMoveString[4];
10970         }
10971         break;
10972
10973       case AmbiguousMove:
10974         if (appData.debugMode)
10975           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10976         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10977                 (forwardMostMove / 2) + 1,
10978                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10979         DisplayError(move, 0);
10980         done = TRUE;
10981         break;
10982
10983       default:
10984       case ImpossibleMove:
10985         if (appData.debugMode)
10986           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10987         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10988                 (forwardMostMove / 2) + 1,
10989                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10990         DisplayError(move, 0);
10991         done = TRUE;
10992         break;
10993     }
10994
10995     if (done) {
10996         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10997             DrawPosition(FALSE, boards[currentMove]);
10998             DisplayBothClocks();
10999             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11000               DisplayComment(currentMove - 1, commentList[currentMove]);
11001         }
11002         (void) StopLoadGameTimer();
11003         gameFileFP = NULL;
11004         cmailOldMove = forwardMostMove;
11005         return FALSE;
11006     } else {
11007         /* currentMoveString is set as a side-effect of yylex */
11008
11009         thinkOutput[0] = NULLCHAR;
11010         MakeMove(fromX, fromY, toX, toY, promoChar);
11011         currentMove = forwardMostMove;
11012         return TRUE;
11013     }
11014 }
11015
11016 /* Load the nth game from the given file */
11017 int
11018 LoadGameFromFile (char *filename, int n, char *title, int useList)
11019 {
11020     FILE *f;
11021     char buf[MSG_SIZ];
11022
11023     if (strcmp(filename, "-") == 0) {
11024         f = stdin;
11025         title = "stdin";
11026     } else {
11027         f = fopen(filename, "rb");
11028         if (f == NULL) {
11029           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11030             DisplayError(buf, errno);
11031             return FALSE;
11032         }
11033     }
11034     if (fseek(f, 0, 0) == -1) {
11035         /* f is not seekable; probably a pipe */
11036         useList = FALSE;
11037     }
11038     if (useList && n == 0) {
11039         int error = GameListBuild(f);
11040         if (error) {
11041             DisplayError(_("Cannot build game list"), error);
11042         } else if (!ListEmpty(&gameList) &&
11043                    ((ListGame *) gameList.tailPred)->number > 1) {
11044             GameListPopUp(f, title);
11045             return TRUE;
11046         }
11047         GameListDestroy();
11048         n = 1;
11049     }
11050     if (n == 0) n = 1;
11051     return LoadGame(f, n, title, FALSE);
11052 }
11053
11054
11055 void
11056 MakeRegisteredMove ()
11057 {
11058     int fromX, fromY, toX, toY;
11059     char promoChar;
11060     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11061         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11062           case CMAIL_MOVE:
11063           case CMAIL_DRAW:
11064             if (appData.debugMode)
11065               fprintf(debugFP, "Restoring %s for game %d\n",
11066                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11067
11068             thinkOutput[0] = NULLCHAR;
11069             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11070             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11071             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11072             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11073             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11074             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11075             MakeMove(fromX, fromY, toX, toY, promoChar);
11076             ShowMove(fromX, fromY, toX, toY);
11077
11078             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11079               case MT_NONE:
11080               case MT_CHECK:
11081                 break;
11082
11083               case MT_CHECKMATE:
11084               case MT_STAINMATE:
11085                 if (WhiteOnMove(currentMove)) {
11086                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11087                 } else {
11088                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11089                 }
11090                 break;
11091
11092               case MT_STALEMATE:
11093                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11094                 break;
11095             }
11096
11097             break;
11098
11099           case CMAIL_RESIGN:
11100             if (WhiteOnMove(currentMove)) {
11101                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11102             } else {
11103                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11104             }
11105             break;
11106
11107           case CMAIL_ACCEPT:
11108             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11109             break;
11110
11111           default:
11112             break;
11113         }
11114     }
11115
11116     return;
11117 }
11118
11119 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11120 int
11121 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11122 {
11123     int retVal;
11124
11125     if (gameNumber > nCmailGames) {
11126         DisplayError(_("No more games in this message"), 0);
11127         return FALSE;
11128     }
11129     if (f == lastLoadGameFP) {
11130         int offset = gameNumber - lastLoadGameNumber;
11131         if (offset == 0) {
11132             cmailMsg[0] = NULLCHAR;
11133             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11134                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11135                 nCmailMovesRegistered--;
11136             }
11137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11138             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11139                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11140             }
11141         } else {
11142             if (! RegisterMove()) return FALSE;
11143         }
11144     }
11145
11146     retVal = LoadGame(f, gameNumber, title, useList);
11147
11148     /* Make move registered during previous look at this game, if any */
11149     MakeRegisteredMove();
11150
11151     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11152         commentList[currentMove]
11153           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11154         DisplayComment(currentMove - 1, commentList[currentMove]);
11155     }
11156
11157     return retVal;
11158 }
11159
11160 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11161 int
11162 ReloadGame (int offset)
11163 {
11164     int gameNumber = lastLoadGameNumber + offset;
11165     if (lastLoadGameFP == NULL) {
11166         DisplayError(_("No game has been loaded yet"), 0);
11167         return FALSE;
11168     }
11169     if (gameNumber <= 0) {
11170         DisplayError(_("Can't back up any further"), 0);
11171         return FALSE;
11172     }
11173     if (cmailMsgLoaded) {
11174         return CmailLoadGame(lastLoadGameFP, gameNumber,
11175                              lastLoadGameTitle, lastLoadGameUseList);
11176     } else {
11177         return LoadGame(lastLoadGameFP, gameNumber,
11178                         lastLoadGameTitle, lastLoadGameUseList);
11179     }
11180 }
11181
11182 int keys[EmptySquare+1];
11183
11184 int
11185 PositionMatches (Board b1, Board b2)
11186 {
11187     int r, f, sum=0;
11188     switch(appData.searchMode) {
11189         case 1: return CompareWithRights(b1, b2);
11190         case 2:
11191             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11192                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11193             }
11194             return TRUE;
11195         case 3:
11196             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11197               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11198                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11199             }
11200             return sum==0;
11201         case 4:
11202             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11203                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11204             }
11205             return sum==0;
11206     }
11207     return TRUE;
11208 }
11209
11210 #define Q_PROMO  4
11211 #define Q_EP     3
11212 #define Q_BCASTL 2
11213 #define Q_WCASTL 1
11214
11215 int pieceList[256], quickBoard[256];
11216 ChessSquare pieceType[256] = { EmptySquare };
11217 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11218 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11219 int soughtTotal, turn;
11220 Boolean epOK, flipSearch;
11221
11222 typedef struct {
11223     unsigned char piece, to;
11224 } Move;
11225
11226 #define DSIZE (250000)
11227
11228 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11229 Move *moveDatabase = initialSpace;
11230 unsigned int movePtr, dataSize = DSIZE;
11231
11232 int
11233 MakePieceList (Board board, int *counts)
11234 {
11235     int r, f, n=Q_PROMO, total=0;
11236     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11237     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11238         int sq = f + (r<<4);
11239         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11240             quickBoard[sq] = ++n;
11241             pieceList[n] = sq;
11242             pieceType[n] = board[r][f];
11243             counts[board[r][f]]++;
11244             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11245             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11246             total++;
11247         }
11248     }
11249     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11250     return total;
11251 }
11252
11253 void
11254 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11255 {
11256     int sq = fromX + (fromY<<4);
11257     int piece = quickBoard[sq];
11258     quickBoard[sq] = 0;
11259     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11260     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11261         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11262         moveDatabase[movePtr++].piece = Q_WCASTL;
11263         quickBoard[sq] = piece;
11264         piece = quickBoard[from]; quickBoard[from] = 0;
11265         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11266     } else
11267     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11268         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11269         moveDatabase[movePtr++].piece = Q_BCASTL;
11270         quickBoard[sq] = piece;
11271         piece = quickBoard[from]; quickBoard[from] = 0;
11272         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11273     } else
11274     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11275         quickBoard[(fromY<<4)+toX] = 0;
11276         moveDatabase[movePtr].piece = Q_EP;
11277         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11278         moveDatabase[movePtr].to = sq;
11279     } else
11280     if(promoPiece != pieceType[piece]) {
11281         moveDatabase[movePtr++].piece = Q_PROMO;
11282         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11283     }
11284     moveDatabase[movePtr].piece = piece;
11285     quickBoard[sq] = piece;
11286     movePtr++;
11287 }
11288
11289 int
11290 PackGame (Board board)
11291 {
11292     Move *newSpace = NULL;
11293     moveDatabase[movePtr].piece = 0; // terminate previous game
11294     if(movePtr > dataSize) {
11295         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11296         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11297         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11298         if(newSpace) {
11299             int i;
11300             Move *p = moveDatabase, *q = newSpace;
11301             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11302             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11303             moveDatabase = newSpace;
11304         } else { // calloc failed, we must be out of memory. Too bad...
11305             dataSize = 0; // prevent calloc events for all subsequent games
11306             return 0;     // and signal this one isn't cached
11307         }
11308     }
11309     movePtr++;
11310     MakePieceList(board, counts);
11311     return movePtr;
11312 }
11313
11314 int
11315 QuickCompare (Board board, int *minCounts, int *maxCounts)
11316 {   // compare according to search mode
11317     int r, f;
11318     switch(appData.searchMode)
11319     {
11320       case 1: // exact position match
11321         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11322         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11323             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11324         }
11325         break;
11326       case 2: // can have extra material on empty squares
11327         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11328             if(board[r][f] == EmptySquare) continue;
11329             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11330         }
11331         break;
11332       case 3: // material with exact Pawn structure
11333         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11334             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11335             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11336         } // fall through to material comparison
11337       case 4: // exact material
11338         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11339         break;
11340       case 6: // material range with given imbalance
11341         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11342         // fall through to range comparison
11343       case 5: // material range
11344         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11345     }
11346     return TRUE;
11347 }
11348
11349 int
11350 QuickScan (Board board, Move *move)
11351 {   // reconstruct game,and compare all positions in it
11352     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11353     do {
11354         int piece = move->piece;
11355         int to = move->to, from = pieceList[piece];
11356         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11357           if(!piece) return -1;
11358           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11359             piece = (++move)->piece;
11360             from = pieceList[piece];
11361             counts[pieceType[piece]]--;
11362             pieceType[piece] = (ChessSquare) move->to;
11363             counts[move->to]++;
11364           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11365             counts[pieceType[quickBoard[to]]]--;
11366             quickBoard[to] = 0; total--;
11367             move++;
11368             continue;
11369           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11370             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11371             from  = pieceList[piece]; // so this must be King
11372             quickBoard[from] = 0;
11373             quickBoard[to] = piece;
11374             pieceList[piece] = to;
11375             move++;
11376             continue;
11377           }
11378         }
11379         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11380         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11381         quickBoard[from] = 0;
11382         quickBoard[to] = piece;
11383         pieceList[piece] = to;
11384         cnt++; turn ^= 3;
11385         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11386            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11387            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11388                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11389           ) {
11390             static int lastCounts[EmptySquare+1];
11391             int i;
11392             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11393             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11394         } else stretch = 0;
11395         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11396         move++;
11397     } while(1);
11398 }
11399
11400 void
11401 InitSearch ()
11402 {
11403     int r, f;
11404     flipSearch = FALSE;
11405     CopyBoard(soughtBoard, boards[currentMove]);
11406     soughtTotal = MakePieceList(soughtBoard, maxSought);
11407     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11408     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11409     CopyBoard(reverseBoard, boards[currentMove]);
11410     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11411         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11412         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11413         reverseBoard[r][f] = piece;
11414     }
11415     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11416     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11417     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11418                  || (boards[currentMove][CASTLING][2] == NoRights || 
11419                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11420                  && (boards[currentMove][CASTLING][5] == NoRights || 
11421                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11422       ) {
11423         flipSearch = TRUE;
11424         CopyBoard(flipBoard, soughtBoard);
11425         CopyBoard(rotateBoard, reverseBoard);
11426         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11427             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11428             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11429         }
11430     }
11431     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11432     if(appData.searchMode >= 5) {
11433         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11434         MakePieceList(soughtBoard, minSought);
11435         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11436     }
11437     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11438         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11439 }
11440
11441 GameInfo dummyInfo;
11442
11443 int
11444 GameContainsPosition (FILE *f, ListGame *lg)
11445 {
11446     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11447     int fromX, fromY, toX, toY;
11448     char promoChar;
11449     static int initDone=FALSE;
11450
11451     // weed out games based on numerical tag comparison
11452     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11453     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11454     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11455     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11456     if(!initDone) {
11457         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11458         initDone = TRUE;
11459     }
11460     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11461     else CopyBoard(boards[scratch], initialPosition); // default start position
11462     if(lg->moves) {
11463         turn = btm + 1;
11464         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11465         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11466     }
11467     if(btm) plyNr++;
11468     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11469     fseek(f, lg->offset, 0);
11470     yynewfile(f);
11471     while(1) {
11472         yyboardindex = scratch;
11473         quickFlag = plyNr+1;
11474         next = Myylex();
11475         quickFlag = 0;
11476         switch(next) {
11477             case PGNTag:
11478                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11479             default:
11480                 continue;
11481
11482             case XBoardGame:
11483             case GNUChessGame:
11484                 if(plyNr) return -1; // after we have seen moves, this is for new game
11485               continue;
11486
11487             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11488             case ImpossibleMove:
11489             case WhiteWins: // game ends here with these four
11490             case BlackWins:
11491             case GameIsDrawn:
11492             case GameUnfinished:
11493                 return -1;
11494
11495             case IllegalMove:
11496                 if(appData.testLegality) return -1;
11497             case WhiteCapturesEnPassant:
11498             case BlackCapturesEnPassant:
11499             case WhitePromotion:
11500             case BlackPromotion:
11501             case WhiteNonPromotion:
11502             case BlackNonPromotion:
11503             case NormalMove:
11504             case WhiteKingSideCastle:
11505             case WhiteQueenSideCastle:
11506             case BlackKingSideCastle:
11507             case BlackQueenSideCastle:
11508             case WhiteKingSideCastleWild:
11509             case WhiteQueenSideCastleWild:
11510             case BlackKingSideCastleWild:
11511             case BlackQueenSideCastleWild:
11512             case WhiteHSideCastleFR:
11513             case WhiteASideCastleFR:
11514             case BlackHSideCastleFR:
11515             case BlackASideCastleFR:
11516                 fromX = currentMoveString[0] - AAA;
11517                 fromY = currentMoveString[1] - ONE;
11518                 toX = currentMoveString[2] - AAA;
11519                 toY = currentMoveString[3] - ONE;
11520                 promoChar = currentMoveString[4];
11521                 break;
11522             case WhiteDrop:
11523             case BlackDrop:
11524                 fromX = next == WhiteDrop ?
11525                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11526                   (int) CharToPiece(ToLower(currentMoveString[0]));
11527                 fromY = DROP_RANK;
11528                 toX = currentMoveString[2] - AAA;
11529                 toY = currentMoveString[3] - ONE;
11530                 promoChar = 0;
11531                 break;
11532         }
11533         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11534         plyNr++;
11535         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11536         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11537         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11538         if(appData.findMirror) {
11539             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11540             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11541         }
11542     }
11543 }
11544
11545 /* Load the nth game from open file f */
11546 int
11547 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11548 {
11549     ChessMove cm;
11550     char buf[MSG_SIZ];
11551     int gn = gameNumber;
11552     ListGame *lg = NULL;
11553     int numPGNTags = 0;
11554     int err, pos = -1;
11555     GameMode oldGameMode;
11556     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11557
11558     if (appData.debugMode)
11559         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11560
11561     if (gameMode == Training )
11562         SetTrainingModeOff();
11563
11564     oldGameMode = gameMode;
11565     if (gameMode != BeginningOfGame) {
11566       Reset(FALSE, TRUE);
11567     }
11568
11569     gameFileFP = f;
11570     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11571         fclose(lastLoadGameFP);
11572     }
11573
11574     if (useList) {
11575         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11576
11577         if (lg) {
11578             fseek(f, lg->offset, 0);
11579             GameListHighlight(gameNumber);
11580             pos = lg->position;
11581             gn = 1;
11582         }
11583         else {
11584             DisplayError(_("Game number out of range"), 0);
11585             return FALSE;
11586         }
11587     } else {
11588         GameListDestroy();
11589         if (fseek(f, 0, 0) == -1) {
11590             if (f == lastLoadGameFP ?
11591                 gameNumber == lastLoadGameNumber + 1 :
11592                 gameNumber == 1) {
11593                 gn = 1;
11594             } else {
11595                 DisplayError(_("Can't seek on game file"), 0);
11596                 return FALSE;
11597             }
11598         }
11599     }
11600     lastLoadGameFP = f;
11601     lastLoadGameNumber = gameNumber;
11602     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11603     lastLoadGameUseList = useList;
11604
11605     yynewfile(f);
11606
11607     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11608       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11609                 lg->gameInfo.black);
11610             DisplayTitle(buf);
11611     } else if (*title != NULLCHAR) {
11612         if (gameNumber > 1) {
11613           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11614             DisplayTitle(buf);
11615         } else {
11616             DisplayTitle(title);
11617         }
11618     }
11619
11620     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11621         gameMode = PlayFromGameFile;
11622         ModeHighlight();
11623     }
11624
11625     currentMove = forwardMostMove = backwardMostMove = 0;
11626     CopyBoard(boards[0], initialPosition);
11627     StopClocks();
11628
11629     /*
11630      * Skip the first gn-1 games in the file.
11631      * Also skip over anything that precedes an identifiable
11632      * start of game marker, to avoid being confused by
11633      * garbage at the start of the file.  Currently
11634      * recognized start of game markers are the move number "1",
11635      * the pattern "gnuchess .* game", the pattern
11636      * "^[#;%] [^ ]* game file", and a PGN tag block.
11637      * A game that starts with one of the latter two patterns
11638      * will also have a move number 1, possibly
11639      * following a position diagram.
11640      * 5-4-02: Let's try being more lenient and allowing a game to
11641      * start with an unnumbered move.  Does that break anything?
11642      */
11643     cm = lastLoadGameStart = EndOfFile;
11644     while (gn > 0) {
11645         yyboardindex = forwardMostMove;
11646         cm = (ChessMove) Myylex();
11647         switch (cm) {
11648           case EndOfFile:
11649             if (cmailMsgLoaded) {
11650                 nCmailGames = CMAIL_MAX_GAMES - gn;
11651             } else {
11652                 Reset(TRUE, TRUE);
11653                 DisplayError(_("Game not found in file"), 0);
11654             }
11655             return FALSE;
11656
11657           case GNUChessGame:
11658           case XBoardGame:
11659             gn--;
11660             lastLoadGameStart = cm;
11661             break;
11662
11663           case MoveNumberOne:
11664             switch (lastLoadGameStart) {
11665               case GNUChessGame:
11666               case XBoardGame:
11667               case PGNTag:
11668                 break;
11669               case MoveNumberOne:
11670               case EndOfFile:
11671                 gn--;           /* count this game */
11672                 lastLoadGameStart = cm;
11673                 break;
11674               default:
11675                 /* impossible */
11676                 break;
11677             }
11678             break;
11679
11680           case PGNTag:
11681             switch (lastLoadGameStart) {
11682               case GNUChessGame:
11683               case PGNTag:
11684               case MoveNumberOne:
11685               case EndOfFile:
11686                 gn--;           /* count this game */
11687                 lastLoadGameStart = cm;
11688                 break;
11689               case XBoardGame:
11690                 lastLoadGameStart = cm; /* game counted already */
11691                 break;
11692               default:
11693                 /* impossible */
11694                 break;
11695             }
11696             if (gn > 0) {
11697                 do {
11698                     yyboardindex = forwardMostMove;
11699                     cm = (ChessMove) Myylex();
11700                 } while (cm == PGNTag || cm == Comment);
11701             }
11702             break;
11703
11704           case WhiteWins:
11705           case BlackWins:
11706           case GameIsDrawn:
11707             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11708                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11709                     != CMAIL_OLD_RESULT) {
11710                     nCmailResults ++ ;
11711                     cmailResult[  CMAIL_MAX_GAMES
11712                                 - gn - 1] = CMAIL_OLD_RESULT;
11713                 }
11714             }
11715             break;
11716
11717           case NormalMove:
11718             /* Only a NormalMove can be at the start of a game
11719              * without a position diagram. */
11720             if (lastLoadGameStart == EndOfFile ) {
11721               gn--;
11722               lastLoadGameStart = MoveNumberOne;
11723             }
11724             break;
11725
11726           default:
11727             break;
11728         }
11729     }
11730
11731     if (appData.debugMode)
11732       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11733
11734     if (cm == XBoardGame) {
11735         /* Skip any header junk before position diagram and/or move 1 */
11736         for (;;) {
11737             yyboardindex = forwardMostMove;
11738             cm = (ChessMove) Myylex();
11739
11740             if (cm == EndOfFile ||
11741                 cm == GNUChessGame || cm == XBoardGame) {
11742                 /* Empty game; pretend end-of-file and handle later */
11743                 cm = EndOfFile;
11744                 break;
11745             }
11746
11747             if (cm == MoveNumberOne || cm == PositionDiagram ||
11748                 cm == PGNTag || cm == Comment)
11749               break;
11750         }
11751     } else if (cm == GNUChessGame) {
11752         if (gameInfo.event != NULL) {
11753             free(gameInfo.event);
11754         }
11755         gameInfo.event = StrSave(yy_text);
11756     }
11757
11758     startedFromSetupPosition = FALSE;
11759     while (cm == PGNTag) {
11760         if (appData.debugMode)
11761           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11762         err = ParsePGNTag(yy_text, &gameInfo);
11763         if (!err) numPGNTags++;
11764
11765         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11766         if(gameInfo.variant != oldVariant) {
11767             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11768             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11769             InitPosition(TRUE);
11770             oldVariant = gameInfo.variant;
11771             if (appData.debugMode)
11772               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11773         }
11774
11775
11776         if (gameInfo.fen != NULL) {
11777           Board initial_position;
11778           startedFromSetupPosition = TRUE;
11779           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11780             Reset(TRUE, TRUE);
11781             DisplayError(_("Bad FEN position in file"), 0);
11782             return FALSE;
11783           }
11784           CopyBoard(boards[0], initial_position);
11785           if (blackPlaysFirst) {
11786             currentMove = forwardMostMove = backwardMostMove = 1;
11787             CopyBoard(boards[1], initial_position);
11788             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11789             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11790             timeRemaining[0][1] = whiteTimeRemaining;
11791             timeRemaining[1][1] = blackTimeRemaining;
11792             if (commentList[0] != NULL) {
11793               commentList[1] = commentList[0];
11794               commentList[0] = NULL;
11795             }
11796           } else {
11797             currentMove = forwardMostMove = backwardMostMove = 0;
11798           }
11799           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11800           {   int i;
11801               initialRulePlies = FENrulePlies;
11802               for( i=0; i< nrCastlingRights; i++ )
11803                   initialRights[i] = initial_position[CASTLING][i];
11804           }
11805           yyboardindex = forwardMostMove;
11806           free(gameInfo.fen);
11807           gameInfo.fen = NULL;
11808         }
11809
11810         yyboardindex = forwardMostMove;
11811         cm = (ChessMove) Myylex();
11812
11813         /* Handle comments interspersed among the tags */
11814         while (cm == Comment) {
11815             char *p;
11816             if (appData.debugMode)
11817               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11818             p = yy_text;
11819             AppendComment(currentMove, p, FALSE);
11820             yyboardindex = forwardMostMove;
11821             cm = (ChessMove) Myylex();
11822         }
11823     }
11824
11825     /* don't rely on existence of Event tag since if game was
11826      * pasted from clipboard the Event tag may not exist
11827      */
11828     if (numPGNTags > 0){
11829         char *tags;
11830         if (gameInfo.variant == VariantNormal) {
11831           VariantClass v = StringToVariant(gameInfo.event);
11832           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11833           if(v < VariantShogi) gameInfo.variant = v;
11834         }
11835         if (!matchMode) {
11836           if( appData.autoDisplayTags ) {
11837             tags = PGNTags(&gameInfo);
11838             TagsPopUp(tags, CmailMsg());
11839             free(tags);
11840           }
11841         }
11842     } else {
11843         /* Make something up, but don't display it now */
11844         SetGameInfo();
11845         TagsPopDown();
11846     }
11847
11848     if (cm == PositionDiagram) {
11849         int i, j;
11850         char *p;
11851         Board initial_position;
11852
11853         if (appData.debugMode)
11854           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11855
11856         if (!startedFromSetupPosition) {
11857             p = yy_text;
11858             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11859               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11860                 switch (*p) {
11861                   case '{':
11862                   case '[':
11863                   case '-':
11864                   case ' ':
11865                   case '\t':
11866                   case '\n':
11867                   case '\r':
11868                     break;
11869                   default:
11870                     initial_position[i][j++] = CharToPiece(*p);
11871                     break;
11872                 }
11873             while (*p == ' ' || *p == '\t' ||
11874                    *p == '\n' || *p == '\r') p++;
11875
11876             if (strncmp(p, "black", strlen("black"))==0)
11877               blackPlaysFirst = TRUE;
11878             else
11879               blackPlaysFirst = FALSE;
11880             startedFromSetupPosition = TRUE;
11881
11882             CopyBoard(boards[0], initial_position);
11883             if (blackPlaysFirst) {
11884                 currentMove = forwardMostMove = backwardMostMove = 1;
11885                 CopyBoard(boards[1], initial_position);
11886                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11887                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11888                 timeRemaining[0][1] = whiteTimeRemaining;
11889                 timeRemaining[1][1] = blackTimeRemaining;
11890                 if (commentList[0] != NULL) {
11891                     commentList[1] = commentList[0];
11892                     commentList[0] = NULL;
11893                 }
11894             } else {
11895                 currentMove = forwardMostMove = backwardMostMove = 0;
11896             }
11897         }
11898         yyboardindex = forwardMostMove;
11899         cm = (ChessMove) Myylex();
11900     }
11901
11902     if (first.pr == NoProc) {
11903         StartChessProgram(&first);
11904     }
11905     InitChessProgram(&first, FALSE);
11906     SendToProgram("force\n", &first);
11907     if (startedFromSetupPosition) {
11908         SendBoard(&first, forwardMostMove);
11909     if (appData.debugMode) {
11910         fprintf(debugFP, "Load Game\n");
11911     }
11912         DisplayBothClocks();
11913     }
11914
11915     /* [HGM] server: flag to write setup moves in broadcast file as one */
11916     loadFlag = appData.suppressLoadMoves;
11917
11918     while (cm == Comment) {
11919         char *p;
11920         if (appData.debugMode)
11921           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11922         p = yy_text;
11923         AppendComment(currentMove, p, FALSE);
11924         yyboardindex = forwardMostMove;
11925         cm = (ChessMove) Myylex();
11926     }
11927
11928     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11929         cm == WhiteWins || cm == BlackWins ||
11930         cm == GameIsDrawn || cm == GameUnfinished) {
11931         DisplayMessage("", _("No moves in game"));
11932         if (cmailMsgLoaded) {
11933             if (appData.debugMode)
11934               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11935             ClearHighlights();
11936             flipView = FALSE;
11937         }
11938         DrawPosition(FALSE, boards[currentMove]);
11939         DisplayBothClocks();
11940         gameMode = EditGame;
11941         ModeHighlight();
11942         gameFileFP = NULL;
11943         cmailOldMove = 0;
11944         return TRUE;
11945     }
11946
11947     // [HGM] PV info: routine tests if comment empty
11948     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11949         DisplayComment(currentMove - 1, commentList[currentMove]);
11950     }
11951     if (!matchMode && appData.timeDelay != 0)
11952       DrawPosition(FALSE, boards[currentMove]);
11953
11954     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11955       programStats.ok_to_send = 1;
11956     }
11957
11958     /* if the first token after the PGN tags is a move
11959      * and not move number 1, retrieve it from the parser
11960      */
11961     if (cm != MoveNumberOne)
11962         LoadGameOneMove(cm);
11963
11964     /* load the remaining moves from the file */
11965     while (LoadGameOneMove(EndOfFile)) {
11966       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11967       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11968     }
11969
11970     /* rewind to the start of the game */
11971     currentMove = backwardMostMove;
11972
11973     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11974
11975     if (oldGameMode == AnalyzeFile ||
11976         oldGameMode == AnalyzeMode) {
11977       AnalyzeFileEvent();
11978     }
11979
11980     if (!matchMode && pos >= 0) {
11981         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11982     } else
11983     if (matchMode || appData.timeDelay == 0) {
11984       ToEndEvent();
11985     } else if (appData.timeDelay > 0) {
11986       AutoPlayGameLoop();
11987     }
11988
11989     if (appData.debugMode)
11990         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11991
11992     loadFlag = 0; /* [HGM] true game starts */
11993     return TRUE;
11994 }
11995
11996 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11997 int
11998 ReloadPosition (int offset)
11999 {
12000     int positionNumber = lastLoadPositionNumber + offset;
12001     if (lastLoadPositionFP == NULL) {
12002         DisplayError(_("No position has been loaded yet"), 0);
12003         return FALSE;
12004     }
12005     if (positionNumber <= 0) {
12006         DisplayError(_("Can't back up any further"), 0);
12007         return FALSE;
12008     }
12009     return LoadPosition(lastLoadPositionFP, positionNumber,
12010                         lastLoadPositionTitle);
12011 }
12012
12013 /* Load the nth position from the given file */
12014 int
12015 LoadPositionFromFile (char *filename, int n, char *title)
12016 {
12017     FILE *f;
12018     char buf[MSG_SIZ];
12019
12020     if (strcmp(filename, "-") == 0) {
12021         return LoadPosition(stdin, n, "stdin");
12022     } else {
12023         f = fopen(filename, "rb");
12024         if (f == NULL) {
12025             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12026             DisplayError(buf, errno);
12027             return FALSE;
12028         } else {
12029             return LoadPosition(f, n, title);
12030         }
12031     }
12032 }
12033
12034 /* Load the nth position from the given open file, and close it */
12035 int
12036 LoadPosition (FILE *f, int positionNumber, char *title)
12037 {
12038     char *p, line[MSG_SIZ];
12039     Board initial_position;
12040     int i, j, fenMode, pn;
12041
12042     if (gameMode == Training )
12043         SetTrainingModeOff();
12044
12045     if (gameMode != BeginningOfGame) {
12046         Reset(FALSE, TRUE);
12047     }
12048     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12049         fclose(lastLoadPositionFP);
12050     }
12051     if (positionNumber == 0) positionNumber = 1;
12052     lastLoadPositionFP = f;
12053     lastLoadPositionNumber = positionNumber;
12054     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12055     if (first.pr == NoProc && !appData.noChessProgram) {
12056       StartChessProgram(&first);
12057       InitChessProgram(&first, FALSE);
12058     }
12059     pn = positionNumber;
12060     if (positionNumber < 0) {
12061         /* Negative position number means to seek to that byte offset */
12062         if (fseek(f, -positionNumber, 0) == -1) {
12063             DisplayError(_("Can't seek on position file"), 0);
12064             return FALSE;
12065         };
12066         pn = 1;
12067     } else {
12068         if (fseek(f, 0, 0) == -1) {
12069             if (f == lastLoadPositionFP ?
12070                 positionNumber == lastLoadPositionNumber + 1 :
12071                 positionNumber == 1) {
12072                 pn = 1;
12073             } else {
12074                 DisplayError(_("Can't seek on position file"), 0);
12075                 return FALSE;
12076             }
12077         }
12078     }
12079     /* See if this file is FEN or old-style xboard */
12080     if (fgets(line, MSG_SIZ, f) == NULL) {
12081         DisplayError(_("Position not found in file"), 0);
12082         return FALSE;
12083     }
12084     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12085     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12086
12087     if (pn >= 2) {
12088         if (fenMode || line[0] == '#') pn--;
12089         while (pn > 0) {
12090             /* skip positions before number pn */
12091             if (fgets(line, MSG_SIZ, f) == NULL) {
12092                 Reset(TRUE, TRUE);
12093                 DisplayError(_("Position not found in file"), 0);
12094                 return FALSE;
12095             }
12096             if (fenMode || line[0] == '#') pn--;
12097         }
12098     }
12099
12100     if (fenMode) {
12101         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12102             DisplayError(_("Bad FEN position in file"), 0);
12103             return FALSE;
12104         }
12105     } else {
12106         (void) fgets(line, MSG_SIZ, f);
12107         (void) fgets(line, MSG_SIZ, f);
12108
12109         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12110             (void) fgets(line, MSG_SIZ, f);
12111             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12112                 if (*p == ' ')
12113                   continue;
12114                 initial_position[i][j++] = CharToPiece(*p);
12115             }
12116         }
12117
12118         blackPlaysFirst = FALSE;
12119         if (!feof(f)) {
12120             (void) fgets(line, MSG_SIZ, f);
12121             if (strncmp(line, "black", strlen("black"))==0)
12122               blackPlaysFirst = TRUE;
12123         }
12124     }
12125     startedFromSetupPosition = TRUE;
12126
12127     CopyBoard(boards[0], initial_position);
12128     if (blackPlaysFirst) {
12129         currentMove = forwardMostMove = backwardMostMove = 1;
12130         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12131         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12132         CopyBoard(boards[1], initial_position);
12133         DisplayMessage("", _("Black to play"));
12134     } else {
12135         currentMove = forwardMostMove = backwardMostMove = 0;
12136         DisplayMessage("", _("White to play"));
12137     }
12138     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12139     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12140         SendToProgram("force\n", &first);
12141         SendBoard(&first, forwardMostMove);
12142     }
12143     if (appData.debugMode) {
12144 int i, j;
12145   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12146   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12147         fprintf(debugFP, "Load Position\n");
12148     }
12149
12150     if (positionNumber > 1) {
12151       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12152         DisplayTitle(line);
12153     } else {
12154         DisplayTitle(title);
12155     }
12156     gameMode = EditGame;
12157     ModeHighlight();
12158     ResetClocks();
12159     timeRemaining[0][1] = whiteTimeRemaining;
12160     timeRemaining[1][1] = blackTimeRemaining;
12161     DrawPosition(FALSE, boards[currentMove]);
12162
12163     return TRUE;
12164 }
12165
12166
12167 void
12168 CopyPlayerNameIntoFileName (char **dest, char *src)
12169 {
12170     while (*src != NULLCHAR && *src != ',') {
12171         if (*src == ' ') {
12172             *(*dest)++ = '_';
12173             src++;
12174         } else {
12175             *(*dest)++ = *src++;
12176         }
12177     }
12178 }
12179
12180 char *
12181 DefaultFileName (char *ext)
12182 {
12183     static char def[MSG_SIZ];
12184     char *p;
12185
12186     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12187         p = def;
12188         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12189         *p++ = '-';
12190         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12191         *p++ = '.';
12192         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12193     } else {
12194         def[0] = NULLCHAR;
12195     }
12196     return def;
12197 }
12198
12199 /* Save the current game to the given file */
12200 int
12201 SaveGameToFile (char *filename, int append)
12202 {
12203     FILE *f;
12204     char buf[MSG_SIZ];
12205     int result, i, t,tot=0;
12206
12207     if (strcmp(filename, "-") == 0) {
12208         return SaveGame(stdout, 0, NULL);
12209     } else {
12210         for(i=0; i<10; i++) { // upto 10 tries
12211              f = fopen(filename, append ? "a" : "w");
12212              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12213              if(f || errno != 13) break;
12214              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12215              tot += t;
12216         }
12217         if (f == NULL) {
12218             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12219             DisplayError(buf, errno);
12220             return FALSE;
12221         } else {
12222             safeStrCpy(buf, lastMsg, MSG_SIZ);
12223             DisplayMessage(_("Waiting for access to save file"), "");
12224             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12225             DisplayMessage(_("Saving game"), "");
12226             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12227             result = SaveGame(f, 0, NULL);
12228             DisplayMessage(buf, "");
12229             return result;
12230         }
12231     }
12232 }
12233
12234 char *
12235 SavePart (char *str)
12236 {
12237     static char buf[MSG_SIZ];
12238     char *p;
12239
12240     p = strchr(str, ' ');
12241     if (p == NULL) return str;
12242     strncpy(buf, str, p - str);
12243     buf[p - str] = NULLCHAR;
12244     return buf;
12245 }
12246
12247 #define PGN_MAX_LINE 75
12248
12249 #define PGN_SIDE_WHITE  0
12250 #define PGN_SIDE_BLACK  1
12251
12252 static int
12253 FindFirstMoveOutOfBook (int side)
12254 {
12255     int result = -1;
12256
12257     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12258         int index = backwardMostMove;
12259         int has_book_hit = 0;
12260
12261         if( (index % 2) != side ) {
12262             index++;
12263         }
12264
12265         while( index < forwardMostMove ) {
12266             /* Check to see if engine is in book */
12267             int depth = pvInfoList[index].depth;
12268             int score = pvInfoList[index].score;
12269             int in_book = 0;
12270
12271             if( depth <= 2 ) {
12272                 in_book = 1;
12273             }
12274             else if( score == 0 && depth == 63 ) {
12275                 in_book = 1; /* Zappa */
12276             }
12277             else if( score == 2 && depth == 99 ) {
12278                 in_book = 1; /* Abrok */
12279             }
12280
12281             has_book_hit += in_book;
12282
12283             if( ! in_book ) {
12284                 result = index;
12285
12286                 break;
12287             }
12288
12289             index += 2;
12290         }
12291     }
12292
12293     return result;
12294 }
12295
12296 void
12297 GetOutOfBookInfo (char * buf)
12298 {
12299     int oob[2];
12300     int i;
12301     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12302
12303     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12304     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12305
12306     *buf = '\0';
12307
12308     if( oob[0] >= 0 || oob[1] >= 0 ) {
12309         for( i=0; i<2; i++ ) {
12310             int idx = oob[i];
12311
12312             if( idx >= 0 ) {
12313                 if( i > 0 && oob[0] >= 0 ) {
12314                     strcat( buf, "   " );
12315                 }
12316
12317                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12318                 sprintf( buf+strlen(buf), "%s%.2f",
12319                     pvInfoList[idx].score >= 0 ? "+" : "",
12320                     pvInfoList[idx].score / 100.0 );
12321             }
12322         }
12323     }
12324 }
12325
12326 /* Save game in PGN style and close the file */
12327 int
12328 SaveGamePGN (FILE *f)
12329 {
12330     int i, offset, linelen, newblock;
12331     time_t tm;
12332 //    char *movetext;
12333     char numtext[32];
12334     int movelen, numlen, blank;
12335     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12336
12337     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12338
12339     tm = time((time_t *) NULL);
12340
12341     PrintPGNTags(f, &gameInfo);
12342
12343     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12344
12345     if (backwardMostMove > 0 || startedFromSetupPosition) {
12346         char *fen = PositionToFEN(backwardMostMove, NULL);
12347         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12348         fprintf(f, "\n{--------------\n");
12349         PrintPosition(f, backwardMostMove);
12350         fprintf(f, "--------------}\n");
12351         free(fen);
12352     }
12353     else {
12354         /* [AS] Out of book annotation */
12355         if( appData.saveOutOfBookInfo ) {
12356             char buf[64];
12357
12358             GetOutOfBookInfo( buf );
12359
12360             if( buf[0] != '\0' ) {
12361                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12362             }
12363         }
12364
12365         fprintf(f, "\n");
12366     }
12367
12368     i = backwardMostMove;
12369     linelen = 0;
12370     newblock = TRUE;
12371
12372     while (i < forwardMostMove) {
12373         /* Print comments preceding this move */
12374         if (commentList[i] != NULL) {
12375             if (linelen > 0) fprintf(f, "\n");
12376             fprintf(f, "%s", commentList[i]);
12377             linelen = 0;
12378             newblock = TRUE;
12379         }
12380
12381         /* Format move number */
12382         if ((i % 2) == 0)
12383           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12384         else
12385           if (newblock)
12386             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12387           else
12388             numtext[0] = NULLCHAR;
12389
12390         numlen = strlen(numtext);
12391         newblock = FALSE;
12392
12393         /* Print move number */
12394         blank = linelen > 0 && numlen > 0;
12395         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12396             fprintf(f, "\n");
12397             linelen = 0;
12398             blank = 0;
12399         }
12400         if (blank) {
12401             fprintf(f, " ");
12402             linelen++;
12403         }
12404         fprintf(f, "%s", numtext);
12405         linelen += numlen;
12406
12407         /* Get move */
12408         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12409         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12410
12411         /* Print move */
12412         blank = linelen > 0 && movelen > 0;
12413         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12414             fprintf(f, "\n");
12415             linelen = 0;
12416             blank = 0;
12417         }
12418         if (blank) {
12419             fprintf(f, " ");
12420             linelen++;
12421         }
12422         fprintf(f, "%s", move_buffer);
12423         linelen += movelen;
12424
12425         /* [AS] Add PV info if present */
12426         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12427             /* [HGM] add time */
12428             char buf[MSG_SIZ]; int seconds;
12429
12430             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12431
12432             if( seconds <= 0)
12433               buf[0] = 0;
12434             else
12435               if( seconds < 30 )
12436                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12437               else
12438                 {
12439                   seconds = (seconds + 4)/10; // round to full seconds
12440                   if( seconds < 60 )
12441                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12442                   else
12443                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12444                 }
12445
12446             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12447                       pvInfoList[i].score >= 0 ? "+" : "",
12448                       pvInfoList[i].score / 100.0,
12449                       pvInfoList[i].depth,
12450                       buf );
12451
12452             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12453
12454             /* Print score/depth */
12455             blank = linelen > 0 && movelen > 0;
12456             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12457                 fprintf(f, "\n");
12458                 linelen = 0;
12459                 blank = 0;
12460             }
12461             if (blank) {
12462                 fprintf(f, " ");
12463                 linelen++;
12464             }
12465             fprintf(f, "%s", move_buffer);
12466             linelen += movelen;
12467         }
12468
12469         i++;
12470     }
12471
12472     /* Start a new line */
12473     if (linelen > 0) fprintf(f, "\n");
12474
12475     /* Print comments after last move */
12476     if (commentList[i] != NULL) {
12477         fprintf(f, "%s\n", commentList[i]);
12478     }
12479
12480     /* Print result */
12481     if (gameInfo.resultDetails != NULL &&
12482         gameInfo.resultDetails[0] != NULLCHAR) {
12483         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12484                 PGNResult(gameInfo.result));
12485     } else {
12486         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12487     }
12488
12489     fclose(f);
12490     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12491     return TRUE;
12492 }
12493
12494 /* Save game in old style and close the file */
12495 int
12496 SaveGameOldStyle (FILE *f)
12497 {
12498     int i, offset;
12499     time_t tm;
12500
12501     tm = time((time_t *) NULL);
12502
12503     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12504     PrintOpponents(f);
12505
12506     if (backwardMostMove > 0 || startedFromSetupPosition) {
12507         fprintf(f, "\n[--------------\n");
12508         PrintPosition(f, backwardMostMove);
12509         fprintf(f, "--------------]\n");
12510     } else {
12511         fprintf(f, "\n");
12512     }
12513
12514     i = backwardMostMove;
12515     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12516
12517     while (i < forwardMostMove) {
12518         if (commentList[i] != NULL) {
12519             fprintf(f, "[%s]\n", commentList[i]);
12520         }
12521
12522         if ((i % 2) == 1) {
12523             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12524             i++;
12525         } else {
12526             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12527             i++;
12528             if (commentList[i] != NULL) {
12529                 fprintf(f, "\n");
12530                 continue;
12531             }
12532             if (i >= forwardMostMove) {
12533                 fprintf(f, "\n");
12534                 break;
12535             }
12536             fprintf(f, "%s\n", parseList[i]);
12537             i++;
12538         }
12539     }
12540
12541     if (commentList[i] != NULL) {
12542         fprintf(f, "[%s]\n", commentList[i]);
12543     }
12544
12545     /* This isn't really the old style, but it's close enough */
12546     if (gameInfo.resultDetails != NULL &&
12547         gameInfo.resultDetails[0] != NULLCHAR) {
12548         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12549                 gameInfo.resultDetails);
12550     } else {
12551         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12552     }
12553
12554     fclose(f);
12555     return TRUE;
12556 }
12557
12558 /* Save the current game to open file f and close the file */
12559 int
12560 SaveGame (FILE *f, int dummy, char *dummy2)
12561 {
12562     if (gameMode == EditPosition) EditPositionDone(TRUE);
12563     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12564     if (appData.oldSaveStyle)
12565       return SaveGameOldStyle(f);
12566     else
12567       return SaveGamePGN(f);
12568 }
12569
12570 /* Save the current position to the given file */
12571 int
12572 SavePositionToFile (char *filename)
12573 {
12574     FILE *f;
12575     char buf[MSG_SIZ];
12576
12577     if (strcmp(filename, "-") == 0) {
12578         return SavePosition(stdout, 0, NULL);
12579     } else {
12580         f = fopen(filename, "a");
12581         if (f == NULL) {
12582             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12583             DisplayError(buf, errno);
12584             return FALSE;
12585         } else {
12586             safeStrCpy(buf, lastMsg, MSG_SIZ);
12587             DisplayMessage(_("Waiting for access to save file"), "");
12588             flock(fileno(f), LOCK_EX); // [HGM] lock
12589             DisplayMessage(_("Saving position"), "");
12590             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12591             SavePosition(f, 0, NULL);
12592             DisplayMessage(buf, "");
12593             return TRUE;
12594         }
12595     }
12596 }
12597
12598 /* Save the current position to the given open file and close the file */
12599 int
12600 SavePosition (FILE *f, int dummy, char *dummy2)
12601 {
12602     time_t tm;
12603     char *fen;
12604
12605     if (gameMode == EditPosition) EditPositionDone(TRUE);
12606     if (appData.oldSaveStyle) {
12607         tm = time((time_t *) NULL);
12608
12609         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12610         PrintOpponents(f);
12611         fprintf(f, "[--------------\n");
12612         PrintPosition(f, currentMove);
12613         fprintf(f, "--------------]\n");
12614     } else {
12615         fen = PositionToFEN(currentMove, NULL);
12616         fprintf(f, "%s\n", fen);
12617         free(fen);
12618     }
12619     fclose(f);
12620     return TRUE;
12621 }
12622
12623 void
12624 ReloadCmailMsgEvent (int unregister)
12625 {
12626 #if !WIN32
12627     static char *inFilename = NULL;
12628     static char *outFilename;
12629     int i;
12630     struct stat inbuf, outbuf;
12631     int status;
12632
12633     /* Any registered moves are unregistered if unregister is set, */
12634     /* i.e. invoked by the signal handler */
12635     if (unregister) {
12636         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12637             cmailMoveRegistered[i] = FALSE;
12638             if (cmailCommentList[i] != NULL) {
12639                 free(cmailCommentList[i]);
12640                 cmailCommentList[i] = NULL;
12641             }
12642         }
12643         nCmailMovesRegistered = 0;
12644     }
12645
12646     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12647         cmailResult[i] = CMAIL_NOT_RESULT;
12648     }
12649     nCmailResults = 0;
12650
12651     if (inFilename == NULL) {
12652         /* Because the filenames are static they only get malloced once  */
12653         /* and they never get freed                                      */
12654         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12655         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12656
12657         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12658         sprintf(outFilename, "%s.out", appData.cmailGameName);
12659     }
12660
12661     status = stat(outFilename, &outbuf);
12662     if (status < 0) {
12663         cmailMailedMove = FALSE;
12664     } else {
12665         status = stat(inFilename, &inbuf);
12666         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12667     }
12668
12669     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12670        counts the games, notes how each one terminated, etc.
12671
12672        It would be nice to remove this kludge and instead gather all
12673        the information while building the game list.  (And to keep it
12674        in the game list nodes instead of having a bunch of fixed-size
12675        parallel arrays.)  Note this will require getting each game's
12676        termination from the PGN tags, as the game list builder does
12677        not process the game moves.  --mann
12678        */
12679     cmailMsgLoaded = TRUE;
12680     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12681
12682     /* Load first game in the file or popup game menu */
12683     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12684
12685 #endif /* !WIN32 */
12686     return;
12687 }
12688
12689 int
12690 RegisterMove ()
12691 {
12692     FILE *f;
12693     char string[MSG_SIZ];
12694
12695     if (   cmailMailedMove
12696         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12697         return TRUE;            /* Allow free viewing  */
12698     }
12699
12700     /* Unregister move to ensure that we don't leave RegisterMove        */
12701     /* with the move registered when the conditions for registering no   */
12702     /* longer hold                                                       */
12703     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12704         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12705         nCmailMovesRegistered --;
12706
12707         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12708           {
12709               free(cmailCommentList[lastLoadGameNumber - 1]);
12710               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12711           }
12712     }
12713
12714     if (cmailOldMove == -1) {
12715         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12716         return FALSE;
12717     }
12718
12719     if (currentMove > cmailOldMove + 1) {
12720         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12721         return FALSE;
12722     }
12723
12724     if (currentMove < cmailOldMove) {
12725         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12726         return FALSE;
12727     }
12728
12729     if (forwardMostMove > currentMove) {
12730         /* Silently truncate extra moves */
12731         TruncateGame();
12732     }
12733
12734     if (   (currentMove == cmailOldMove + 1)
12735         || (   (currentMove == cmailOldMove)
12736             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12737                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12738         if (gameInfo.result != GameUnfinished) {
12739             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12740         }
12741
12742         if (commentList[currentMove] != NULL) {
12743             cmailCommentList[lastLoadGameNumber - 1]
12744               = StrSave(commentList[currentMove]);
12745         }
12746         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12747
12748         if (appData.debugMode)
12749           fprintf(debugFP, "Saving %s for game %d\n",
12750                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12751
12752         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12753
12754         f = fopen(string, "w");
12755         if (appData.oldSaveStyle) {
12756             SaveGameOldStyle(f); /* also closes the file */
12757
12758             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12759             f = fopen(string, "w");
12760             SavePosition(f, 0, NULL); /* also closes the file */
12761         } else {
12762             fprintf(f, "{--------------\n");
12763             PrintPosition(f, currentMove);
12764             fprintf(f, "--------------}\n\n");
12765
12766             SaveGame(f, 0, NULL); /* also closes the file*/
12767         }
12768
12769         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12770         nCmailMovesRegistered ++;
12771     } else if (nCmailGames == 1) {
12772         DisplayError(_("You have not made a move yet"), 0);
12773         return FALSE;
12774     }
12775
12776     return TRUE;
12777 }
12778
12779 void
12780 MailMoveEvent ()
12781 {
12782 #if !WIN32
12783     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12784     FILE *commandOutput;
12785     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12786     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12787     int nBuffers;
12788     int i;
12789     int archived;
12790     char *arcDir;
12791
12792     if (! cmailMsgLoaded) {
12793         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12794         return;
12795     }
12796
12797     if (nCmailGames == nCmailResults) {
12798         DisplayError(_("No unfinished games"), 0);
12799         return;
12800     }
12801
12802 #if CMAIL_PROHIBIT_REMAIL
12803     if (cmailMailedMove) {
12804       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);
12805         DisplayError(msg, 0);
12806         return;
12807     }
12808 #endif
12809
12810     if (! (cmailMailedMove || RegisterMove())) return;
12811
12812     if (   cmailMailedMove
12813         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12814       snprintf(string, MSG_SIZ, partCommandString,
12815                appData.debugMode ? " -v" : "", appData.cmailGameName);
12816         commandOutput = popen(string, "r");
12817
12818         if (commandOutput == NULL) {
12819             DisplayError(_("Failed to invoke cmail"), 0);
12820         } else {
12821             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12822                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12823             }
12824             if (nBuffers > 1) {
12825                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12826                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12827                 nBytes = MSG_SIZ - 1;
12828             } else {
12829                 (void) memcpy(msg, buffer, nBytes);
12830             }
12831             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12832
12833             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12834                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12835
12836                 archived = TRUE;
12837                 for (i = 0; i < nCmailGames; i ++) {
12838                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12839                         archived = FALSE;
12840                     }
12841                 }
12842                 if (   archived
12843                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12844                         != NULL)) {
12845                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12846                            arcDir,
12847                            appData.cmailGameName,
12848                            gameInfo.date);
12849                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12850                     cmailMsgLoaded = FALSE;
12851                 }
12852             }
12853
12854             DisplayInformation(msg);
12855             pclose(commandOutput);
12856         }
12857     } else {
12858         if ((*cmailMsg) != '\0') {
12859             DisplayInformation(cmailMsg);
12860         }
12861     }
12862
12863     return;
12864 #endif /* !WIN32 */
12865 }
12866
12867 char *
12868 CmailMsg ()
12869 {
12870 #if WIN32
12871     return NULL;
12872 #else
12873     int  prependComma = 0;
12874     char number[5];
12875     char string[MSG_SIZ];       /* Space for game-list */
12876     int  i;
12877
12878     if (!cmailMsgLoaded) return "";
12879
12880     if (cmailMailedMove) {
12881       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12882     } else {
12883         /* Create a list of games left */
12884       snprintf(string, MSG_SIZ, "[");
12885         for (i = 0; i < nCmailGames; i ++) {
12886             if (! (   cmailMoveRegistered[i]
12887                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12888                 if (prependComma) {
12889                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12890                 } else {
12891                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12892                     prependComma = 1;
12893                 }
12894
12895                 strcat(string, number);
12896             }
12897         }
12898         strcat(string, "]");
12899
12900         if (nCmailMovesRegistered + nCmailResults == 0) {
12901             switch (nCmailGames) {
12902               case 1:
12903                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12904                 break;
12905
12906               case 2:
12907                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12908                 break;
12909
12910               default:
12911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12912                          nCmailGames);
12913                 break;
12914             }
12915         } else {
12916             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12917               case 1:
12918                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12919                          string);
12920                 break;
12921
12922               case 0:
12923                 if (nCmailResults == nCmailGames) {
12924                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12925                 } else {
12926                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12927                 }
12928                 break;
12929
12930               default:
12931                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12932                          string);
12933             }
12934         }
12935     }
12936     return cmailMsg;
12937 #endif /* WIN32 */
12938 }
12939
12940 void
12941 ResetGameEvent ()
12942 {
12943     if (gameMode == Training)
12944       SetTrainingModeOff();
12945
12946     Reset(TRUE, TRUE);
12947     cmailMsgLoaded = FALSE;
12948     if (appData.icsActive) {
12949       SendToICS(ics_prefix);
12950       SendToICS("refresh\n");
12951     }
12952 }
12953
12954 void
12955 ExitEvent (int status)
12956 {
12957     exiting++;
12958     if (exiting > 2) {
12959       /* Give up on clean exit */
12960       exit(status);
12961     }
12962     if (exiting > 1) {
12963       /* Keep trying for clean exit */
12964       return;
12965     }
12966
12967     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12968
12969     if (telnetISR != NULL) {
12970       RemoveInputSource(telnetISR);
12971     }
12972     if (icsPR != NoProc) {
12973       DestroyChildProcess(icsPR, TRUE);
12974     }
12975
12976     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12977     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12978
12979     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12980     /* make sure this other one finishes before killing it!                  */
12981     if(endingGame) { int count = 0;
12982         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12983         while(endingGame && count++ < 10) DoSleep(1);
12984         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12985     }
12986
12987     /* Kill off chess programs */
12988     if (first.pr != NoProc) {
12989         ExitAnalyzeMode();
12990
12991         DoSleep( appData.delayBeforeQuit );
12992         SendToProgram("quit\n", &first);
12993         DoSleep( appData.delayAfterQuit );
12994         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12995     }
12996     if (second.pr != NoProc) {
12997         DoSleep( appData.delayBeforeQuit );
12998         SendToProgram("quit\n", &second);
12999         DoSleep( appData.delayAfterQuit );
13000         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13001     }
13002     if (first.isr != NULL) {
13003         RemoveInputSource(first.isr);
13004     }
13005     if (second.isr != NULL) {
13006         RemoveInputSource(second.isr);
13007     }
13008
13009     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13010     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13011
13012     ShutDownFrontEnd();
13013     exit(status);
13014 }
13015
13016 void
13017 PauseEvent ()
13018 {
13019     if (appData.debugMode)
13020         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13021     if (pausing) {
13022         pausing = FALSE;
13023         ModeHighlight();
13024         if (gameMode == MachinePlaysWhite ||
13025             gameMode == MachinePlaysBlack) {
13026             StartClocks();
13027         } else {
13028             DisplayBothClocks();
13029         }
13030         if (gameMode == PlayFromGameFile) {
13031             if (appData.timeDelay >= 0)
13032                 AutoPlayGameLoop();
13033         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13034             Reset(FALSE, TRUE);
13035             SendToICS(ics_prefix);
13036             SendToICS("refresh\n");
13037         } else if (currentMove < forwardMostMove) {
13038             ForwardInner(forwardMostMove);
13039         }
13040         pauseExamInvalid = FALSE;
13041     } else {
13042         switch (gameMode) {
13043           default:
13044             return;
13045           case IcsExamining:
13046             pauseExamForwardMostMove = forwardMostMove;
13047             pauseExamInvalid = FALSE;
13048             /* fall through */
13049           case IcsObserving:
13050           case IcsPlayingWhite:
13051           case IcsPlayingBlack:
13052             pausing = TRUE;
13053             ModeHighlight();
13054             return;
13055           case PlayFromGameFile:
13056             (void) StopLoadGameTimer();
13057             pausing = TRUE;
13058             ModeHighlight();
13059             break;
13060           case BeginningOfGame:
13061             if (appData.icsActive) return;
13062             /* else fall through */
13063           case MachinePlaysWhite:
13064           case MachinePlaysBlack:
13065           case TwoMachinesPlay:
13066             if (forwardMostMove == 0)
13067               return;           /* don't pause if no one has moved */
13068             if ((gameMode == MachinePlaysWhite &&
13069                  !WhiteOnMove(forwardMostMove)) ||
13070                 (gameMode == MachinePlaysBlack &&
13071                  WhiteOnMove(forwardMostMove))) {
13072                 StopClocks();
13073             }
13074             pausing = TRUE;
13075             ModeHighlight();
13076             break;
13077         }
13078     }
13079 }
13080
13081 void
13082 EditCommentEvent ()
13083 {
13084     char title[MSG_SIZ];
13085
13086     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13087       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13088     } else {
13089       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13090                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13091                parseList[currentMove - 1]);
13092     }
13093
13094     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13095 }
13096
13097
13098 void
13099 EditTagsEvent ()
13100 {
13101     char *tags = PGNTags(&gameInfo);
13102     bookUp = FALSE;
13103     EditTagsPopUp(tags, NULL);
13104     free(tags);
13105 }
13106
13107 void
13108 AnalyzeModeEvent ()
13109 {
13110     if (appData.noChessProgram || gameMode == AnalyzeMode)
13111       return;
13112
13113     if (gameMode != AnalyzeFile) {
13114         if (!appData.icsEngineAnalyze) {
13115                EditGameEvent();
13116                if (gameMode != EditGame) return;
13117         }
13118         ResurrectChessProgram();
13119         SendToProgram("analyze\n", &first);
13120         first.analyzing = TRUE;
13121         /*first.maybeThinking = TRUE;*/
13122         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13123         EngineOutputPopUp();
13124     }
13125     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13126     pausing = FALSE;
13127     ModeHighlight();
13128     SetGameInfo();
13129
13130     StartAnalysisClock();
13131     GetTimeMark(&lastNodeCountTime);
13132     lastNodeCount = 0;
13133 }
13134
13135 void
13136 AnalyzeFileEvent ()
13137 {
13138     if (appData.noChessProgram || gameMode == AnalyzeFile)
13139       return;
13140
13141     if (gameMode != AnalyzeMode) {
13142         EditGameEvent();
13143         if (gameMode != EditGame) return;
13144         ResurrectChessProgram();
13145         SendToProgram("analyze\n", &first);
13146         first.analyzing = TRUE;
13147         /*first.maybeThinking = TRUE;*/
13148         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13149         EngineOutputPopUp();
13150     }
13151     gameMode = AnalyzeFile;
13152     pausing = FALSE;
13153     ModeHighlight();
13154     SetGameInfo();
13155
13156     StartAnalysisClock();
13157     GetTimeMark(&lastNodeCountTime);
13158     lastNodeCount = 0;
13159     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13160 }
13161
13162 void
13163 MachineWhiteEvent ()
13164 {
13165     char buf[MSG_SIZ];
13166     char *bookHit = NULL;
13167
13168     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13169       return;
13170
13171
13172     if (gameMode == PlayFromGameFile ||
13173         gameMode == TwoMachinesPlay  ||
13174         gameMode == Training         ||
13175         gameMode == AnalyzeMode      ||
13176         gameMode == EndOfGame)
13177         EditGameEvent();
13178
13179     if (gameMode == EditPosition)
13180         EditPositionDone(TRUE);
13181
13182     if (!WhiteOnMove(currentMove)) {
13183         DisplayError(_("It is not White's turn"), 0);
13184         return;
13185     }
13186
13187     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13188       ExitAnalyzeMode();
13189
13190     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13191         gameMode == AnalyzeFile)
13192         TruncateGame();
13193
13194     ResurrectChessProgram();    /* in case it isn't running */
13195     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13196         gameMode = MachinePlaysWhite;
13197         ResetClocks();
13198     } else
13199     gameMode = MachinePlaysWhite;
13200     pausing = FALSE;
13201     ModeHighlight();
13202     SetGameInfo();
13203     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13204     DisplayTitle(buf);
13205     if (first.sendName) {
13206       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13207       SendToProgram(buf, &first);
13208     }
13209     if (first.sendTime) {
13210       if (first.useColors) {
13211         SendToProgram("black\n", &first); /*gnu kludge*/
13212       }
13213       SendTimeRemaining(&first, TRUE);
13214     }
13215     if (first.useColors) {
13216       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13217     }
13218     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13219     SetMachineThinkingEnables();
13220     first.maybeThinking = TRUE;
13221     StartClocks();
13222     firstMove = FALSE;
13223
13224     if (appData.autoFlipView && !flipView) {
13225       flipView = !flipView;
13226       DrawPosition(FALSE, NULL);
13227       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13228     }
13229
13230     if(bookHit) { // [HGM] book: simulate book reply
13231         static char bookMove[MSG_SIZ]; // a bit generous?
13232
13233         programStats.nodes = programStats.depth = programStats.time =
13234         programStats.score = programStats.got_only_move = 0;
13235         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13236
13237         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13238         strcat(bookMove, bookHit);
13239         HandleMachineMove(bookMove, &first);
13240     }
13241 }
13242
13243 void
13244 MachineBlackEvent ()
13245 {
13246   char buf[MSG_SIZ];
13247   char *bookHit = NULL;
13248
13249     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13250         return;
13251
13252
13253     if (gameMode == PlayFromGameFile ||
13254         gameMode == TwoMachinesPlay  ||
13255         gameMode == Training         ||
13256         gameMode == AnalyzeMode      ||
13257         gameMode == EndOfGame)
13258         EditGameEvent();
13259
13260     if (gameMode == EditPosition)
13261         EditPositionDone(TRUE);
13262
13263     if (WhiteOnMove(currentMove)) {
13264         DisplayError(_("It is not Black's turn"), 0);
13265         return;
13266     }
13267
13268     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13269       ExitAnalyzeMode();
13270
13271     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13272         gameMode == AnalyzeFile)
13273         TruncateGame();
13274
13275     ResurrectChessProgram();    /* in case it isn't running */
13276     gameMode = MachinePlaysBlack;
13277     pausing = FALSE;
13278     ModeHighlight();
13279     SetGameInfo();
13280     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13281     DisplayTitle(buf);
13282     if (first.sendName) {
13283       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13284       SendToProgram(buf, &first);
13285     }
13286     if (first.sendTime) {
13287       if (first.useColors) {
13288         SendToProgram("white\n", &first); /*gnu kludge*/
13289       }
13290       SendTimeRemaining(&first, FALSE);
13291     }
13292     if (first.useColors) {
13293       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13294     }
13295     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13296     SetMachineThinkingEnables();
13297     first.maybeThinking = TRUE;
13298     StartClocks();
13299
13300     if (appData.autoFlipView && flipView) {
13301       flipView = !flipView;
13302       DrawPosition(FALSE, NULL);
13303       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13304     }
13305     if(bookHit) { // [HGM] book: simulate book reply
13306         static char bookMove[MSG_SIZ]; // a bit generous?
13307
13308         programStats.nodes = programStats.depth = programStats.time =
13309         programStats.score = programStats.got_only_move = 0;
13310         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13311
13312         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13313         strcat(bookMove, bookHit);
13314         HandleMachineMove(bookMove, &first);
13315     }
13316 }
13317
13318
13319 void
13320 DisplayTwoMachinesTitle ()
13321 {
13322     char buf[MSG_SIZ];
13323     if (appData.matchGames > 0) {
13324         if(appData.tourneyFile[0]) {
13325           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13326                    gameInfo.white, _("vs."), gameInfo.black,
13327                    nextGame+1, appData.matchGames+1,
13328                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13329         } else 
13330         if (first.twoMachinesColor[0] == 'w') {
13331           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13332                    gameInfo.white, _("vs."),  gameInfo.black,
13333                    first.matchWins, second.matchWins,
13334                    matchGame - 1 - (first.matchWins + second.matchWins));
13335         } else {
13336           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13337                    gameInfo.white, _("vs."), gameInfo.black,
13338                    second.matchWins, first.matchWins,
13339                    matchGame - 1 - (first.matchWins + second.matchWins));
13340         }
13341     } else {
13342       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13343     }
13344     DisplayTitle(buf);
13345 }
13346
13347 void
13348 SettingsMenuIfReady ()
13349 {
13350   if (second.lastPing != second.lastPong) {
13351     DisplayMessage("", _("Waiting for second chess program"));
13352     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13353     return;
13354   }
13355   ThawUI();
13356   DisplayMessage("", "");
13357   SettingsPopUp(&second);
13358 }
13359
13360 int
13361 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13362 {
13363     char buf[MSG_SIZ];
13364     if (cps->pr == NoProc) {
13365         StartChessProgram(cps);
13366         if (cps->protocolVersion == 1) {
13367           retry();
13368         } else {
13369           /* kludge: allow timeout for initial "feature" command */
13370           FreezeUI();
13371           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13372           DisplayMessage("", buf);
13373           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13374         }
13375         return 1;
13376     }
13377     return 0;
13378 }
13379
13380 void
13381 TwoMachinesEvent P((void))
13382 {
13383     int i;
13384     char buf[MSG_SIZ];
13385     ChessProgramState *onmove;
13386     char *bookHit = NULL;
13387     static int stalling = 0;
13388     TimeMark now;
13389     long wait;
13390
13391     if (appData.noChessProgram) return;
13392
13393     switch (gameMode) {
13394       case TwoMachinesPlay:
13395         return;
13396       case MachinePlaysWhite:
13397       case MachinePlaysBlack:
13398         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13399             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13400             return;
13401         }
13402         /* fall through */
13403       case BeginningOfGame:
13404       case PlayFromGameFile:
13405       case EndOfGame:
13406         EditGameEvent();
13407         if (gameMode != EditGame) return;
13408         break;
13409       case EditPosition:
13410         EditPositionDone(TRUE);
13411         break;
13412       case AnalyzeMode:
13413       case AnalyzeFile:
13414         ExitAnalyzeMode();
13415         break;
13416       case EditGame:
13417       default:
13418         break;
13419     }
13420
13421 //    forwardMostMove = currentMove;
13422     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13423
13424     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13425
13426     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13427     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13428       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13429       return;
13430     }
13431     if(!stalling) {
13432       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13433       SendToProgram("force\n", &second);
13434       stalling = 1;
13435       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13436       return;
13437     }
13438     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13439     if(appData.matchPause>10000 || appData.matchPause<10)
13440                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13441     wait = SubtractTimeMarks(&now, &pauseStart);
13442     if(wait < appData.matchPause) {
13443         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13444         return;
13445     }
13446     // we are now committed to starting the game
13447     stalling = 0;
13448     DisplayMessage("", "");
13449     if (startedFromSetupPosition) {
13450         SendBoard(&second, backwardMostMove);
13451     if (appData.debugMode) {
13452         fprintf(debugFP, "Two Machines\n");
13453     }
13454     }
13455     for (i = backwardMostMove; i < forwardMostMove; i++) {
13456         SendMoveToProgram(i, &second);
13457     }
13458
13459     gameMode = TwoMachinesPlay;
13460     pausing = FALSE;
13461     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13462     SetGameInfo();
13463     DisplayTwoMachinesTitle();
13464     firstMove = TRUE;
13465     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13466         onmove = &first;
13467     } else {
13468         onmove = &second;
13469     }
13470     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13471     SendToProgram(first.computerString, &first);
13472     if (first.sendName) {
13473       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13474       SendToProgram(buf, &first);
13475     }
13476     SendToProgram(second.computerString, &second);
13477     if (second.sendName) {
13478       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13479       SendToProgram(buf, &second);
13480     }
13481
13482     ResetClocks();
13483     if (!first.sendTime || !second.sendTime) {
13484         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13485         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13486     }
13487     if (onmove->sendTime) {
13488       if (onmove->useColors) {
13489         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13490       }
13491       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13492     }
13493     if (onmove->useColors) {
13494       SendToProgram(onmove->twoMachinesColor, onmove);
13495     }
13496     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13497 //    SendToProgram("go\n", onmove);
13498     onmove->maybeThinking = TRUE;
13499     SetMachineThinkingEnables();
13500
13501     StartClocks();
13502
13503     if(bookHit) { // [HGM] book: simulate book reply
13504         static char bookMove[MSG_SIZ]; // a bit generous?
13505
13506         programStats.nodes = programStats.depth = programStats.time =
13507         programStats.score = programStats.got_only_move = 0;
13508         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13509
13510         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13511         strcat(bookMove, bookHit);
13512         savedMessage = bookMove; // args for deferred call
13513         savedState = onmove;
13514         ScheduleDelayedEvent(DeferredBookMove, 1);
13515     }
13516 }
13517
13518 void
13519 TrainingEvent ()
13520 {
13521     if (gameMode == Training) {
13522       SetTrainingModeOff();
13523       gameMode = PlayFromGameFile;
13524       DisplayMessage("", _("Training mode off"));
13525     } else {
13526       gameMode = Training;
13527       animateTraining = appData.animate;
13528
13529       /* make sure we are not already at the end of the game */
13530       if (currentMove < forwardMostMove) {
13531         SetTrainingModeOn();
13532         DisplayMessage("", _("Training mode on"));
13533       } else {
13534         gameMode = PlayFromGameFile;
13535         DisplayError(_("Already at end of game"), 0);
13536       }
13537     }
13538     ModeHighlight();
13539 }
13540
13541 void
13542 IcsClientEvent ()
13543 {
13544     if (!appData.icsActive) return;
13545     switch (gameMode) {
13546       case IcsPlayingWhite:
13547       case IcsPlayingBlack:
13548       case IcsObserving:
13549       case IcsIdle:
13550       case BeginningOfGame:
13551       case IcsExamining:
13552         return;
13553
13554       case EditGame:
13555         break;
13556
13557       case EditPosition:
13558         EditPositionDone(TRUE);
13559         break;
13560
13561       case AnalyzeMode:
13562       case AnalyzeFile:
13563         ExitAnalyzeMode();
13564         break;
13565
13566       default:
13567         EditGameEvent();
13568         break;
13569     }
13570
13571     gameMode = IcsIdle;
13572     ModeHighlight();
13573     return;
13574 }
13575
13576 void
13577 EditGameEvent ()
13578 {
13579     int i;
13580
13581     switch (gameMode) {
13582       case Training:
13583         SetTrainingModeOff();
13584         break;
13585       case MachinePlaysWhite:
13586       case MachinePlaysBlack:
13587       case BeginningOfGame:
13588         SendToProgram("force\n", &first);
13589         SetUserThinkingEnables();
13590         break;
13591       case PlayFromGameFile:
13592         (void) StopLoadGameTimer();
13593         if (gameFileFP != NULL) {
13594             gameFileFP = NULL;
13595         }
13596         break;
13597       case EditPosition:
13598         EditPositionDone(TRUE);
13599         break;
13600       case AnalyzeMode:
13601       case AnalyzeFile:
13602         ExitAnalyzeMode();
13603         SendToProgram("force\n", &first);
13604         break;
13605       case TwoMachinesPlay:
13606         GameEnds(EndOfFile, NULL, GE_PLAYER);
13607         ResurrectChessProgram();
13608         SetUserThinkingEnables();
13609         break;
13610       case EndOfGame:
13611         ResurrectChessProgram();
13612         break;
13613       case IcsPlayingBlack:
13614       case IcsPlayingWhite:
13615         DisplayError(_("Warning: You are still playing a game"), 0);
13616         break;
13617       case IcsObserving:
13618         DisplayError(_("Warning: You are still observing a game"), 0);
13619         break;
13620       case IcsExamining:
13621         DisplayError(_("Warning: You are still examining a game"), 0);
13622         break;
13623       case IcsIdle:
13624         break;
13625       case EditGame:
13626       default:
13627         return;
13628     }
13629
13630     pausing = FALSE;
13631     StopClocks();
13632     first.offeredDraw = second.offeredDraw = 0;
13633
13634     if (gameMode == PlayFromGameFile) {
13635         whiteTimeRemaining = timeRemaining[0][currentMove];
13636         blackTimeRemaining = timeRemaining[1][currentMove];
13637         DisplayTitle("");
13638     }
13639
13640     if (gameMode == MachinePlaysWhite ||
13641         gameMode == MachinePlaysBlack ||
13642         gameMode == TwoMachinesPlay ||
13643         gameMode == EndOfGame) {
13644         i = forwardMostMove;
13645         while (i > currentMove) {
13646             SendToProgram("undo\n", &first);
13647             i--;
13648         }
13649         if(!adjustedClock) {
13650         whiteTimeRemaining = timeRemaining[0][currentMove];
13651         blackTimeRemaining = timeRemaining[1][currentMove];
13652         DisplayBothClocks();
13653         }
13654         if (whiteFlag || blackFlag) {
13655             whiteFlag = blackFlag = 0;
13656         }
13657         DisplayTitle("");
13658     }
13659
13660     gameMode = EditGame;
13661     ModeHighlight();
13662     SetGameInfo();
13663 }
13664
13665
13666 void
13667 EditPositionEvent ()
13668 {
13669     if (gameMode == EditPosition) {
13670         EditGameEvent();
13671         return;
13672     }
13673
13674     EditGameEvent();
13675     if (gameMode != EditGame) return;
13676
13677     gameMode = EditPosition;
13678     ModeHighlight();
13679     SetGameInfo();
13680     if (currentMove > 0)
13681       CopyBoard(boards[0], boards[currentMove]);
13682
13683     blackPlaysFirst = !WhiteOnMove(currentMove);
13684     ResetClocks();
13685     currentMove = forwardMostMove = backwardMostMove = 0;
13686     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13687     DisplayMove(-1);
13688 }
13689
13690 void
13691 ExitAnalyzeMode ()
13692 {
13693     /* [DM] icsEngineAnalyze - possible call from other functions */
13694     if (appData.icsEngineAnalyze) {
13695         appData.icsEngineAnalyze = FALSE;
13696
13697         DisplayMessage("",_("Close ICS engine analyze..."));
13698     }
13699     if (first.analysisSupport && first.analyzing) {
13700       SendToProgram("exit\n", &first);
13701       first.analyzing = FALSE;
13702     }
13703     thinkOutput[0] = NULLCHAR;
13704 }
13705
13706 void
13707 EditPositionDone (Boolean fakeRights)
13708 {
13709     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13710
13711     startedFromSetupPosition = TRUE;
13712     InitChessProgram(&first, FALSE);
13713     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13714       boards[0][EP_STATUS] = EP_NONE;
13715       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13716     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13717         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13718         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13719       } else boards[0][CASTLING][2] = NoRights;
13720     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13721         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13722         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13723       } else boards[0][CASTLING][5] = NoRights;
13724     }
13725     SendToProgram("force\n", &first);
13726     if (blackPlaysFirst) {
13727         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13728         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13729         currentMove = forwardMostMove = backwardMostMove = 1;
13730         CopyBoard(boards[1], boards[0]);
13731     } else {
13732         currentMove = forwardMostMove = backwardMostMove = 0;
13733     }
13734     SendBoard(&first, forwardMostMove);
13735     if (appData.debugMode) {
13736         fprintf(debugFP, "EditPosDone\n");
13737     }
13738     DisplayTitle("");
13739     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13740     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13741     gameMode = EditGame;
13742     ModeHighlight();
13743     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13744     ClearHighlights(); /* [AS] */
13745 }
13746
13747 /* Pause for `ms' milliseconds */
13748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13749 void
13750 TimeDelay (long ms)
13751 {
13752     TimeMark m1, m2;
13753
13754     GetTimeMark(&m1);
13755     do {
13756         GetTimeMark(&m2);
13757     } while (SubtractTimeMarks(&m2, &m1) < ms);
13758 }
13759
13760 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13761 void
13762 SendMultiLineToICS (char *buf)
13763 {
13764     char temp[MSG_SIZ+1], *p;
13765     int len;
13766
13767     len = strlen(buf);
13768     if (len > MSG_SIZ)
13769       len = MSG_SIZ;
13770
13771     strncpy(temp, buf, len);
13772     temp[len] = 0;
13773
13774     p = temp;
13775     while (*p) {
13776         if (*p == '\n' || *p == '\r')
13777           *p = ' ';
13778         ++p;
13779     }
13780
13781     strcat(temp, "\n");
13782     SendToICS(temp);
13783     SendToPlayer(temp, strlen(temp));
13784 }
13785
13786 void
13787 SetWhiteToPlayEvent ()
13788 {
13789     if (gameMode == EditPosition) {
13790         blackPlaysFirst = FALSE;
13791         DisplayBothClocks();    /* works because currentMove is 0 */
13792     } else if (gameMode == IcsExamining) {
13793         SendToICS(ics_prefix);
13794         SendToICS("tomove white\n");
13795     }
13796 }
13797
13798 void
13799 SetBlackToPlayEvent ()
13800 {
13801     if (gameMode == EditPosition) {
13802         blackPlaysFirst = TRUE;
13803         currentMove = 1;        /* kludge */
13804         DisplayBothClocks();
13805         currentMove = 0;
13806     } else if (gameMode == IcsExamining) {
13807         SendToICS(ics_prefix);
13808         SendToICS("tomove black\n");
13809     }
13810 }
13811
13812 void
13813 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13814 {
13815     char buf[MSG_SIZ];
13816     ChessSquare piece = boards[0][y][x];
13817
13818     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13819
13820     switch (selection) {
13821       case ClearBoard:
13822         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13823             SendToICS(ics_prefix);
13824             SendToICS("bsetup clear\n");
13825         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13826             SendToICS(ics_prefix);
13827             SendToICS("clearboard\n");
13828         } else {
13829             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13830                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13831                 for (y = 0; y < BOARD_HEIGHT; y++) {
13832                     if (gameMode == IcsExamining) {
13833                         if (boards[currentMove][y][x] != EmptySquare) {
13834                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13835                                     AAA + x, ONE + y);
13836                             SendToICS(buf);
13837                         }
13838                     } else {
13839                         boards[0][y][x] = p;
13840                     }
13841                 }
13842             }
13843         }
13844         if (gameMode == EditPosition) {
13845             DrawPosition(FALSE, boards[0]);
13846         }
13847         break;
13848
13849       case WhitePlay:
13850         SetWhiteToPlayEvent();
13851         break;
13852
13853       case BlackPlay:
13854         SetBlackToPlayEvent();
13855         break;
13856
13857       case EmptySquare:
13858         if (gameMode == IcsExamining) {
13859             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13860             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13861             SendToICS(buf);
13862         } else {
13863             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13864                 if(x == BOARD_LEFT-2) {
13865                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13866                     boards[0][y][1] = 0;
13867                 } else
13868                 if(x == BOARD_RGHT+1) {
13869                     if(y >= gameInfo.holdingsSize) break;
13870                     boards[0][y][BOARD_WIDTH-2] = 0;
13871                 } else break;
13872             }
13873             boards[0][y][x] = EmptySquare;
13874             DrawPosition(FALSE, boards[0]);
13875         }
13876         break;
13877
13878       case PromotePiece:
13879         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13880            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13881             selection = (ChessSquare) (PROMOTED piece);
13882         } else if(piece == EmptySquare) selection = WhiteSilver;
13883         else selection = (ChessSquare)((int)piece - 1);
13884         goto defaultlabel;
13885
13886       case DemotePiece:
13887         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13888            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13889             selection = (ChessSquare) (DEMOTED piece);
13890         } else if(piece == EmptySquare) selection = BlackSilver;
13891         else selection = (ChessSquare)((int)piece + 1);
13892         goto defaultlabel;
13893
13894       case WhiteQueen:
13895       case BlackQueen:
13896         if(gameInfo.variant == VariantShatranj ||
13897            gameInfo.variant == VariantXiangqi  ||
13898            gameInfo.variant == VariantCourier  ||
13899            gameInfo.variant == VariantMakruk     )
13900             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13901         goto defaultlabel;
13902
13903       case WhiteKing:
13904       case BlackKing:
13905         if(gameInfo.variant == VariantXiangqi)
13906             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13907         if(gameInfo.variant == VariantKnightmate)
13908             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13909       default:
13910         defaultlabel:
13911         if (gameMode == IcsExamining) {
13912             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13913             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13914                      PieceToChar(selection), AAA + x, ONE + y);
13915             SendToICS(buf);
13916         } else {
13917             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13918                 int n;
13919                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13920                     n = PieceToNumber(selection - BlackPawn);
13921                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13922                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13923                     boards[0][BOARD_HEIGHT-1-n][1]++;
13924                 } else
13925                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13926                     n = PieceToNumber(selection);
13927                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13928                     boards[0][n][BOARD_WIDTH-1] = selection;
13929                     boards[0][n][BOARD_WIDTH-2]++;
13930                 }
13931             } else
13932             boards[0][y][x] = selection;
13933             DrawPosition(TRUE, boards[0]);
13934             ClearHighlights();
13935             fromX = fromY = -1;
13936         }
13937         break;
13938     }
13939 }
13940
13941
13942 void
13943 DropMenuEvent (ChessSquare selection, int x, int y)
13944 {
13945     ChessMove moveType;
13946
13947     switch (gameMode) {
13948       case IcsPlayingWhite:
13949       case MachinePlaysBlack:
13950         if (!WhiteOnMove(currentMove)) {
13951             DisplayMoveError(_("It is Black's turn"));
13952             return;
13953         }
13954         moveType = WhiteDrop;
13955         break;
13956       case IcsPlayingBlack:
13957       case MachinePlaysWhite:
13958         if (WhiteOnMove(currentMove)) {
13959             DisplayMoveError(_("It is White's turn"));
13960             return;
13961         }
13962         moveType = BlackDrop;
13963         break;
13964       case EditGame:
13965         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13966         break;
13967       default:
13968         return;
13969     }
13970
13971     if (moveType == BlackDrop && selection < BlackPawn) {
13972       selection = (ChessSquare) ((int) selection
13973                                  + (int) BlackPawn - (int) WhitePawn);
13974     }
13975     if (boards[currentMove][y][x] != EmptySquare) {
13976         DisplayMoveError(_("That square is occupied"));
13977         return;
13978     }
13979
13980     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13981 }
13982
13983 void
13984 AcceptEvent ()
13985 {
13986     /* Accept a pending offer of any kind from opponent */
13987
13988     if (appData.icsActive) {
13989         SendToICS(ics_prefix);
13990         SendToICS("accept\n");
13991     } else if (cmailMsgLoaded) {
13992         if (currentMove == cmailOldMove &&
13993             commentList[cmailOldMove] != NULL &&
13994             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13995                    "Black offers a draw" : "White offers a draw")) {
13996             TruncateGame();
13997             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13998             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13999         } else {
14000             DisplayError(_("There is no pending offer on this move"), 0);
14001             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14002         }
14003     } else {
14004         /* Not used for offers from chess program */
14005     }
14006 }
14007
14008 void
14009 DeclineEvent ()
14010 {
14011     /* Decline a pending offer of any kind from opponent */
14012
14013     if (appData.icsActive) {
14014         SendToICS(ics_prefix);
14015         SendToICS("decline\n");
14016     } else if (cmailMsgLoaded) {
14017         if (currentMove == cmailOldMove &&
14018             commentList[cmailOldMove] != NULL &&
14019             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14020                    "Black offers a draw" : "White offers a draw")) {
14021 #ifdef NOTDEF
14022             AppendComment(cmailOldMove, "Draw declined", TRUE);
14023             DisplayComment(cmailOldMove - 1, "Draw declined");
14024 #endif /*NOTDEF*/
14025         } else {
14026             DisplayError(_("There is no pending offer on this move"), 0);
14027         }
14028     } else {
14029         /* Not used for offers from chess program */
14030     }
14031 }
14032
14033 void
14034 RematchEvent ()
14035 {
14036     /* Issue ICS rematch command */
14037     if (appData.icsActive) {
14038         SendToICS(ics_prefix);
14039         SendToICS("rematch\n");
14040     }
14041 }
14042
14043 void
14044 CallFlagEvent ()
14045 {
14046     /* Call your opponent's flag (claim a win on time) */
14047     if (appData.icsActive) {
14048         SendToICS(ics_prefix);
14049         SendToICS("flag\n");
14050     } else {
14051         switch (gameMode) {
14052           default:
14053             return;
14054           case MachinePlaysWhite:
14055             if (whiteFlag) {
14056                 if (blackFlag)
14057                   GameEnds(GameIsDrawn, "Both players ran out of time",
14058                            GE_PLAYER);
14059                 else
14060                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14061             } else {
14062                 DisplayError(_("Your opponent is not out of time"), 0);
14063             }
14064             break;
14065           case MachinePlaysBlack:
14066             if (blackFlag) {
14067                 if (whiteFlag)
14068                   GameEnds(GameIsDrawn, "Both players ran out of time",
14069                            GE_PLAYER);
14070                 else
14071                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14072             } else {
14073                 DisplayError(_("Your opponent is not out of time"), 0);
14074             }
14075             break;
14076         }
14077     }
14078 }
14079
14080 void
14081 ClockClick (int which)
14082 {       // [HGM] code moved to back-end from winboard.c
14083         if(which) { // black clock
14084           if (gameMode == EditPosition || gameMode == IcsExamining) {
14085             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14086             SetBlackToPlayEvent();
14087           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14088           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14089           } else if (shiftKey) {
14090             AdjustClock(which, -1);
14091           } else if (gameMode == IcsPlayingWhite ||
14092                      gameMode == MachinePlaysBlack) {
14093             CallFlagEvent();
14094           }
14095         } else { // white clock
14096           if (gameMode == EditPosition || gameMode == IcsExamining) {
14097             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14098             SetWhiteToPlayEvent();
14099           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14100           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14101           } else if (shiftKey) {
14102             AdjustClock(which, -1);
14103           } else if (gameMode == IcsPlayingBlack ||
14104                    gameMode == MachinePlaysWhite) {
14105             CallFlagEvent();
14106           }
14107         }
14108 }
14109
14110 void
14111 DrawEvent ()
14112 {
14113     /* Offer draw or accept pending draw offer from opponent */
14114
14115     if (appData.icsActive) {
14116         /* Note: tournament rules require draw offers to be
14117            made after you make your move but before you punch
14118            your clock.  Currently ICS doesn't let you do that;
14119            instead, you immediately punch your clock after making
14120            a move, but you can offer a draw at any time. */
14121
14122         SendToICS(ics_prefix);
14123         SendToICS("draw\n");
14124         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14125     } else if (cmailMsgLoaded) {
14126         if (currentMove == cmailOldMove &&
14127             commentList[cmailOldMove] != NULL &&
14128             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14129                    "Black offers a draw" : "White offers a draw")) {
14130             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14132         } else if (currentMove == cmailOldMove + 1) {
14133             char *offer = WhiteOnMove(cmailOldMove) ?
14134               "White offers a draw" : "Black offers a draw";
14135             AppendComment(currentMove, offer, TRUE);
14136             DisplayComment(currentMove - 1, offer);
14137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14138         } else {
14139             DisplayError(_("You must make your move before offering a draw"), 0);
14140             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14141         }
14142     } else if (first.offeredDraw) {
14143         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14144     } else {
14145         if (first.sendDrawOffers) {
14146             SendToProgram("draw\n", &first);
14147             userOfferedDraw = TRUE;
14148         }
14149     }
14150 }
14151
14152 void
14153 AdjournEvent ()
14154 {
14155     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14156
14157     if (appData.icsActive) {
14158         SendToICS(ics_prefix);
14159         SendToICS("adjourn\n");
14160     } else {
14161         /* Currently GNU Chess doesn't offer or accept Adjourns */
14162     }
14163 }
14164
14165
14166 void
14167 AbortEvent ()
14168 {
14169     /* Offer Abort or accept pending Abort offer from opponent */
14170
14171     if (appData.icsActive) {
14172         SendToICS(ics_prefix);
14173         SendToICS("abort\n");
14174     } else {
14175         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14176     }
14177 }
14178
14179 void
14180 ResignEvent ()
14181 {
14182     /* Resign.  You can do this even if it's not your turn. */
14183
14184     if (appData.icsActive) {
14185         SendToICS(ics_prefix);
14186         SendToICS("resign\n");
14187     } else {
14188         switch (gameMode) {
14189           case MachinePlaysWhite:
14190             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14191             break;
14192           case MachinePlaysBlack:
14193             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14194             break;
14195           case EditGame:
14196             if (cmailMsgLoaded) {
14197                 TruncateGame();
14198                 if (WhiteOnMove(cmailOldMove)) {
14199                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14200                 } else {
14201                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14202                 }
14203                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14204             }
14205             break;
14206           default:
14207             break;
14208         }
14209     }
14210 }
14211
14212
14213 void
14214 StopObservingEvent ()
14215 {
14216     /* Stop observing current games */
14217     SendToICS(ics_prefix);
14218     SendToICS("unobserve\n");
14219 }
14220
14221 void
14222 StopExaminingEvent ()
14223 {
14224     /* Stop observing current game */
14225     SendToICS(ics_prefix);
14226     SendToICS("unexamine\n");
14227 }
14228
14229 void
14230 ForwardInner (int target)
14231 {
14232     int limit; int oldSeekGraphUp = seekGraphUp;
14233
14234     if (appData.debugMode)
14235         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14236                 target, currentMove, forwardMostMove);
14237
14238     if (gameMode == EditPosition)
14239       return;
14240
14241     seekGraphUp = FALSE;
14242     MarkTargetSquares(1);
14243
14244     if (gameMode == PlayFromGameFile && !pausing)
14245       PauseEvent();
14246
14247     if (gameMode == IcsExamining && pausing)
14248       limit = pauseExamForwardMostMove;
14249     else
14250       limit = forwardMostMove;
14251
14252     if (target > limit) target = limit;
14253
14254     if (target > 0 && moveList[target - 1][0]) {
14255         int fromX, fromY, toX, toY;
14256         toX = moveList[target - 1][2] - AAA;
14257         toY = moveList[target - 1][3] - ONE;
14258         if (moveList[target - 1][1] == '@') {
14259             if (appData.highlightLastMove) {
14260                 SetHighlights(-1, -1, toX, toY);
14261             }
14262         } else {
14263             fromX = moveList[target - 1][0] - AAA;
14264             fromY = moveList[target - 1][1] - ONE;
14265             if (target == currentMove + 1) {
14266                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14267             }
14268             if (appData.highlightLastMove) {
14269                 SetHighlights(fromX, fromY, toX, toY);
14270             }
14271         }
14272     }
14273     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14274         gameMode == Training || gameMode == PlayFromGameFile ||
14275         gameMode == AnalyzeFile) {
14276         while (currentMove < target) {
14277             SendMoveToProgram(currentMove++, &first);
14278         }
14279     } else {
14280         currentMove = target;
14281     }
14282
14283     if (gameMode == EditGame || gameMode == EndOfGame) {
14284         whiteTimeRemaining = timeRemaining[0][currentMove];
14285         blackTimeRemaining = timeRemaining[1][currentMove];
14286     }
14287     DisplayBothClocks();
14288     DisplayMove(currentMove - 1);
14289     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14290     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14291     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14292         DisplayComment(currentMove - 1, commentList[currentMove]);
14293     }
14294 }
14295
14296
14297 void
14298 ForwardEvent ()
14299 {
14300     if (gameMode == IcsExamining && !pausing) {
14301         SendToICS(ics_prefix);
14302         SendToICS("forward\n");
14303     } else {
14304         ForwardInner(currentMove + 1);
14305     }
14306 }
14307
14308 void
14309 ToEndEvent ()
14310 {
14311     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14312         /* to optimze, we temporarily turn off analysis mode while we feed
14313          * the remaining moves to the engine. Otherwise we get analysis output
14314          * after each move.
14315          */
14316         if (first.analysisSupport) {
14317           SendToProgram("exit\nforce\n", &first);
14318           first.analyzing = FALSE;
14319         }
14320     }
14321
14322     if (gameMode == IcsExamining && !pausing) {
14323         SendToICS(ics_prefix);
14324         SendToICS("forward 999999\n");
14325     } else {
14326         ForwardInner(forwardMostMove);
14327     }
14328
14329     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14330         /* we have fed all the moves, so reactivate analysis mode */
14331         SendToProgram("analyze\n", &first);
14332         first.analyzing = TRUE;
14333         /*first.maybeThinking = TRUE;*/
14334         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14335     }
14336 }
14337
14338 void
14339 BackwardInner (int target)
14340 {
14341     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14342
14343     if (appData.debugMode)
14344         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14345                 target, currentMove, forwardMostMove);
14346
14347     if (gameMode == EditPosition) return;
14348     seekGraphUp = FALSE;
14349     MarkTargetSquares(1);
14350     if (currentMove <= backwardMostMove) {
14351         ClearHighlights();
14352         DrawPosition(full_redraw, boards[currentMove]);
14353         return;
14354     }
14355     if (gameMode == PlayFromGameFile && !pausing)
14356       PauseEvent();
14357
14358     if (moveList[target][0]) {
14359         int fromX, fromY, toX, toY;
14360         toX = moveList[target][2] - AAA;
14361         toY = moveList[target][3] - ONE;
14362         if (moveList[target][1] == '@') {
14363             if (appData.highlightLastMove) {
14364                 SetHighlights(-1, -1, toX, toY);
14365             }
14366         } else {
14367             fromX = moveList[target][0] - AAA;
14368             fromY = moveList[target][1] - ONE;
14369             if (target == currentMove - 1) {
14370                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14371             }
14372             if (appData.highlightLastMove) {
14373                 SetHighlights(fromX, fromY, toX, toY);
14374             }
14375         }
14376     }
14377     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14378         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14379         while (currentMove > target) {
14380             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14381                 // null move cannot be undone. Reload program with move history before it.
14382                 int i;
14383                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14384                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14385                 }
14386                 SendBoard(&first, i); 
14387                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14388                 break;
14389             }
14390             SendToProgram("undo\n", &first);
14391             currentMove--;
14392         }
14393     } else {
14394         currentMove = target;
14395     }
14396
14397     if (gameMode == EditGame || gameMode == EndOfGame) {
14398         whiteTimeRemaining = timeRemaining[0][currentMove];
14399         blackTimeRemaining = timeRemaining[1][currentMove];
14400     }
14401     DisplayBothClocks();
14402     DisplayMove(currentMove - 1);
14403     DrawPosition(full_redraw, boards[currentMove]);
14404     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14405     // [HGM] PV info: routine tests if comment empty
14406     DisplayComment(currentMove - 1, commentList[currentMove]);
14407 }
14408
14409 void
14410 BackwardEvent ()
14411 {
14412     if (gameMode == IcsExamining && !pausing) {
14413         SendToICS(ics_prefix);
14414         SendToICS("backward\n");
14415     } else {
14416         BackwardInner(currentMove - 1);
14417     }
14418 }
14419
14420 void
14421 ToStartEvent ()
14422 {
14423     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14424         /* to optimize, we temporarily turn off analysis mode while we undo
14425          * all the moves. Otherwise we get analysis output after each undo.
14426          */
14427         if (first.analysisSupport) {
14428           SendToProgram("exit\nforce\n", &first);
14429           first.analyzing = FALSE;
14430         }
14431     }
14432
14433     if (gameMode == IcsExamining && !pausing) {
14434         SendToICS(ics_prefix);
14435         SendToICS("backward 999999\n");
14436     } else {
14437         BackwardInner(backwardMostMove);
14438     }
14439
14440     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14441         /* we have fed all the moves, so reactivate analysis mode */
14442         SendToProgram("analyze\n", &first);
14443         first.analyzing = TRUE;
14444         /*first.maybeThinking = TRUE;*/
14445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14446     }
14447 }
14448
14449 void
14450 ToNrEvent (int to)
14451 {
14452   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14453   if (to >= forwardMostMove) to = forwardMostMove;
14454   if (to <= backwardMostMove) to = backwardMostMove;
14455   if (to < currentMove) {
14456     BackwardInner(to);
14457   } else {
14458     ForwardInner(to);
14459   }
14460 }
14461
14462 void
14463 RevertEvent (Boolean annotate)
14464 {
14465     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14466         return;
14467     }
14468     if (gameMode != IcsExamining) {
14469         DisplayError(_("You are not examining a game"), 0);
14470         return;
14471     }
14472     if (pausing) {
14473         DisplayError(_("You can't revert while pausing"), 0);
14474         return;
14475     }
14476     SendToICS(ics_prefix);
14477     SendToICS("revert\n");
14478 }
14479
14480 void
14481 RetractMoveEvent ()
14482 {
14483     switch (gameMode) {
14484       case MachinePlaysWhite:
14485       case MachinePlaysBlack:
14486         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14487             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14488             return;
14489         }
14490         if (forwardMostMove < 2) return;
14491         currentMove = forwardMostMove = forwardMostMove - 2;
14492         whiteTimeRemaining = timeRemaining[0][currentMove];
14493         blackTimeRemaining = timeRemaining[1][currentMove];
14494         DisplayBothClocks();
14495         DisplayMove(currentMove - 1);
14496         ClearHighlights();/*!! could figure this out*/
14497         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14498         SendToProgram("remove\n", &first);
14499         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14500         break;
14501
14502       case BeginningOfGame:
14503       default:
14504         break;
14505
14506       case IcsPlayingWhite:
14507       case IcsPlayingBlack:
14508         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14509             SendToICS(ics_prefix);
14510             SendToICS("takeback 2\n");
14511         } else {
14512             SendToICS(ics_prefix);
14513             SendToICS("takeback 1\n");
14514         }
14515         break;
14516     }
14517 }
14518
14519 void
14520 MoveNowEvent ()
14521 {
14522     ChessProgramState *cps;
14523
14524     switch (gameMode) {
14525       case MachinePlaysWhite:
14526         if (!WhiteOnMove(forwardMostMove)) {
14527             DisplayError(_("It is your turn"), 0);
14528             return;
14529         }
14530         cps = &first;
14531         break;
14532       case MachinePlaysBlack:
14533         if (WhiteOnMove(forwardMostMove)) {
14534             DisplayError(_("It is your turn"), 0);
14535             return;
14536         }
14537         cps = &first;
14538         break;
14539       case TwoMachinesPlay:
14540         if (WhiteOnMove(forwardMostMove) ==
14541             (first.twoMachinesColor[0] == 'w')) {
14542             cps = &first;
14543         } else {
14544             cps = &second;
14545         }
14546         break;
14547       case BeginningOfGame:
14548       default:
14549         return;
14550     }
14551     SendToProgram("?\n", cps);
14552 }
14553
14554 void
14555 TruncateGameEvent ()
14556 {
14557     EditGameEvent();
14558     if (gameMode != EditGame) return;
14559     TruncateGame();
14560 }
14561
14562 void
14563 TruncateGame ()
14564 {
14565     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14566     if (forwardMostMove > currentMove) {
14567         if (gameInfo.resultDetails != NULL) {
14568             free(gameInfo.resultDetails);
14569             gameInfo.resultDetails = NULL;
14570             gameInfo.result = GameUnfinished;
14571         }
14572         forwardMostMove = currentMove;
14573         HistorySet(parseList, backwardMostMove, forwardMostMove,
14574                    currentMove-1);
14575     }
14576 }
14577
14578 void
14579 HintEvent ()
14580 {
14581     if (appData.noChessProgram) return;
14582     switch (gameMode) {
14583       case MachinePlaysWhite:
14584         if (WhiteOnMove(forwardMostMove)) {
14585             DisplayError(_("Wait until your turn"), 0);
14586             return;
14587         }
14588         break;
14589       case BeginningOfGame:
14590       case MachinePlaysBlack:
14591         if (!WhiteOnMove(forwardMostMove)) {
14592             DisplayError(_("Wait until your turn"), 0);
14593             return;
14594         }
14595         break;
14596       default:
14597         DisplayError(_("No hint available"), 0);
14598         return;
14599     }
14600     SendToProgram("hint\n", &first);
14601     hintRequested = TRUE;
14602 }
14603
14604 void
14605 BookEvent ()
14606 {
14607     if (appData.noChessProgram) return;
14608     switch (gameMode) {
14609       case MachinePlaysWhite:
14610         if (WhiteOnMove(forwardMostMove)) {
14611             DisplayError(_("Wait until your turn"), 0);
14612             return;
14613         }
14614         break;
14615       case BeginningOfGame:
14616       case MachinePlaysBlack:
14617         if (!WhiteOnMove(forwardMostMove)) {
14618             DisplayError(_("Wait until your turn"), 0);
14619             return;
14620         }
14621         break;
14622       case EditPosition:
14623         EditPositionDone(TRUE);
14624         break;
14625       case TwoMachinesPlay:
14626         return;
14627       default:
14628         break;
14629     }
14630     SendToProgram("bk\n", &first);
14631     bookOutput[0] = NULLCHAR;
14632     bookRequested = TRUE;
14633 }
14634
14635 void
14636 AboutGameEvent ()
14637 {
14638     char *tags = PGNTags(&gameInfo);
14639     TagsPopUp(tags, CmailMsg());
14640     free(tags);
14641 }
14642
14643 /* end button procedures */
14644
14645 void
14646 PrintPosition (FILE *fp, int move)
14647 {
14648     int i, j;
14649
14650     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14651         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14652             char c = PieceToChar(boards[move][i][j]);
14653             fputc(c == 'x' ? '.' : c, fp);
14654             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14655         }
14656     }
14657     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14658       fprintf(fp, "white to play\n");
14659     else
14660       fprintf(fp, "black to play\n");
14661 }
14662
14663 void
14664 PrintOpponents (FILE *fp)
14665 {
14666     if (gameInfo.white != NULL) {
14667         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14668     } else {
14669         fprintf(fp, "\n");
14670     }
14671 }
14672
14673 /* Find last component of program's own name, using some heuristics */
14674 void
14675 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14676 {
14677     char *p, *q, c;
14678     int local = (strcmp(host, "localhost") == 0);
14679     while (!local && (p = strchr(prog, ';')) != NULL) {
14680         p++;
14681         while (*p == ' ') p++;
14682         prog = p;
14683     }
14684     if (*prog == '"' || *prog == '\'') {
14685         q = strchr(prog + 1, *prog);
14686     } else {
14687         q = strchr(prog, ' ');
14688     }
14689     if (q == NULL) q = prog + strlen(prog);
14690     p = q;
14691     while (p >= prog && *p != '/' && *p != '\\') p--;
14692     p++;
14693     if(p == prog && *p == '"') p++;
14694     c = *q; *q = 0;
14695     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14696     memcpy(buf, p, q - p);
14697     buf[q - p] = NULLCHAR;
14698     if (!local) {
14699         strcat(buf, "@");
14700         strcat(buf, host);
14701     }
14702 }
14703
14704 char *
14705 TimeControlTagValue ()
14706 {
14707     char buf[MSG_SIZ];
14708     if (!appData.clockMode) {
14709       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14710     } else if (movesPerSession > 0) {
14711       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14712     } else if (timeIncrement == 0) {
14713       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14714     } else {
14715       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14716     }
14717     return StrSave(buf);
14718 }
14719
14720 void
14721 SetGameInfo ()
14722 {
14723     /* This routine is used only for certain modes */
14724     VariantClass v = gameInfo.variant;
14725     ChessMove r = GameUnfinished;
14726     char *p = NULL;
14727
14728     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14729         r = gameInfo.result;
14730         p = gameInfo.resultDetails;
14731         gameInfo.resultDetails = NULL;
14732     }
14733     ClearGameInfo(&gameInfo);
14734     gameInfo.variant = v;
14735
14736     switch (gameMode) {
14737       case MachinePlaysWhite:
14738         gameInfo.event = StrSave( appData.pgnEventHeader );
14739         gameInfo.site = StrSave(HostName());
14740         gameInfo.date = PGNDate();
14741         gameInfo.round = StrSave("-");
14742         gameInfo.white = StrSave(first.tidy);
14743         gameInfo.black = StrSave(UserName());
14744         gameInfo.timeControl = TimeControlTagValue();
14745         break;
14746
14747       case MachinePlaysBlack:
14748         gameInfo.event = StrSave( appData.pgnEventHeader );
14749         gameInfo.site = StrSave(HostName());
14750         gameInfo.date = PGNDate();
14751         gameInfo.round = StrSave("-");
14752         gameInfo.white = StrSave(UserName());
14753         gameInfo.black = StrSave(first.tidy);
14754         gameInfo.timeControl = TimeControlTagValue();
14755         break;
14756
14757       case TwoMachinesPlay:
14758         gameInfo.event = StrSave( appData.pgnEventHeader );
14759         gameInfo.site = StrSave(HostName());
14760         gameInfo.date = PGNDate();
14761         if (roundNr > 0) {
14762             char buf[MSG_SIZ];
14763             snprintf(buf, MSG_SIZ, "%d", roundNr);
14764             gameInfo.round = StrSave(buf);
14765         } else {
14766             gameInfo.round = StrSave("-");
14767         }
14768         if (first.twoMachinesColor[0] == 'w') {
14769             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14770             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14771         } else {
14772             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14773             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14774         }
14775         gameInfo.timeControl = TimeControlTagValue();
14776         break;
14777
14778       case EditGame:
14779         gameInfo.event = StrSave("Edited game");
14780         gameInfo.site = StrSave(HostName());
14781         gameInfo.date = PGNDate();
14782         gameInfo.round = StrSave("-");
14783         gameInfo.white = StrSave("-");
14784         gameInfo.black = StrSave("-");
14785         gameInfo.result = r;
14786         gameInfo.resultDetails = p;
14787         break;
14788
14789       case EditPosition:
14790         gameInfo.event = StrSave("Edited position");
14791         gameInfo.site = StrSave(HostName());
14792         gameInfo.date = PGNDate();
14793         gameInfo.round = StrSave("-");
14794         gameInfo.white = StrSave("-");
14795         gameInfo.black = StrSave("-");
14796         break;
14797
14798       case IcsPlayingWhite:
14799       case IcsPlayingBlack:
14800       case IcsObserving:
14801       case IcsExamining:
14802         break;
14803
14804       case PlayFromGameFile:
14805         gameInfo.event = StrSave("Game from non-PGN file");
14806         gameInfo.site = StrSave(HostName());
14807         gameInfo.date = PGNDate();
14808         gameInfo.round = StrSave("-");
14809         gameInfo.white = StrSave("?");
14810         gameInfo.black = StrSave("?");
14811         break;
14812
14813       default:
14814         break;
14815     }
14816 }
14817
14818 void
14819 ReplaceComment (int index, char *text)
14820 {
14821     int len;
14822     char *p;
14823     float score;
14824
14825     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14826        pvInfoList[index-1].depth == len &&
14827        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14828        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14829     while (*text == '\n') text++;
14830     len = strlen(text);
14831     while (len > 0 && text[len - 1] == '\n') len--;
14832
14833     if (commentList[index] != NULL)
14834       free(commentList[index]);
14835
14836     if (len == 0) {
14837         commentList[index] = NULL;
14838         return;
14839     }
14840   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14841       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14842       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14843     commentList[index] = (char *) malloc(len + 2);
14844     strncpy(commentList[index], text, len);
14845     commentList[index][len] = '\n';
14846     commentList[index][len + 1] = NULLCHAR;
14847   } else {
14848     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14849     char *p;
14850     commentList[index] = (char *) malloc(len + 7);
14851     safeStrCpy(commentList[index], "{\n", 3);
14852     safeStrCpy(commentList[index]+2, text, len+1);
14853     commentList[index][len+2] = NULLCHAR;
14854     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14855     strcat(commentList[index], "\n}\n");
14856   }
14857 }
14858
14859 void
14860 CrushCRs (char *text)
14861 {
14862   char *p = text;
14863   char *q = text;
14864   char ch;
14865
14866   do {
14867     ch = *p++;
14868     if (ch == '\r') continue;
14869     *q++ = ch;
14870   } while (ch != '\0');
14871 }
14872
14873 void
14874 AppendComment (int index, char *text, Boolean addBraces)
14875 /* addBraces  tells if we should add {} */
14876 {
14877     int oldlen, len;
14878     char *old;
14879
14880 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14881     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14882
14883     CrushCRs(text);
14884     while (*text == '\n') text++;
14885     len = strlen(text);
14886     while (len > 0 && text[len - 1] == '\n') len--;
14887     text[len] = NULLCHAR;
14888
14889     if (len == 0) return;
14890
14891     if (commentList[index] != NULL) {
14892       Boolean addClosingBrace = addBraces;
14893         old = commentList[index];
14894         oldlen = strlen(old);
14895         while(commentList[index][oldlen-1] ==  '\n')
14896           commentList[index][--oldlen] = NULLCHAR;
14897         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14898         safeStrCpy(commentList[index], old, oldlen + len + 6);
14899         free(old);
14900         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14901         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14902           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14903           while (*text == '\n') { text++; len--; }
14904           commentList[index][--oldlen] = NULLCHAR;
14905       }
14906         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14907         else          strcat(commentList[index], "\n");
14908         strcat(commentList[index], text);
14909         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14910         else          strcat(commentList[index], "\n");
14911     } else {
14912         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14913         if(addBraces)
14914           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14915         else commentList[index][0] = NULLCHAR;
14916         strcat(commentList[index], text);
14917         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14918         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14919     }
14920 }
14921
14922 static char *
14923 FindStr (char * text, char * sub_text)
14924 {
14925     char * result = strstr( text, sub_text );
14926
14927     if( result != NULL ) {
14928         result += strlen( sub_text );
14929     }
14930
14931     return result;
14932 }
14933
14934 /* [AS] Try to extract PV info from PGN comment */
14935 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14936 char *
14937 GetInfoFromComment (int index, char * text)
14938 {
14939     char * sep = text, *p;
14940
14941     if( text != NULL && index > 0 ) {
14942         int score = 0;
14943         int depth = 0;
14944         int time = -1, sec = 0, deci;
14945         char * s_eval = FindStr( text, "[%eval " );
14946         char * s_emt = FindStr( text, "[%emt " );
14947
14948         if( s_eval != NULL || s_emt != NULL ) {
14949             /* New style */
14950             char delim;
14951
14952             if( s_eval != NULL ) {
14953                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14954                     return text;
14955                 }
14956
14957                 if( delim != ']' ) {
14958                     return text;
14959                 }
14960             }
14961
14962             if( s_emt != NULL ) {
14963             }
14964                 return text;
14965         }
14966         else {
14967             /* We expect something like: [+|-]nnn.nn/dd */
14968             int score_lo = 0;
14969
14970             if(*text != '{') return text; // [HGM] braces: must be normal comment
14971
14972             sep = strchr( text, '/' );
14973             if( sep == NULL || sep < (text+4) ) {
14974                 return text;
14975             }
14976
14977             p = text;
14978             if(p[1] == '(') { // comment starts with PV
14979                p = strchr(p, ')'); // locate end of PV
14980                if(p == NULL || sep < p+5) return text;
14981                // at this point we have something like "{(.*) +0.23/6 ..."
14982                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14983                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14984                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14985             }
14986             time = -1; sec = -1; deci = -1;
14987             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14988                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14989                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14990                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14991                 return text;
14992             }
14993
14994             if( score_lo < 0 || score_lo >= 100 ) {
14995                 return text;
14996             }
14997
14998             if(sec >= 0) time = 600*time + 10*sec; else
14999             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15000
15001             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15002
15003             /* [HGM] PV time: now locate end of PV info */
15004             while( *++sep >= '0' && *sep <= '9'); // strip depth
15005             if(time >= 0)
15006             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15007             if(sec >= 0)
15008             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15009             if(deci >= 0)
15010             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15011             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15012         }
15013
15014         if( depth <= 0 ) {
15015             return text;
15016         }
15017
15018         if( time < 0 ) {
15019             time = -1;
15020         }
15021
15022         pvInfoList[index-1].depth = depth;
15023         pvInfoList[index-1].score = score;
15024         pvInfoList[index-1].time  = 10*time; // centi-sec
15025         if(*sep == '}') *sep = 0; else *--sep = '{';
15026         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15027     }
15028     return sep;
15029 }
15030
15031 void
15032 SendToProgram (char *message, ChessProgramState *cps)
15033 {
15034     int count, outCount, error;
15035     char buf[MSG_SIZ];
15036
15037     if (cps->pr == NoProc) return;
15038     Attention(cps);
15039
15040     if (appData.debugMode) {
15041         TimeMark now;
15042         GetTimeMark(&now);
15043         fprintf(debugFP, "%ld >%-6s: %s",
15044                 SubtractTimeMarks(&now, &programStartTime),
15045                 cps->which, message);
15046         if(serverFP)
15047             fprintf(serverFP, "%ld >%-6s: %s",
15048                 SubtractTimeMarks(&now, &programStartTime),
15049                 cps->which, message), fflush(serverFP);
15050     }
15051
15052     count = strlen(message);
15053     outCount = OutputToProcess(cps->pr, message, count, &error);
15054     if (outCount < count && !exiting
15055                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15056       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15057       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15058         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15059             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15060                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15061                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15062                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15063             } else {
15064                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15065                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15066                 gameInfo.result = res;
15067             }
15068             gameInfo.resultDetails = StrSave(buf);
15069         }
15070         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15071         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15072     }
15073 }
15074
15075 void
15076 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15077 {
15078     char *end_str;
15079     char buf[MSG_SIZ];
15080     ChessProgramState *cps = (ChessProgramState *)closure;
15081
15082     if (isr != cps->isr) return; /* Killed intentionally */
15083     if (count <= 0) {
15084         if (count == 0) {
15085             RemoveInputSource(cps->isr);
15086             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15087             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15088                     _(cps->which), cps->program);
15089         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15090                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15091                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15092                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15093                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15094                 } else {
15095                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15096                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15097                     gameInfo.result = res;
15098                 }
15099                 gameInfo.resultDetails = StrSave(buf);
15100             }
15101             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15102             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15103         } else {
15104             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15105                     _(cps->which), cps->program);
15106             RemoveInputSource(cps->isr);
15107
15108             /* [AS] Program is misbehaving badly... kill it */
15109             if( count == -2 ) {
15110                 DestroyChildProcess( cps->pr, 9 );
15111                 cps->pr = NoProc;
15112             }
15113
15114             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15115         }
15116         return;
15117     }
15118
15119     if ((end_str = strchr(message, '\r')) != NULL)
15120       *end_str = NULLCHAR;
15121     if ((end_str = strchr(message, '\n')) != NULL)
15122       *end_str = NULLCHAR;
15123
15124     if (appData.debugMode) {
15125         TimeMark now; int print = 1;
15126         char *quote = ""; char c; int i;
15127
15128         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15129                 char start = message[0];
15130                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15131                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15132                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15133                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15134                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15135                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15136                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15137                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15138                    sscanf(message, "hint: %c", &c)!=1 && 
15139                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15140                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15141                     print = (appData.engineComments >= 2);
15142                 }
15143                 message[0] = start; // restore original message
15144         }
15145         if(print) {
15146                 GetTimeMark(&now);
15147                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15148                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15149                         quote,
15150                         message);
15151                 if(serverFP)
15152                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15153                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15154                         quote,
15155                         message), fflush(serverFP);
15156         }
15157     }
15158
15159     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15160     if (appData.icsEngineAnalyze) {
15161         if (strstr(message, "whisper") != NULL ||
15162              strstr(message, "kibitz") != NULL ||
15163             strstr(message, "tellics") != NULL) return;
15164     }
15165
15166     HandleMachineMove(message, cps);
15167 }
15168
15169
15170 void
15171 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15172 {
15173     char buf[MSG_SIZ];
15174     int seconds;
15175
15176     if( timeControl_2 > 0 ) {
15177         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15178             tc = timeControl_2;
15179         }
15180     }
15181     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15182     inc /= cps->timeOdds;
15183     st  /= cps->timeOdds;
15184
15185     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15186
15187     if (st > 0) {
15188       /* Set exact time per move, normally using st command */
15189       if (cps->stKludge) {
15190         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15191         seconds = st % 60;
15192         if (seconds == 0) {
15193           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15194         } else {
15195           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15196         }
15197       } else {
15198         snprintf(buf, MSG_SIZ, "st %d\n", st);
15199       }
15200     } else {
15201       /* Set conventional or incremental time control, using level command */
15202       if (seconds == 0) {
15203         /* Note old gnuchess bug -- minutes:seconds used to not work.
15204            Fixed in later versions, but still avoid :seconds
15205            when seconds is 0. */
15206         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15207       } else {
15208         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15209                  seconds, inc/1000.);
15210       }
15211     }
15212     SendToProgram(buf, cps);
15213
15214     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15215     /* Orthogonally, limit search to given depth */
15216     if (sd > 0) {
15217       if (cps->sdKludge) {
15218         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15219       } else {
15220         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15221       }
15222       SendToProgram(buf, cps);
15223     }
15224
15225     if(cps->nps >= 0) { /* [HGM] nps */
15226         if(cps->supportsNPS == FALSE)
15227           cps->nps = -1; // don't use if engine explicitly says not supported!
15228         else {
15229           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15230           SendToProgram(buf, cps);
15231         }
15232     }
15233 }
15234
15235 ChessProgramState *
15236 WhitePlayer ()
15237 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15238 {
15239     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15240        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15241         return &second;
15242     return &first;
15243 }
15244
15245 void
15246 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15247 {
15248     char message[MSG_SIZ];
15249     long time, otime;
15250
15251     /* Note: this routine must be called when the clocks are stopped
15252        or when they have *just* been set or switched; otherwise
15253        it will be off by the time since the current tick started.
15254     */
15255     if (machineWhite) {
15256         time = whiteTimeRemaining / 10;
15257         otime = blackTimeRemaining / 10;
15258     } else {
15259         time = blackTimeRemaining / 10;
15260         otime = whiteTimeRemaining / 10;
15261     }
15262     /* [HGM] translate opponent's time by time-odds factor */
15263     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15264
15265     if (time <= 0) time = 1;
15266     if (otime <= 0) otime = 1;
15267
15268     snprintf(message, MSG_SIZ, "time %ld\n", time);
15269     SendToProgram(message, cps);
15270
15271     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15272     SendToProgram(message, cps);
15273 }
15274
15275 int
15276 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15277 {
15278   char buf[MSG_SIZ];
15279   int len = strlen(name);
15280   int val;
15281
15282   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15283     (*p) += len + 1;
15284     sscanf(*p, "%d", &val);
15285     *loc = (val != 0);
15286     while (**p && **p != ' ')
15287       (*p)++;
15288     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15289     SendToProgram(buf, cps);
15290     return TRUE;
15291   }
15292   return FALSE;
15293 }
15294
15295 int
15296 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15297 {
15298   char buf[MSG_SIZ];
15299   int len = strlen(name);
15300   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15301     (*p) += len + 1;
15302     sscanf(*p, "%d", loc);
15303     while (**p && **p != ' ') (*p)++;
15304     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15305     SendToProgram(buf, cps);
15306     return TRUE;
15307   }
15308   return FALSE;
15309 }
15310
15311 int
15312 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15313 {
15314   char buf[MSG_SIZ];
15315   int len = strlen(name);
15316   if (strncmp((*p), name, len) == 0
15317       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15318     (*p) += len + 2;
15319     sscanf(*p, "%[^\"]", loc);
15320     while (**p && **p != '\"') (*p)++;
15321     if (**p == '\"') (*p)++;
15322     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15323     SendToProgram(buf, cps);
15324     return TRUE;
15325   }
15326   return FALSE;
15327 }
15328
15329 int
15330 ParseOption (Option *opt, ChessProgramState *cps)
15331 // [HGM] options: process the string that defines an engine option, and determine
15332 // name, type, default value, and allowed value range
15333 {
15334         char *p, *q, buf[MSG_SIZ];
15335         int n, min = (-1)<<31, max = 1<<31, def;
15336
15337         if(p = strstr(opt->name, " -spin ")) {
15338             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15339             if(max < min) max = min; // enforce consistency
15340             if(def < min) def = min;
15341             if(def > max) def = max;
15342             opt->value = def;
15343             opt->min = min;
15344             opt->max = max;
15345             opt->type = Spin;
15346         } else if((p = strstr(opt->name, " -slider "))) {
15347             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15348             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15349             if(max < min) max = min; // enforce consistency
15350             if(def < min) def = min;
15351             if(def > max) def = max;
15352             opt->value = def;
15353             opt->min = min;
15354             opt->max = max;
15355             opt->type = Spin; // Slider;
15356         } else if((p = strstr(opt->name, " -string "))) {
15357             opt->textValue = p+9;
15358             opt->type = TextBox;
15359         } else if((p = strstr(opt->name, " -file "))) {
15360             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15361             opt->textValue = p+7;
15362             opt->type = FileName; // FileName;
15363         } else if((p = strstr(opt->name, " -path "))) {
15364             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15365             opt->textValue = p+7;
15366             opt->type = PathName; // PathName;
15367         } else if(p = strstr(opt->name, " -check ")) {
15368             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15369             opt->value = (def != 0);
15370             opt->type = CheckBox;
15371         } else if(p = strstr(opt->name, " -combo ")) {
15372             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15373             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15374             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15375             opt->value = n = 0;
15376             while(q = StrStr(q, " /// ")) {
15377                 n++; *q = 0;    // count choices, and null-terminate each of them
15378                 q += 5;
15379                 if(*q == '*') { // remember default, which is marked with * prefix
15380                     q++;
15381                     opt->value = n;
15382                 }
15383                 cps->comboList[cps->comboCnt++] = q;
15384             }
15385             cps->comboList[cps->comboCnt++] = NULL;
15386             opt->max = n + 1;
15387             opt->type = ComboBox;
15388         } else if(p = strstr(opt->name, " -button")) {
15389             opt->type = Button;
15390         } else if(p = strstr(opt->name, " -save")) {
15391             opt->type = SaveButton;
15392         } else return FALSE;
15393         *p = 0; // terminate option name
15394         // now look if the command-line options define a setting for this engine option.
15395         if(cps->optionSettings && cps->optionSettings[0])
15396             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15397         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15398           snprintf(buf, MSG_SIZ, "option %s", p);
15399                 if(p = strstr(buf, ",")) *p = 0;
15400                 if(q = strchr(buf, '=')) switch(opt->type) {
15401                     case ComboBox:
15402                         for(n=0; n<opt->max; n++)
15403                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15404                         break;
15405                     case TextBox:
15406                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15407                         break;
15408                     case Spin:
15409                     case CheckBox:
15410                         opt->value = atoi(q+1);
15411                     default:
15412                         break;
15413                 }
15414                 strcat(buf, "\n");
15415                 SendToProgram(buf, cps);
15416         }
15417         return TRUE;
15418 }
15419
15420 void
15421 FeatureDone (ChessProgramState *cps, int val)
15422 {
15423   DelayedEventCallback cb = GetDelayedEvent();
15424   if ((cb == InitBackEnd3 && cps == &first) ||
15425       (cb == SettingsMenuIfReady && cps == &second) ||
15426       (cb == LoadEngine) ||
15427       (cb == TwoMachinesEventIfReady)) {
15428     CancelDelayedEvent();
15429     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15430   }
15431   cps->initDone = val;
15432 }
15433
15434 /* Parse feature command from engine */
15435 void
15436 ParseFeatures (char *args, ChessProgramState *cps)
15437 {
15438   char *p = args;
15439   char *q;
15440   int val;
15441   char buf[MSG_SIZ];
15442
15443   for (;;) {
15444     while (*p == ' ') p++;
15445     if (*p == NULLCHAR) return;
15446
15447     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15448     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15449     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15450     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15451     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15452     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15453     if (BoolFeature(&p, "reuse", &val, cps)) {
15454       /* Engine can disable reuse, but can't enable it if user said no */
15455       if (!val) cps->reuse = FALSE;
15456       continue;
15457     }
15458     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15459     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15460       if (gameMode == TwoMachinesPlay) {
15461         DisplayTwoMachinesTitle();
15462       } else {
15463         DisplayTitle("");
15464       }
15465       continue;
15466     }
15467     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15468     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15469     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15470     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15471     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15472     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15473     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15474     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15475     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15476     if (IntFeature(&p, "done", &val, cps)) {
15477       FeatureDone(cps, val);
15478       continue;
15479     }
15480     /* Added by Tord: */
15481     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15482     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15483     /* End of additions by Tord */
15484
15485     /* [HGM] added features: */
15486     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15487     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15488     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15489     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15490     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15491     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15492     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15493         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15494           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15495             SendToProgram(buf, cps);
15496             continue;
15497         }
15498         if(cps->nrOptions >= MAX_OPTIONS) {
15499             cps->nrOptions--;
15500             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15501             DisplayError(buf, 0);
15502         }
15503         continue;
15504     }
15505     /* End of additions by HGM */
15506
15507     /* unknown feature: complain and skip */
15508     q = p;
15509     while (*q && *q != '=') q++;
15510     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15511     SendToProgram(buf, cps);
15512     p = q;
15513     if (*p == '=') {
15514       p++;
15515       if (*p == '\"') {
15516         p++;
15517         while (*p && *p != '\"') p++;
15518         if (*p == '\"') p++;
15519       } else {
15520         while (*p && *p != ' ') p++;
15521       }
15522     }
15523   }
15524
15525 }
15526
15527 void
15528 PeriodicUpdatesEvent (int newState)
15529 {
15530     if (newState == appData.periodicUpdates)
15531       return;
15532
15533     appData.periodicUpdates=newState;
15534
15535     /* Display type changes, so update it now */
15536 //    DisplayAnalysis();
15537
15538     /* Get the ball rolling again... */
15539     if (newState) {
15540         AnalysisPeriodicEvent(1);
15541         StartAnalysisClock();
15542     }
15543 }
15544
15545 void
15546 PonderNextMoveEvent (int newState)
15547 {
15548     if (newState == appData.ponderNextMove) return;
15549     if (gameMode == EditPosition) EditPositionDone(TRUE);
15550     if (newState) {
15551         SendToProgram("hard\n", &first);
15552         if (gameMode == TwoMachinesPlay) {
15553             SendToProgram("hard\n", &second);
15554         }
15555     } else {
15556         SendToProgram("easy\n", &first);
15557         thinkOutput[0] = NULLCHAR;
15558         if (gameMode == TwoMachinesPlay) {
15559             SendToProgram("easy\n", &second);
15560         }
15561     }
15562     appData.ponderNextMove = newState;
15563 }
15564
15565 void
15566 NewSettingEvent (int option, int *feature, char *command, int value)
15567 {
15568     char buf[MSG_SIZ];
15569
15570     if (gameMode == EditPosition) EditPositionDone(TRUE);
15571     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15572     if(feature == NULL || *feature) SendToProgram(buf, &first);
15573     if (gameMode == TwoMachinesPlay) {
15574         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15575     }
15576 }
15577
15578 void
15579 ShowThinkingEvent ()
15580 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15581 {
15582     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15583     int newState = appData.showThinking
15584         // [HGM] thinking: other features now need thinking output as well
15585         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15586
15587     if (oldState == newState) return;
15588     oldState = newState;
15589     if (gameMode == EditPosition) EditPositionDone(TRUE);
15590     if (oldState) {
15591         SendToProgram("post\n", &first);
15592         if (gameMode == TwoMachinesPlay) {
15593             SendToProgram("post\n", &second);
15594         }
15595     } else {
15596         SendToProgram("nopost\n", &first);
15597         thinkOutput[0] = NULLCHAR;
15598         if (gameMode == TwoMachinesPlay) {
15599             SendToProgram("nopost\n", &second);
15600         }
15601     }
15602 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15603 }
15604
15605 void
15606 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15607 {
15608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15609   if (pr == NoProc) return;
15610   AskQuestion(title, question, replyPrefix, pr);
15611 }
15612
15613 void
15614 TypeInEvent (char firstChar)
15615 {
15616     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15617         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15618         gameMode == AnalyzeMode || gameMode == EditGame || 
15619         gameMode == EditPosition || gameMode == IcsExamining ||
15620         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15621         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15622                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15623                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15624         gameMode == Training) PopUpMoveDialog(firstChar);
15625 }
15626
15627 void
15628 TypeInDoneEvent (char *move)
15629 {
15630         Board board;
15631         int n, fromX, fromY, toX, toY;
15632         char promoChar;
15633         ChessMove moveType;
15634
15635         // [HGM] FENedit
15636         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15637                 EditPositionPasteFEN(move);
15638                 return;
15639         }
15640         // [HGM] movenum: allow move number to be typed in any mode
15641         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15642           ToNrEvent(2*n-1);
15643           return;
15644         }
15645         // undocumented kludge: allow command-line option to be typed in!
15646         // (potentially fatal, and does not implement the effect of the option.)
15647         // should only be used for options that are values on which future decisions will be made,
15648         // and definitely not on options that would be used during initialization.
15649         if(strstr(move, "!!! -") == move) {
15650             ParseArgsFromString(move+4);
15651             return;
15652         }
15653
15654       if (gameMode != EditGame && currentMove != forwardMostMove && 
15655         gameMode != Training) {
15656         DisplayMoveError(_("Displayed move is not current"));
15657       } else {
15658         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15659           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15660         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15661         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15662           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15663           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15664         } else {
15665           DisplayMoveError(_("Could not parse move"));
15666         }
15667       }
15668 }
15669
15670 void
15671 DisplayMove (int moveNumber)
15672 {
15673     char message[MSG_SIZ];
15674     char res[MSG_SIZ];
15675     char cpThinkOutput[MSG_SIZ];
15676
15677     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15678
15679     if (moveNumber == forwardMostMove - 1 ||
15680         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15681
15682         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15683
15684         if (strchr(cpThinkOutput, '\n')) {
15685             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15686         }
15687     } else {
15688         *cpThinkOutput = NULLCHAR;
15689     }
15690
15691     /* [AS] Hide thinking from human user */
15692     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15693         *cpThinkOutput = NULLCHAR;
15694         if( thinkOutput[0] != NULLCHAR ) {
15695             int i;
15696
15697             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15698                 cpThinkOutput[i] = '.';
15699             }
15700             cpThinkOutput[i] = NULLCHAR;
15701             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15702         }
15703     }
15704
15705     if (moveNumber == forwardMostMove - 1 &&
15706         gameInfo.resultDetails != NULL) {
15707         if (gameInfo.resultDetails[0] == NULLCHAR) {
15708           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15709         } else {
15710           snprintf(res, MSG_SIZ, " {%s} %s",
15711                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15712         }
15713     } else {
15714         res[0] = NULLCHAR;
15715     }
15716
15717     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15718         DisplayMessage(res, cpThinkOutput);
15719     } else {
15720       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15721                 WhiteOnMove(moveNumber) ? " " : ".. ",
15722                 parseList[moveNumber], res);
15723         DisplayMessage(message, cpThinkOutput);
15724     }
15725 }
15726
15727 void
15728 DisplayComment (int moveNumber, char *text)
15729 {
15730     char title[MSG_SIZ];
15731
15732     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15733       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15734     } else {
15735       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15736               WhiteOnMove(moveNumber) ? " " : ".. ",
15737               parseList[moveNumber]);
15738     }
15739     if (text != NULL && (appData.autoDisplayComment || commentUp))
15740         CommentPopUp(title, text);
15741 }
15742
15743 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15744  * might be busy thinking or pondering.  It can be omitted if your
15745  * gnuchess is configured to stop thinking immediately on any user
15746  * input.  However, that gnuchess feature depends on the FIONREAD
15747  * ioctl, which does not work properly on some flavors of Unix.
15748  */
15749 void
15750 Attention (ChessProgramState *cps)
15751 {
15752 #if ATTENTION
15753     if (!cps->useSigint) return;
15754     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15755     switch (gameMode) {
15756       case MachinePlaysWhite:
15757       case MachinePlaysBlack:
15758       case TwoMachinesPlay:
15759       case IcsPlayingWhite:
15760       case IcsPlayingBlack:
15761       case AnalyzeMode:
15762       case AnalyzeFile:
15763         /* Skip if we know it isn't thinking */
15764         if (!cps->maybeThinking) return;
15765         if (appData.debugMode)
15766           fprintf(debugFP, "Interrupting %s\n", cps->which);
15767         InterruptChildProcess(cps->pr);
15768         cps->maybeThinking = FALSE;
15769         break;
15770       default:
15771         break;
15772     }
15773 #endif /*ATTENTION*/
15774 }
15775
15776 int
15777 CheckFlags ()
15778 {
15779     if (whiteTimeRemaining <= 0) {
15780         if (!whiteFlag) {
15781             whiteFlag = TRUE;
15782             if (appData.icsActive) {
15783                 if (appData.autoCallFlag &&
15784                     gameMode == IcsPlayingBlack && !blackFlag) {
15785                   SendToICS(ics_prefix);
15786                   SendToICS("flag\n");
15787                 }
15788             } else {
15789                 if (blackFlag) {
15790                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15791                 } else {
15792                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15793                     if (appData.autoCallFlag) {
15794                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15795                         return TRUE;
15796                     }
15797                 }
15798             }
15799         }
15800     }
15801     if (blackTimeRemaining <= 0) {
15802         if (!blackFlag) {
15803             blackFlag = TRUE;
15804             if (appData.icsActive) {
15805                 if (appData.autoCallFlag &&
15806                     gameMode == IcsPlayingWhite && !whiteFlag) {
15807                   SendToICS(ics_prefix);
15808                   SendToICS("flag\n");
15809                 }
15810             } else {
15811                 if (whiteFlag) {
15812                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15813                 } else {
15814                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15815                     if (appData.autoCallFlag) {
15816                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15817                         return TRUE;
15818                     }
15819                 }
15820             }
15821         }
15822     }
15823     return FALSE;
15824 }
15825
15826 void
15827 CheckTimeControl ()
15828 {
15829     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15830         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15831
15832     /*
15833      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15834      */
15835     if ( !WhiteOnMove(forwardMostMove) ) {
15836         /* White made time control */
15837         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15838         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15839         /* [HGM] time odds: correct new time quota for time odds! */
15840                                             / WhitePlayer()->timeOdds;
15841         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15842     } else {
15843         lastBlack -= blackTimeRemaining;
15844         /* Black made time control */
15845         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15846                                             / WhitePlayer()->other->timeOdds;
15847         lastWhite = whiteTimeRemaining;
15848     }
15849 }
15850
15851 void
15852 DisplayBothClocks ()
15853 {
15854     int wom = gameMode == EditPosition ?
15855       !blackPlaysFirst : WhiteOnMove(currentMove);
15856     DisplayWhiteClock(whiteTimeRemaining, wom);
15857     DisplayBlackClock(blackTimeRemaining, !wom);
15858 }
15859
15860
15861 /* Timekeeping seems to be a portability nightmare.  I think everyone
15862    has ftime(), but I'm really not sure, so I'm including some ifdefs
15863    to use other calls if you don't.  Clocks will be less accurate if
15864    you have neither ftime nor gettimeofday.
15865 */
15866
15867 /* VS 2008 requires the #include outside of the function */
15868 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15869 #include <sys/timeb.h>
15870 #endif
15871
15872 /* Get the current time as a TimeMark */
15873 void
15874 GetTimeMark (TimeMark *tm)
15875 {
15876 #if HAVE_GETTIMEOFDAY
15877
15878     struct timeval timeVal;
15879     struct timezone timeZone;
15880
15881     gettimeofday(&timeVal, &timeZone);
15882     tm->sec = (long) timeVal.tv_sec;
15883     tm->ms = (int) (timeVal.tv_usec / 1000L);
15884
15885 #else /*!HAVE_GETTIMEOFDAY*/
15886 #if HAVE_FTIME
15887
15888 // include <sys/timeb.h> / moved to just above start of function
15889     struct timeb timeB;
15890
15891     ftime(&timeB);
15892     tm->sec = (long) timeB.time;
15893     tm->ms = (int) timeB.millitm;
15894
15895 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15896     tm->sec = (long) time(NULL);
15897     tm->ms = 0;
15898 #endif
15899 #endif
15900 }
15901
15902 /* Return the difference in milliseconds between two
15903    time marks.  We assume the difference will fit in a long!
15904 */
15905 long
15906 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15907 {
15908     return 1000L*(tm2->sec - tm1->sec) +
15909            (long) (tm2->ms - tm1->ms);
15910 }
15911
15912
15913 /*
15914  * Code to manage the game clocks.
15915  *
15916  * In tournament play, black starts the clock and then white makes a move.
15917  * We give the human user a slight advantage if he is playing white---the
15918  * clocks don't run until he makes his first move, so it takes zero time.
15919  * Also, we don't account for network lag, so we could get out of sync
15920  * with GNU Chess's clock -- but then, referees are always right.
15921  */
15922
15923 static TimeMark tickStartTM;
15924 static long intendedTickLength;
15925
15926 long
15927 NextTickLength (long timeRemaining)
15928 {
15929     long nominalTickLength, nextTickLength;
15930
15931     if (timeRemaining > 0L && timeRemaining <= 10000L)
15932       nominalTickLength = 100L;
15933     else
15934       nominalTickLength = 1000L;
15935     nextTickLength = timeRemaining % nominalTickLength;
15936     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15937
15938     return nextTickLength;
15939 }
15940
15941 /* Adjust clock one minute up or down */
15942 void
15943 AdjustClock (Boolean which, int dir)
15944 {
15945     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15946     if(which) blackTimeRemaining += 60000*dir;
15947     else      whiteTimeRemaining += 60000*dir;
15948     DisplayBothClocks();
15949     adjustedClock = TRUE;
15950 }
15951
15952 /* Stop clocks and reset to a fresh time control */
15953 void
15954 ResetClocks ()
15955 {
15956     (void) StopClockTimer();
15957     if (appData.icsActive) {
15958         whiteTimeRemaining = blackTimeRemaining = 0;
15959     } else if (searchTime) {
15960         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15961         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15962     } else { /* [HGM] correct new time quote for time odds */
15963         whiteTC = blackTC = fullTimeControlString;
15964         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15965         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15966     }
15967     if (whiteFlag || blackFlag) {
15968         DisplayTitle("");
15969         whiteFlag = blackFlag = FALSE;
15970     }
15971     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15972     DisplayBothClocks();
15973     adjustedClock = FALSE;
15974 }
15975
15976 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15977
15978 /* Decrement running clock by amount of time that has passed */
15979 void
15980 DecrementClocks ()
15981 {
15982     long timeRemaining;
15983     long lastTickLength, fudge;
15984     TimeMark now;
15985
15986     if (!appData.clockMode) return;
15987     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15988
15989     GetTimeMark(&now);
15990
15991     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15992
15993     /* Fudge if we woke up a little too soon */
15994     fudge = intendedTickLength - lastTickLength;
15995     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15996
15997     if (WhiteOnMove(forwardMostMove)) {
15998         if(whiteNPS >= 0) lastTickLength = 0;
15999         timeRemaining = whiteTimeRemaining -= lastTickLength;
16000         if(timeRemaining < 0 && !appData.icsActive) {
16001             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16002             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16003                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16004                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16005             }
16006         }
16007         DisplayWhiteClock(whiteTimeRemaining - fudge,
16008                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16009     } else {
16010         if(blackNPS >= 0) lastTickLength = 0;
16011         timeRemaining = blackTimeRemaining -= lastTickLength;
16012         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16013             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16014             if(suddenDeath) {
16015                 blackStartMove = forwardMostMove;
16016                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16017             }
16018         }
16019         DisplayBlackClock(blackTimeRemaining - fudge,
16020                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16021     }
16022     if (CheckFlags()) return;
16023
16024     tickStartTM = now;
16025     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16026     StartClockTimer(intendedTickLength);
16027
16028     /* if the time remaining has fallen below the alarm threshold, sound the
16029      * alarm. if the alarm has sounded and (due to a takeback or time control
16030      * with increment) the time remaining has increased to a level above the
16031      * threshold, reset the alarm so it can sound again.
16032      */
16033
16034     if (appData.icsActive && appData.icsAlarm) {
16035
16036         /* make sure we are dealing with the user's clock */
16037         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16038                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16039            )) return;
16040
16041         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16042             alarmSounded = FALSE;
16043         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16044             PlayAlarmSound();
16045             alarmSounded = TRUE;
16046         }
16047     }
16048 }
16049
16050
16051 /* A player has just moved, so stop the previously running
16052    clock and (if in clock mode) start the other one.
16053    We redisplay both clocks in case we're in ICS mode, because
16054    ICS gives us an update to both clocks after every move.
16055    Note that this routine is called *after* forwardMostMove
16056    is updated, so the last fractional tick must be subtracted
16057    from the color that is *not* on move now.
16058 */
16059 void
16060 SwitchClocks (int newMoveNr)
16061 {
16062     long lastTickLength;
16063     TimeMark now;
16064     int flagged = FALSE;
16065
16066     GetTimeMark(&now);
16067
16068     if (StopClockTimer() && appData.clockMode) {
16069         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16070         if (!WhiteOnMove(forwardMostMove)) {
16071             if(blackNPS >= 0) lastTickLength = 0;
16072             blackTimeRemaining -= lastTickLength;
16073            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16074 //         if(pvInfoList[forwardMostMove].time == -1)
16075                  pvInfoList[forwardMostMove].time =               // use GUI time
16076                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16077         } else {
16078            if(whiteNPS >= 0) lastTickLength = 0;
16079            whiteTimeRemaining -= lastTickLength;
16080            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16081 //         if(pvInfoList[forwardMostMove].time == -1)
16082                  pvInfoList[forwardMostMove].time =
16083                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16084         }
16085         flagged = CheckFlags();
16086     }
16087     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16088     CheckTimeControl();
16089
16090     if (flagged || !appData.clockMode) return;
16091
16092     switch (gameMode) {
16093       case MachinePlaysBlack:
16094       case MachinePlaysWhite:
16095       case BeginningOfGame:
16096         if (pausing) return;
16097         break;
16098
16099       case EditGame:
16100       case PlayFromGameFile:
16101       case IcsExamining:
16102         return;
16103
16104       default:
16105         break;
16106     }
16107
16108     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16109         if(WhiteOnMove(forwardMostMove))
16110              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16111         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16112     }
16113
16114     tickStartTM = now;
16115     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16116       whiteTimeRemaining : blackTimeRemaining);
16117     StartClockTimer(intendedTickLength);
16118 }
16119
16120
16121 /* Stop both clocks */
16122 void
16123 StopClocks ()
16124 {
16125     long lastTickLength;
16126     TimeMark now;
16127
16128     if (!StopClockTimer()) return;
16129     if (!appData.clockMode) return;
16130
16131     GetTimeMark(&now);
16132
16133     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16134     if (WhiteOnMove(forwardMostMove)) {
16135         if(whiteNPS >= 0) lastTickLength = 0;
16136         whiteTimeRemaining -= lastTickLength;
16137         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16138     } else {
16139         if(blackNPS >= 0) lastTickLength = 0;
16140         blackTimeRemaining -= lastTickLength;
16141         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16142     }
16143     CheckFlags();
16144 }
16145
16146 /* Start clock of player on move.  Time may have been reset, so
16147    if clock is already running, stop and restart it. */
16148 void
16149 StartClocks ()
16150 {
16151     (void) StopClockTimer(); /* in case it was running already */
16152     DisplayBothClocks();
16153     if (CheckFlags()) return;
16154
16155     if (!appData.clockMode) return;
16156     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16157
16158     GetTimeMark(&tickStartTM);
16159     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16160       whiteTimeRemaining : blackTimeRemaining);
16161
16162    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16163     whiteNPS = blackNPS = -1;
16164     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16165        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16166         whiteNPS = first.nps;
16167     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16168        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16169         blackNPS = first.nps;
16170     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16171         whiteNPS = second.nps;
16172     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16173         blackNPS = second.nps;
16174     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16175
16176     StartClockTimer(intendedTickLength);
16177 }
16178
16179 char *
16180 TimeString (long ms)
16181 {
16182     long second, minute, hour, day;
16183     char *sign = "";
16184     static char buf[32];
16185
16186     if (ms > 0 && ms <= 9900) {
16187       /* convert milliseconds to tenths, rounding up */
16188       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16189
16190       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16191       return buf;
16192     }
16193
16194     /* convert milliseconds to seconds, rounding up */
16195     /* use floating point to avoid strangeness of integer division
16196        with negative dividends on many machines */
16197     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16198
16199     if (second < 0) {
16200         sign = "-";
16201         second = -second;
16202     }
16203
16204     day = second / (60 * 60 * 24);
16205     second = second % (60 * 60 * 24);
16206     hour = second / (60 * 60);
16207     second = second % (60 * 60);
16208     minute = second / 60;
16209     second = second % 60;
16210
16211     if (day > 0)
16212       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16213               sign, day, hour, minute, second);
16214     else if (hour > 0)
16215       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16216     else
16217       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16218
16219     return buf;
16220 }
16221
16222
16223 /*
16224  * This is necessary because some C libraries aren't ANSI C compliant yet.
16225  */
16226 char *
16227 StrStr (char *string, char *match)
16228 {
16229     int i, length;
16230
16231     length = strlen(match);
16232
16233     for (i = strlen(string) - length; i >= 0; i--, string++)
16234       if (!strncmp(match, string, length))
16235         return string;
16236
16237     return NULL;
16238 }
16239
16240 char *
16241 StrCaseStr (char *string, char *match)
16242 {
16243     int i, j, length;
16244
16245     length = strlen(match);
16246
16247     for (i = strlen(string) - length; i >= 0; i--, string++) {
16248         for (j = 0; j < length; j++) {
16249             if (ToLower(match[j]) != ToLower(string[j]))
16250               break;
16251         }
16252         if (j == length) return string;
16253     }
16254
16255     return NULL;
16256 }
16257
16258 #ifndef _amigados
16259 int
16260 StrCaseCmp (char *s1, char *s2)
16261 {
16262     char c1, c2;
16263
16264     for (;;) {
16265         c1 = ToLower(*s1++);
16266         c2 = ToLower(*s2++);
16267         if (c1 > c2) return 1;
16268         if (c1 < c2) return -1;
16269         if (c1 == NULLCHAR) return 0;
16270     }
16271 }
16272
16273
16274 int
16275 ToLower (int c)
16276 {
16277     return isupper(c) ? tolower(c) : c;
16278 }
16279
16280
16281 int
16282 ToUpper (int c)
16283 {
16284     return islower(c) ? toupper(c) : c;
16285 }
16286 #endif /* !_amigados    */
16287
16288 char *
16289 StrSave (char *s)
16290 {
16291   char *ret;
16292
16293   if ((ret = (char *) malloc(strlen(s) + 1)))
16294     {
16295       safeStrCpy(ret, s, strlen(s)+1);
16296     }
16297   return ret;
16298 }
16299
16300 char *
16301 StrSavePtr (char *s, char **savePtr)
16302 {
16303     if (*savePtr) {
16304         free(*savePtr);
16305     }
16306     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16307       safeStrCpy(*savePtr, s, strlen(s)+1);
16308     }
16309     return(*savePtr);
16310 }
16311
16312 char *
16313 PGNDate ()
16314 {
16315     time_t clock;
16316     struct tm *tm;
16317     char buf[MSG_SIZ];
16318
16319     clock = time((time_t *)NULL);
16320     tm = localtime(&clock);
16321     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16322             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16323     return StrSave(buf);
16324 }
16325
16326
16327 char *
16328 PositionToFEN (int move, char *overrideCastling)
16329 {
16330     int i, j, fromX, fromY, toX, toY;
16331     int whiteToPlay;
16332     char buf[MSG_SIZ];
16333     char *p, *q;
16334     int emptycount;
16335     ChessSquare piece;
16336
16337     whiteToPlay = (gameMode == EditPosition) ?
16338       !blackPlaysFirst : (move % 2 == 0);
16339     p = buf;
16340
16341     /* Piece placement data */
16342     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16343         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16344         emptycount = 0;
16345         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16346             if (boards[move][i][j] == EmptySquare) {
16347                 emptycount++;
16348             } else { ChessSquare piece = boards[move][i][j];
16349                 if (emptycount > 0) {
16350                     if(emptycount<10) /* [HGM] can be >= 10 */
16351                         *p++ = '0' + emptycount;
16352                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16353                     emptycount = 0;
16354                 }
16355                 if(PieceToChar(piece) == '+') {
16356                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16357                     *p++ = '+';
16358                     piece = (ChessSquare)(DEMOTED piece);
16359                 }
16360                 *p++ = PieceToChar(piece);
16361                 if(p[-1] == '~') {
16362                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16363                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16364                     *p++ = '~';
16365                 }
16366             }
16367         }
16368         if (emptycount > 0) {
16369             if(emptycount<10) /* [HGM] can be >= 10 */
16370                 *p++ = '0' + emptycount;
16371             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16372             emptycount = 0;
16373         }
16374         *p++ = '/';
16375     }
16376     *(p - 1) = ' ';
16377
16378     /* [HGM] print Crazyhouse or Shogi holdings */
16379     if( gameInfo.holdingsWidth ) {
16380         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16381         q = p;
16382         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16383             piece = boards[move][i][BOARD_WIDTH-1];
16384             if( piece != EmptySquare )
16385               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16386                   *p++ = PieceToChar(piece);
16387         }
16388         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16389             piece = boards[move][BOARD_HEIGHT-i-1][0];
16390             if( piece != EmptySquare )
16391               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16392                   *p++ = PieceToChar(piece);
16393         }
16394
16395         if( q == p ) *p++ = '-';
16396         *p++ = ']';
16397         *p++ = ' ';
16398     }
16399
16400     /* Active color */
16401     *p++ = whiteToPlay ? 'w' : 'b';
16402     *p++ = ' ';
16403
16404   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16405     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16406   } else {
16407   if(nrCastlingRights) {
16408      q = p;
16409      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16410        /* [HGM] write directly from rights */
16411            if(boards[move][CASTLING][2] != NoRights &&
16412               boards[move][CASTLING][0] != NoRights   )
16413                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16414            if(boards[move][CASTLING][2] != NoRights &&
16415               boards[move][CASTLING][1] != NoRights   )
16416                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16417            if(boards[move][CASTLING][5] != NoRights &&
16418               boards[move][CASTLING][3] != NoRights   )
16419                 *p++ = boards[move][CASTLING][3] + AAA;
16420            if(boards[move][CASTLING][5] != NoRights &&
16421               boards[move][CASTLING][4] != NoRights   )
16422                 *p++ = boards[move][CASTLING][4] + AAA;
16423      } else {
16424
16425         /* [HGM] write true castling rights */
16426         if( nrCastlingRights == 6 ) {
16427             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16428                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16429             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16430                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16431             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16432                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16433             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16434                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16435         }
16436      }
16437      if (q == p) *p++ = '-'; /* No castling rights */
16438      *p++ = ' ';
16439   }
16440
16441   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16442      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16443     /* En passant target square */
16444     if (move > backwardMostMove) {
16445         fromX = moveList[move - 1][0] - AAA;
16446         fromY = moveList[move - 1][1] - ONE;
16447         toX = moveList[move - 1][2] - AAA;
16448         toY = moveList[move - 1][3] - ONE;
16449         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16450             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16451             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16452             fromX == toX) {
16453             /* 2-square pawn move just happened */
16454             *p++ = toX + AAA;
16455             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16456         } else {
16457             *p++ = '-';
16458         }
16459     } else if(move == backwardMostMove) {
16460         // [HGM] perhaps we should always do it like this, and forget the above?
16461         if((signed char)boards[move][EP_STATUS] >= 0) {
16462             *p++ = boards[move][EP_STATUS] + AAA;
16463             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16464         } else {
16465             *p++ = '-';
16466         }
16467     } else {
16468         *p++ = '-';
16469     }
16470     *p++ = ' ';
16471   }
16472   }
16473
16474     /* [HGM] find reversible plies */
16475     {   int i = 0, j=move;
16476
16477         if (appData.debugMode) { int k;
16478             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16479             for(k=backwardMostMove; k<=forwardMostMove; k++)
16480                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16481
16482         }
16483
16484         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16485         if( j == backwardMostMove ) i += initialRulePlies;
16486         sprintf(p, "%d ", i);
16487         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16488     }
16489     /* Fullmove number */
16490     sprintf(p, "%d", (move / 2) + 1);
16491
16492     return StrSave(buf);
16493 }
16494
16495 Boolean
16496 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16497 {
16498     int i, j;
16499     char *p, c;
16500     int emptycount;
16501     ChessSquare piece;
16502
16503     p = fen;
16504
16505     /* [HGM] by default clear Crazyhouse holdings, if present */
16506     if(gameInfo.holdingsWidth) {
16507        for(i=0; i<BOARD_HEIGHT; i++) {
16508            board[i][0]             = EmptySquare; /* black holdings */
16509            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16510            board[i][1]             = (ChessSquare) 0; /* black counts */
16511            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16512        }
16513     }
16514
16515     /* Piece placement data */
16516     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16517         j = 0;
16518         for (;;) {
16519             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16520                 if (*p == '/') p++;
16521                 emptycount = gameInfo.boardWidth - j;
16522                 while (emptycount--)
16523                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16524                 break;
16525 #if(BOARD_FILES >= 10)
16526             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16527                 p++; emptycount=10;
16528                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16529                 while (emptycount--)
16530                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16531 #endif
16532             } else if (isdigit(*p)) {
16533                 emptycount = *p++ - '0';
16534                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16535                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16536                 while (emptycount--)
16537                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16538             } else if (*p == '+' || isalpha(*p)) {
16539                 if (j >= gameInfo.boardWidth) return FALSE;
16540                 if(*p=='+') {
16541                     piece = CharToPiece(*++p);
16542                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16543                     piece = (ChessSquare) (PROMOTED piece ); p++;
16544                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16545                 } else piece = CharToPiece(*p++);
16546
16547                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16548                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16549                     piece = (ChessSquare) (PROMOTED piece);
16550                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16551                     p++;
16552                 }
16553                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16554             } else {
16555                 return FALSE;
16556             }
16557         }
16558     }
16559     while (*p == '/' || *p == ' ') p++;
16560
16561     /* [HGM] look for Crazyhouse holdings here */
16562     while(*p==' ') p++;
16563     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16564         if(*p == '[') p++;
16565         if(*p == '-' ) p++; /* empty holdings */ else {
16566             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16567             /* if we would allow FEN reading to set board size, we would   */
16568             /* have to add holdings and shift the board read so far here   */
16569             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16570                 p++;
16571                 if((int) piece >= (int) BlackPawn ) {
16572                     i = (int)piece - (int)BlackPawn;
16573                     i = PieceToNumber((ChessSquare)i);
16574                     if( i >= gameInfo.holdingsSize ) return FALSE;
16575                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16576                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16577                 } else {
16578                     i = (int)piece - (int)WhitePawn;
16579                     i = PieceToNumber((ChessSquare)i);
16580                     if( i >= gameInfo.holdingsSize ) return FALSE;
16581                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16582                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16583                 }
16584             }
16585         }
16586         if(*p == ']') p++;
16587     }
16588
16589     while(*p == ' ') p++;
16590
16591     /* Active color */
16592     c = *p++;
16593     if(appData.colorNickNames) {
16594       if( c == appData.colorNickNames[0] ) c = 'w'; else
16595       if( c == appData.colorNickNames[1] ) c = 'b';
16596     }
16597     switch (c) {
16598       case 'w':
16599         *blackPlaysFirst = FALSE;
16600         break;
16601       case 'b':
16602         *blackPlaysFirst = TRUE;
16603         break;
16604       default:
16605         return FALSE;
16606     }
16607
16608     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16609     /* return the extra info in global variiables             */
16610
16611     /* set defaults in case FEN is incomplete */
16612     board[EP_STATUS] = EP_UNKNOWN;
16613     for(i=0; i<nrCastlingRights; i++ ) {
16614         board[CASTLING][i] =
16615             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16616     }   /* assume possible unless obviously impossible */
16617     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16618     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16619     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16620                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16621     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16622     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16623     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16624                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16625     FENrulePlies = 0;
16626
16627     while(*p==' ') p++;
16628     if(nrCastlingRights) {
16629       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16630           /* castling indicator present, so default becomes no castlings */
16631           for(i=0; i<nrCastlingRights; i++ ) {
16632                  board[CASTLING][i] = NoRights;
16633           }
16634       }
16635       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16636              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16637              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16638              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16639         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16640
16641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16642             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16643             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16644         }
16645         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16646             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16647         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16648                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16649         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16650                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16651         switch(c) {
16652           case'K':
16653               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16654               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16655               board[CASTLING][2] = whiteKingFile;
16656               break;
16657           case'Q':
16658               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16659               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16660               board[CASTLING][2] = whiteKingFile;
16661               break;
16662           case'k':
16663               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16664               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16665               board[CASTLING][5] = blackKingFile;
16666               break;
16667           case'q':
16668               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16669               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16670               board[CASTLING][5] = blackKingFile;
16671           case '-':
16672               break;
16673           default: /* FRC castlings */
16674               if(c >= 'a') { /* black rights */
16675                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16676                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16677                   if(i == BOARD_RGHT) break;
16678                   board[CASTLING][5] = i;
16679                   c -= AAA;
16680                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16681                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16682                   if(c > i)
16683                       board[CASTLING][3] = c;
16684                   else
16685                       board[CASTLING][4] = c;
16686               } else { /* white rights */
16687                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16688                     if(board[0][i] == WhiteKing) break;
16689                   if(i == BOARD_RGHT) break;
16690                   board[CASTLING][2] = i;
16691                   c -= AAA - 'a' + 'A';
16692                   if(board[0][c] >= WhiteKing) break;
16693                   if(c > i)
16694                       board[CASTLING][0] = c;
16695                   else
16696                       board[CASTLING][1] = c;
16697               }
16698         }
16699       }
16700       for(i=0; i<nrCastlingRights; i++)
16701         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16702     if (appData.debugMode) {
16703         fprintf(debugFP, "FEN castling rights:");
16704         for(i=0; i<nrCastlingRights; i++)
16705         fprintf(debugFP, " %d", board[CASTLING][i]);
16706         fprintf(debugFP, "\n");
16707     }
16708
16709       while(*p==' ') p++;
16710     }
16711
16712     /* read e.p. field in games that know e.p. capture */
16713     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16714        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16715       if(*p=='-') {
16716         p++; board[EP_STATUS] = EP_NONE;
16717       } else {
16718          char c = *p++ - AAA;
16719
16720          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16721          if(*p >= '0' && *p <='9') p++;
16722          board[EP_STATUS] = c;
16723       }
16724     }
16725
16726
16727     if(sscanf(p, "%d", &i) == 1) {
16728         FENrulePlies = i; /* 50-move ply counter */
16729         /* (The move number is still ignored)    */
16730     }
16731
16732     return TRUE;
16733 }
16734
16735 void
16736 EditPositionPasteFEN (char *fen)
16737 {
16738   if (fen != NULL) {
16739     Board initial_position;
16740
16741     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16742       DisplayError(_("Bad FEN position in clipboard"), 0);
16743       return ;
16744     } else {
16745       int savedBlackPlaysFirst = blackPlaysFirst;
16746       EditPositionEvent();
16747       blackPlaysFirst = savedBlackPlaysFirst;
16748       CopyBoard(boards[0], initial_position);
16749       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16750       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16751       DisplayBothClocks();
16752       DrawPosition(FALSE, boards[currentMove]);
16753     }
16754   }
16755 }
16756
16757 static char cseq[12] = "\\   ";
16758
16759 Boolean
16760 set_cont_sequence (char *new_seq)
16761 {
16762     int len;
16763     Boolean ret;
16764
16765     // handle bad attempts to set the sequence
16766         if (!new_seq)
16767                 return 0; // acceptable error - no debug
16768
16769     len = strlen(new_seq);
16770     ret = (len > 0) && (len < sizeof(cseq));
16771     if (ret)
16772       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16773     else if (appData.debugMode)
16774       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16775     return ret;
16776 }
16777
16778 /*
16779     reformat a source message so words don't cross the width boundary.  internal
16780     newlines are not removed.  returns the wrapped size (no null character unless
16781     included in source message).  If dest is NULL, only calculate the size required
16782     for the dest buffer.  lp argument indicats line position upon entry, and it's
16783     passed back upon exit.
16784 */
16785 int
16786 wrap (char *dest, char *src, int count, int width, int *lp)
16787 {
16788     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16789
16790     cseq_len = strlen(cseq);
16791     old_line = line = *lp;
16792     ansi = len = clen = 0;
16793
16794     for (i=0; i < count; i++)
16795     {
16796         if (src[i] == '\033')
16797             ansi = 1;
16798
16799         // if we hit the width, back up
16800         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16801         {
16802             // store i & len in case the word is too long
16803             old_i = i, old_len = len;
16804
16805             // find the end of the last word
16806             while (i && src[i] != ' ' && src[i] != '\n')
16807             {
16808                 i--;
16809                 len--;
16810             }
16811
16812             // word too long?  restore i & len before splitting it
16813             if ((old_i-i+clen) >= width)
16814             {
16815                 i = old_i;
16816                 len = old_len;
16817             }
16818
16819             // extra space?
16820             if (i && src[i-1] == ' ')
16821                 len--;
16822
16823             if (src[i] != ' ' && src[i] != '\n')
16824             {
16825                 i--;
16826                 if (len)
16827                     len--;
16828             }
16829
16830             // now append the newline and continuation sequence
16831             if (dest)
16832                 dest[len] = '\n';
16833             len++;
16834             if (dest)
16835                 strncpy(dest+len, cseq, cseq_len);
16836             len += cseq_len;
16837             line = cseq_len;
16838             clen = cseq_len;
16839             continue;
16840         }
16841
16842         if (dest)
16843             dest[len] = src[i];
16844         len++;
16845         if (!ansi)
16846             line++;
16847         if (src[i] == '\n')
16848             line = 0;
16849         if (src[i] == 'm')
16850             ansi = 0;
16851     }
16852     if (dest && appData.debugMode)
16853     {
16854         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16855             count, width, line, len, *lp);
16856         show_bytes(debugFP, src, count);
16857         fprintf(debugFP, "\ndest: ");
16858         show_bytes(debugFP, dest, len);
16859         fprintf(debugFP, "\n");
16860     }
16861     *lp = dest ? line : old_line;
16862
16863     return len;
16864 }
16865
16866 // [HGM] vari: routines for shelving variations
16867 Boolean modeRestore = FALSE;
16868
16869 void
16870 PushInner (int firstMove, int lastMove)
16871 {
16872         int i, j, nrMoves = lastMove - firstMove;
16873
16874         // push current tail of game on stack
16875         savedResult[storedGames] = gameInfo.result;
16876         savedDetails[storedGames] = gameInfo.resultDetails;
16877         gameInfo.resultDetails = NULL;
16878         savedFirst[storedGames] = firstMove;
16879         savedLast [storedGames] = lastMove;
16880         savedFramePtr[storedGames] = framePtr;
16881         framePtr -= nrMoves; // reserve space for the boards
16882         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16883             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16884             for(j=0; j<MOVE_LEN; j++)
16885                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16886             for(j=0; j<2*MOVE_LEN; j++)
16887                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16888             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16889             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16890             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16891             pvInfoList[firstMove+i-1].depth = 0;
16892             commentList[framePtr+i] = commentList[firstMove+i];
16893             commentList[firstMove+i] = NULL;
16894         }
16895
16896         storedGames++;
16897         forwardMostMove = firstMove; // truncate game so we can start variation
16898 }
16899
16900 void
16901 PushTail (int firstMove, int lastMove)
16902 {
16903         if(appData.icsActive) { // only in local mode
16904                 forwardMostMove = currentMove; // mimic old ICS behavior
16905                 return;
16906         }
16907         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16908
16909         PushInner(firstMove, lastMove);
16910         if(storedGames == 1) GreyRevert(FALSE);
16911         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16912 }
16913
16914 void
16915 PopInner (Boolean annotate)
16916 {
16917         int i, j, nrMoves;
16918         char buf[8000], moveBuf[20];
16919
16920         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16921         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16922         nrMoves = savedLast[storedGames] - currentMove;
16923         if(annotate) {
16924                 int cnt = 10;
16925                 if(!WhiteOnMove(currentMove))
16926                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16927                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16928                 for(i=currentMove; i<forwardMostMove; i++) {
16929                         if(WhiteOnMove(i))
16930                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16931                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16932                         strcat(buf, moveBuf);
16933                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16934                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16935                 }
16936                 strcat(buf, ")");
16937         }
16938         for(i=1; i<=nrMoves; i++) { // copy last variation back
16939             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16940             for(j=0; j<MOVE_LEN; j++)
16941                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16942             for(j=0; j<2*MOVE_LEN; j++)
16943                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16944             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16945             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16946             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16947             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16948             commentList[currentMove+i] = commentList[framePtr+i];
16949             commentList[framePtr+i] = NULL;
16950         }
16951         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16952         framePtr = savedFramePtr[storedGames];
16953         gameInfo.result = savedResult[storedGames];
16954         if(gameInfo.resultDetails != NULL) {
16955             free(gameInfo.resultDetails);
16956       }
16957         gameInfo.resultDetails = savedDetails[storedGames];
16958         forwardMostMove = currentMove + nrMoves;
16959 }
16960
16961 Boolean
16962 PopTail (Boolean annotate)
16963 {
16964         if(appData.icsActive) return FALSE; // only in local mode
16965         if(!storedGames) return FALSE; // sanity
16966         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16967
16968         PopInner(annotate);
16969         if(currentMove < forwardMostMove) ForwardEvent(); else
16970         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16971
16972         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16973         return TRUE;
16974 }
16975
16976 void
16977 CleanupTail ()
16978 {       // remove all shelved variations
16979         int i;
16980         for(i=0; i<storedGames; i++) {
16981             if(savedDetails[i])
16982                 free(savedDetails[i]);
16983             savedDetails[i] = NULL;
16984         }
16985         for(i=framePtr; i<MAX_MOVES; i++) {
16986                 if(commentList[i]) free(commentList[i]);
16987                 commentList[i] = NULL;
16988         }
16989         framePtr = MAX_MOVES-1;
16990         storedGames = 0;
16991 }
16992
16993 void
16994 LoadVariation (int index, char *text)
16995 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16996         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16997         int level = 0, move;
16998
16999         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17000         // first find outermost bracketing variation
17001         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17002             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17003                 if(*p == '{') wait = '}'; else
17004                 if(*p == '[') wait = ']'; else
17005                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17006                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17007             }
17008             if(*p == wait) wait = NULLCHAR; // closing ]} found
17009             p++;
17010         }
17011         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17012         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17013         end[1] = NULLCHAR; // clip off comment beyond variation
17014         ToNrEvent(currentMove-1);
17015         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17016         // kludge: use ParsePV() to append variation to game
17017         move = currentMove;
17018         ParsePV(start, TRUE, TRUE);
17019         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17020         ClearPremoveHighlights();
17021         CommentPopDown();
17022         ToNrEvent(currentMove+1);
17023 }
17024