Use combobox line for recent engines when available
[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(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2620         if(click == Release || moving) return FALSE;
2621         nrOfSeekAds = 0;
2622         soughtPending = TRUE;
2623         SendToICS(ics_prefix);
2624         SendToICS("sought\n"); // should this be "sought all"?
2625     } else { // issue challenge based on clicked ad
2626         int dist = 10000; int i, closest = 0, second = 0;
2627         for(i=0; i<nrOfSeekAds; i++) {
2628             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2629             if(d < dist) { dist = d; closest = i; }
2630             second += (d - zList[i] < 120); // count in-range ads
2631             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2632         }
2633         if(dist < 120) {
2634             char buf[MSG_SIZ];
2635             second = (second > 1);
2636             if(displayed != closest || second != lastSecond) {
2637                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2638                 lastSecond = second; displayed = closest;
2639             }
2640             if(click == Press) {
2641                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2642                 lastDown = closest;
2643                 return TRUE;
2644             } // on press 'hit', only show info
2645             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2646             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2647             SendToICS(ics_prefix);
2648             SendToICS(buf);
2649             return TRUE; // let incoming board of started game pop down the graph
2650         } else if(click == Release) { // release 'miss' is ignored
2651             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2652             if(moving == 2) { // right up-click
2653                 nrOfSeekAds = 0; // refresh graph
2654                 soughtPending = TRUE;
2655                 SendToICS(ics_prefix);
2656                 SendToICS("sought\n"); // should this be "sought all"?
2657             }
2658             return TRUE;
2659         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2660         // press miss or release hit 'pop down' seek graph
2661         seekGraphUp = FALSE;
2662         DrawPosition(TRUE, NULL);
2663     }
2664     return TRUE;
2665 }
2666
2667 void
2668 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2669 {
2670 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2671 #define STARTED_NONE 0
2672 #define STARTED_MOVES 1
2673 #define STARTED_BOARD 2
2674 #define STARTED_OBSERVE 3
2675 #define STARTED_HOLDINGS 4
2676 #define STARTED_CHATTER 5
2677 #define STARTED_COMMENT 6
2678 #define STARTED_MOVES_NOHIDE 7
2679
2680     static int started = STARTED_NONE;
2681     static char parse[20000];
2682     static int parse_pos = 0;
2683     static char buf[BUF_SIZE + 1];
2684     static int firstTime = TRUE, intfSet = FALSE;
2685     static ColorClass prevColor = ColorNormal;
2686     static int savingComment = FALSE;
2687     static int cmatch = 0; // continuation sequence match
2688     char *bp;
2689     char str[MSG_SIZ];
2690     int i, oldi;
2691     int buf_len;
2692     int next_out;
2693     int tkind;
2694     int backup;    /* [DM] For zippy color lines */
2695     char *p;
2696     char talker[MSG_SIZ]; // [HGM] chat
2697     int channel;
2698
2699     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2700
2701     if (appData.debugMode) {
2702       if (!error) {
2703         fprintf(debugFP, "<ICS: ");
2704         show_bytes(debugFP, data, count);
2705         fprintf(debugFP, "\n");
2706       }
2707     }
2708
2709     if (appData.debugMode) { int f = forwardMostMove;
2710         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2711                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2712                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2713     }
2714     if (count > 0) {
2715         /* If last read ended with a partial line that we couldn't parse,
2716            prepend it to the new read and try again. */
2717         if (leftover_len > 0) {
2718             for (i=0; i<leftover_len; i++)
2719               buf[i] = buf[leftover_start + i];
2720         }
2721
2722     /* copy new characters into the buffer */
2723     bp = buf + leftover_len;
2724     buf_len=leftover_len;
2725     for (i=0; i<count; i++)
2726     {
2727         // ignore these
2728         if (data[i] == '\r')
2729             continue;
2730
2731         // join lines split by ICS?
2732         if (!appData.noJoin)
2733         {
2734             /*
2735                 Joining just consists of finding matches against the
2736                 continuation sequence, and discarding that sequence
2737                 if found instead of copying it.  So, until a match
2738                 fails, there's nothing to do since it might be the
2739                 complete sequence, and thus, something we don't want
2740                 copied.
2741             */
2742             if (data[i] == cont_seq[cmatch])
2743             {
2744                 cmatch++;
2745                 if (cmatch == strlen(cont_seq))
2746                 {
2747                     cmatch = 0; // complete match.  just reset the counter
2748
2749                     /*
2750                         it's possible for the ICS to not include the space
2751                         at the end of the last word, making our [correct]
2752                         join operation fuse two separate words.  the server
2753                         does this when the space occurs at the width setting.
2754                     */
2755                     if (!buf_len || buf[buf_len-1] != ' ')
2756                     {
2757                         *bp++ = ' ';
2758                         buf_len++;
2759                     }
2760                 }
2761                 continue;
2762             }
2763             else if (cmatch)
2764             {
2765                 /*
2766                     match failed, so we have to copy what matched before
2767                     falling through and copying this character.  In reality,
2768                     this will only ever be just the newline character, but
2769                     it doesn't hurt to be precise.
2770                 */
2771                 strncpy(bp, cont_seq, cmatch);
2772                 bp += cmatch;
2773                 buf_len += cmatch;
2774                 cmatch = 0;
2775             }
2776         }
2777
2778         // copy this char
2779         *bp++ = data[i];
2780         buf_len++;
2781     }
2782
2783         buf[buf_len] = NULLCHAR;
2784 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2785         next_out = 0;
2786         leftover_start = 0;
2787
2788         i = 0;
2789         while (i < buf_len) {
2790             /* Deal with part of the TELNET option negotiation
2791                protocol.  We refuse to do anything beyond the
2792                defaults, except that we allow the WILL ECHO option,
2793                which ICS uses to turn off password echoing when we are
2794                directly connected to it.  We reject this option
2795                if localLineEditing mode is on (always on in xboard)
2796                and we are talking to port 23, which might be a real
2797                telnet server that will try to keep WILL ECHO on permanently.
2798              */
2799             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2800                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2801                 unsigned char option;
2802                 oldi = i;
2803                 switch ((unsigned char) buf[++i]) {
2804                   case TN_WILL:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<WILL ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       case TN_ECHO:
2809                         if (appData.debugMode)
2810                           fprintf(debugFP, "ECHO ");
2811                         /* Reply only if this is a change, according
2812                            to the protocol rules. */
2813                         if (remoteEchoOption) break;
2814                         if (appData.localLineEditing &&
2815                             atoi(appData.icsPort) == TN_PORT) {
2816                             TelnetRequest(TN_DONT, TN_ECHO);
2817                         } else {
2818                             EchoOff();
2819                             TelnetRequest(TN_DO, TN_ECHO);
2820                             remoteEchoOption = TRUE;
2821                         }
2822                         break;
2823                       default:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "%d ", option);
2826                         /* Whatever this is, we don't want it. */
2827                         TelnetRequest(TN_DONT, option);
2828                         break;
2829                     }
2830                     break;
2831                   case TN_WONT:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<WONT ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       case TN_ECHO:
2836                         if (appData.debugMode)
2837                           fprintf(debugFP, "ECHO ");
2838                         /* Reply only if this is a change, according
2839                            to the protocol rules. */
2840                         if (!remoteEchoOption) break;
2841                         EchoOn();
2842                         TelnetRequest(TN_DONT, TN_ECHO);
2843                         remoteEchoOption = FALSE;
2844                         break;
2845                       default:
2846                         if (appData.debugMode)
2847                           fprintf(debugFP, "%d ", (unsigned char) option);
2848                         /* Whatever this is, it must already be turned
2849                            off, because we never agree to turn on
2850                            anything non-default, so according to the
2851                            protocol rules, we don't reply. */
2852                         break;
2853                     }
2854                     break;
2855                   case TN_DO:
2856                     if (appData.debugMode)
2857                       fprintf(debugFP, "\n<DO ");
2858                     switch (option = (unsigned char) buf[++i]) {
2859                       default:
2860                         /* Whatever this is, we refuse to do it. */
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", option);
2863                         TelnetRequest(TN_WONT, option);
2864                         break;
2865                     }
2866                     break;
2867                   case TN_DONT:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<DONT ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       default:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "%d ", option);
2874                         /* Whatever this is, we are already not doing
2875                            it, because we never agree to do anything
2876                            non-default, so according to the protocol
2877                            rules, we don't reply. */
2878                         break;
2879                     }
2880                     break;
2881                   case TN_IAC:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<IAC ");
2884                     /* Doubled IAC; pass it through */
2885                     i--;
2886                     break;
2887                   default:
2888                     if (appData.debugMode)
2889                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2890                     /* Drop all other telnet commands on the floor */
2891                     break;
2892                 }
2893                 if (oldi > next_out)
2894                   SendToPlayer(&buf[next_out], oldi - next_out);
2895                 if (++i > next_out)
2896                   next_out = i;
2897                 continue;
2898             }
2899
2900             /* OK, this at least will *usually* work */
2901             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2902                 loggedOn = TRUE;
2903             }
2904
2905             if (loggedOn && !intfSet) {
2906                 if (ics_type == ICS_ICC) {
2907                   snprintf(str, MSG_SIZ,
2908                           "/set-quietly interface %s\n/set-quietly style 12\n",
2909                           programVersion);
2910                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2912                 } else if (ics_type == ICS_CHESSNET) {
2913                   snprintf(str, MSG_SIZ, "/style 12\n");
2914                 } else {
2915                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2916                   strcat(str, programVersion);
2917                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2918                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2920 #ifdef WIN32
2921                   strcat(str, "$iset nohighlight 1\n");
2922 #endif
2923                   strcat(str, "$iset lock 1\n$style 12\n");
2924                 }
2925                 SendToICS(str);
2926                 NotifyFrontendLogin();
2927                 intfSet = TRUE;
2928             }
2929
2930             if (started == STARTED_COMMENT) {
2931                 /* Accumulate characters in comment */
2932                 parse[parse_pos++] = buf[i];
2933                 if (buf[i] == '\n') {
2934                     parse[parse_pos] = NULLCHAR;
2935                     if(chattingPartner>=0) {
2936                         char mess[MSG_SIZ];
2937                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2938                         OutputChatMessage(chattingPartner, mess);
2939                         chattingPartner = -1;
2940                         next_out = i+1; // [HGM] suppress printing in ICS window
2941                     } else
2942                     if(!suppressKibitz) // [HGM] kibitz
2943                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2944                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2945                         int nrDigit = 0, nrAlph = 0, j;
2946                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2947                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2948                         parse[parse_pos] = NULLCHAR;
2949                         // try to be smart: if it does not look like search info, it should go to
2950                         // ICS interaction window after all, not to engine-output window.
2951                         for(j=0; j<parse_pos; j++) { // count letters and digits
2952                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2953                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2954                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2955                         }
2956                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2957                             int depth=0; float score;
2958                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2959                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2960                                 pvInfoList[forwardMostMove-1].depth = depth;
2961                                 pvInfoList[forwardMostMove-1].score = 100*score;
2962                             }
2963                             OutputKibitz(suppressKibitz, parse);
2964                         } else {
2965                             char tmp[MSG_SIZ];
2966                             if(gameMode == IcsObserving) // restore original ICS messages
2967                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2968                             else
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
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, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3103                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3104                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3105                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3106                         suppressKibitz = TRUE;
3107                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                         next_out = i;
3109                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3110                                 && (gameMode == IcsPlayingWhite)) ||
3111                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3112                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3113                             started = STARTED_CHATTER; // own kibitz we simply discard
3114                         else {
3115                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3116                             parse_pos = 0; parse[0] = NULLCHAR;
3117                             savingComment = TRUE;
3118                             suppressKibitz = gameMode != IcsObserving ? 2 :
3119                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3120                         }
3121                         continue;
3122                 } else
3123                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3124                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3125                          && atoi(star_match[0])) {
3126                     // suppress the acknowledgements of our own autoKibitz
3127                     char *p;
3128                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3129                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3130                     SendToPlayer(star_match[0], strlen(star_match[0]));
3131                     if(looking_at(buf, &i, "*% ")) // eat prompt
3132                         suppressKibitz = FALSE;
3133                     next_out = i;
3134                     continue;
3135                 }
3136             } // [HGM] kibitz: end of patch
3137
3138             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3139
3140             // [HGM] chat: intercept tells by users for which we have an open chat window
3141             channel = -1;
3142             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3143                                            looking_at(buf, &i, "* whispers:") ||
3144                                            looking_at(buf, &i, "* kibitzes:") ||
3145                                            looking_at(buf, &i, "* shouts:") ||
3146                                            looking_at(buf, &i, "* c-shouts:") ||
3147                                            looking_at(buf, &i, "--> * ") ||
3148                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3150                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3151                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3152                 int p;
3153                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3154                 chattingPartner = -1;
3155
3156                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3157                 for(p=0; p<MAX_CHAT; p++) {
3158                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3159                     talker[0] = '['; strcat(talker, "] ");
3160                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3161                     chattingPartner = p; break;
3162                     }
3163                 } else
3164                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3165                 for(p=0; p<MAX_CHAT; p++) {
3166                     if(!strcmp("kibitzes", chatPartner[p])) {
3167                         talker[0] = '['; strcat(talker, "] ");
3168                         chattingPartner = p; break;
3169                     }
3170                 } else
3171                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(!strcmp("whispers", chatPartner[p])) {
3174                         talker[0] = '['; strcat(talker, "] ");
3175                         chattingPartner = p; break;
3176                     }
3177                 } else
3178                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3179                   if(buf[i-8] == '-' && buf[i-3] == 't')
3180                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3181                     if(!strcmp("c-shouts", chatPartner[p])) {
3182                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3183                         chattingPartner = p; break;
3184                     }
3185                   }
3186                   if(chattingPartner < 0)
3187                   for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("shouts", chatPartner[p])) {
3189                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3190                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3191                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3192                         chattingPartner = p; break;
3193                     }
3194                   }
3195                 }
3196                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3197                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3198                     talker[0] = 0; Colorize(ColorTell, FALSE);
3199                     chattingPartner = p; break;
3200                 }
3201                 if(chattingPartner<0) i = oldi; else {
3202                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3203                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3204                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3205                     started = STARTED_COMMENT;
3206                     parse_pos = 0; parse[0] = NULLCHAR;
3207                     savingComment = 3 + chattingPartner; // counts as TRUE
3208                     suppressKibitz = TRUE;
3209                     continue;
3210                 }
3211             } // [HGM] chat: end of patch
3212
3213           backup = i;
3214             if (appData.zippyTalk || appData.zippyPlay) {
3215                 /* [DM] Backup address for color zippy lines */
3216 #if ZIPPY
3217                if (loggedOn == TRUE)
3218                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3219                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3220 #endif
3221             } // [DM] 'else { ' deleted
3222                 if (
3223                     /* Regular tells and says */
3224                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3225                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3226                     looking_at(buf, &i, "* says: ") ||
3227                     /* Don't color "message" or "messages" output */
3228                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3229                     looking_at(buf, &i, "*. * at *:*: ") ||
3230                     looking_at(buf, &i, "--* (*:*): ") ||
3231                     /* Message notifications (same color as tells) */
3232                     looking_at(buf, &i, "* has left a message ") ||
3233                     looking_at(buf, &i, "* just sent you a message:\n") ||
3234                     /* Whispers and kibitzes */
3235                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3236                     looking_at(buf, &i, "* kibitzes: ") ||
3237                     /* Channel tells */
3238                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3239
3240                   if (tkind == 1 && strchr(star_match[0], ':')) {
3241                       /* Avoid "tells you:" spoofs in channels */
3242                      tkind = 3;
3243                   }
3244                   if (star_match[0][0] == NULLCHAR ||
3245                       strchr(star_match[0], ' ') ||
3246                       (tkind == 3 && strchr(star_match[1], ' '))) {
3247                     /* Reject bogus matches */
3248                     i = oldi;
3249                   } else {
3250                     if (appData.colorize) {
3251                       if (oldi > next_out) {
3252                         SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = oldi;
3254                       }
3255                       switch (tkind) {
3256                       case 1:
3257                         Colorize(ColorTell, FALSE);
3258                         curColor = ColorTell;
3259                         break;
3260                       case 2:
3261                         Colorize(ColorKibitz, FALSE);
3262                         curColor = ColorKibitz;
3263                         break;
3264                       case 3:
3265                         p = strrchr(star_match[1], '(');
3266                         if (p == NULL) {
3267                           p = star_match[1];
3268                         } else {
3269                           p++;
3270                         }
3271                         if (atoi(p) == 1) {
3272                           Colorize(ColorChannel1, FALSE);
3273                           curColor = ColorChannel1;
3274                         } else {
3275                           Colorize(ColorChannel, FALSE);
3276                           curColor = ColorChannel;
3277                         }
3278                         break;
3279                       case 5:
3280                         curColor = ColorNormal;
3281                         break;
3282                       }
3283                     }
3284                     if (started == STARTED_NONE && appData.autoComment &&
3285                         (gameMode == IcsObserving ||
3286                          gameMode == IcsPlayingWhite ||
3287                          gameMode == IcsPlayingBlack)) {
3288                       parse_pos = i - oldi;
3289                       memcpy(parse, &buf[oldi], parse_pos);
3290                       parse[parse_pos] = NULLCHAR;
3291                       started = STARTED_COMMENT;
3292                       savingComment = TRUE;
3293                     } else {
3294                       started = STARTED_CHATTER;
3295                       savingComment = FALSE;
3296                     }
3297                     loggedOn = TRUE;
3298                     continue;
3299                   }
3300                 }
3301
3302                 if (looking_at(buf, &i, "* s-shouts: ") ||
3303                     looking_at(buf, &i, "* c-shouts: ")) {
3304                     if (appData.colorize) {
3305                         if (oldi > next_out) {
3306                             SendToPlayer(&buf[next_out], oldi - next_out);
3307                             next_out = oldi;
3308                         }
3309                         Colorize(ColorSShout, FALSE);
3310                         curColor = ColorSShout;
3311                     }
3312                     loggedOn = TRUE;
3313                     started = STARTED_CHATTER;
3314                     continue;
3315                 }
3316
3317                 if (looking_at(buf, &i, "--->")) {
3318                     loggedOn = TRUE;
3319                     continue;
3320                 }
3321
3322                 if (looking_at(buf, &i, "* shouts: ") ||
3323                     looking_at(buf, &i, "--> ")) {
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorShout, FALSE);
3330                         curColor = ColorShout;
3331                     }
3332                     loggedOn = TRUE;
3333                     started = STARTED_CHATTER;
3334                     continue;
3335                 }
3336
3337                 if (looking_at( buf, &i, "Challenge:")) {
3338                     if (appData.colorize) {
3339                         if (oldi > next_out) {
3340                             SendToPlayer(&buf[next_out], oldi - next_out);
3341                             next_out = oldi;
3342                         }
3343                         Colorize(ColorChallenge, FALSE);
3344                         curColor = ColorChallenge;
3345                     }
3346                     loggedOn = TRUE;
3347                     continue;
3348                 }
3349
3350                 if (looking_at(buf, &i, "* offers you") ||
3351                     looking_at(buf, &i, "* offers to be") ||
3352                     looking_at(buf, &i, "* would like to") ||
3353                     looking_at(buf, &i, "* requests to") ||
3354                     looking_at(buf, &i, "Your opponent offers") ||
3355                     looking_at(buf, &i, "Your opponent requests")) {
3356
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorRequest, FALSE);
3363                         curColor = ColorRequest;
3364                     }
3365                     continue;
3366                 }
3367
3368                 if (looking_at(buf, &i, "* (*) seeking")) {
3369                     if (appData.colorize) {
3370                         if (oldi > next_out) {
3371                             SendToPlayer(&buf[next_out], oldi - next_out);
3372                             next_out = oldi;
3373                         }
3374                         Colorize(ColorSeek, FALSE);
3375                         curColor = ColorSeek;
3376                     }
3377                     continue;
3378             }
3379
3380           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3381
3382             if (looking_at(buf, &i, "\\   ")) {
3383                 if (prevColor != ColorNormal) {
3384                     if (oldi > next_out) {
3385                         SendToPlayer(&buf[next_out], oldi - next_out);
3386                         next_out = oldi;
3387                     }
3388                     Colorize(prevColor, TRUE);
3389                     curColor = prevColor;
3390                 }
3391                 if (savingComment) {
3392                     parse_pos = i - oldi;
3393                     memcpy(parse, &buf[oldi], parse_pos);
3394                     parse[parse_pos] = NULLCHAR;
3395                     started = STARTED_COMMENT;
3396                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3397                         chattingPartner = savingComment - 3; // kludge to remember the box
3398                 } else {
3399                     started = STARTED_CHATTER;
3400                 }
3401                 continue;
3402             }
3403
3404             if (looking_at(buf, &i, "Black Strength :") ||
3405                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3406                 looking_at(buf, &i, "<10>") ||
3407                 looking_at(buf, &i, "#@#")) {
3408                 /* Wrong board style */
3409                 loggedOn = TRUE;
3410                 SendToICS(ics_prefix);
3411                 SendToICS("set style 12\n");
3412                 SendToICS(ics_prefix);
3413                 SendToICS("refresh\n");
3414                 continue;
3415             }
3416
3417             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3418                 ICSInitScript();
3419                 have_sent_ICS_logon = 1;
3420                 continue;
3421             }
3422
3423             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3424                 (looking_at(buf, &i, "\n<12> ") ||
3425                  looking_at(buf, &i, "<12> "))) {
3426                 loggedOn = TRUE;
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_BOARD;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3437                 looking_at(buf, &i, "<b1> ")) {
3438                 if (oldi > next_out) {
3439                     SendToPlayer(&buf[next_out], oldi - next_out);
3440                 }
3441                 next_out = i;
3442                 started = STARTED_HOLDINGS;
3443                 parse_pos = 0;
3444                 continue;
3445             }
3446
3447             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3448                 loggedOn = TRUE;
3449                 /* Header for a move list -- first line */
3450
3451                 switch (ics_getting_history) {
3452                   case H_FALSE:
3453                     switch (gameMode) {
3454                       case IcsIdle:
3455                       case BeginningOfGame:
3456                         /* User typed "moves" or "oldmoves" while we
3457                            were idle.  Pretend we asked for these
3458                            moves and soak them up so user can step
3459                            through them and/or save them.
3460                            */
3461                         Reset(FALSE, TRUE);
3462                         gameMode = IcsObserving;
3463                         ModeHighlight();
3464                         ics_gamenum = -1;
3465                         ics_getting_history = H_GOT_UNREQ_HEADER;
3466                         break;
3467                       case EditGame: /*?*/
3468                       case EditPosition: /*?*/
3469                         /* Should above feature work in these modes too? */
3470                         /* For now it doesn't */
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                       default:
3474                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3475                         break;
3476                     }
3477                     break;
3478                   case H_REQUESTED:
3479                     /* Is this the right one? */
3480                     if (gameInfo.white && gameInfo.black &&
3481                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3482                         strcmp(gameInfo.black, star_match[2]) == 0) {
3483                         /* All is well */
3484                         ics_getting_history = H_GOT_REQ_HEADER;
3485                     }
3486                     break;
3487                   case H_GOT_REQ_HEADER:
3488                   case H_GOT_UNREQ_HEADER:
3489                   case H_GOT_UNWANTED_HEADER:
3490                   case H_GETTING_MOVES:
3491                     /* Should not happen */
3492                     DisplayError(_("Error gathering move list: two headers"), 0);
3493                     ics_getting_history = H_FALSE;
3494                     break;
3495                 }
3496
3497                 /* Save player ratings into gameInfo if needed */
3498                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3499                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3500                     (gameInfo.whiteRating == -1 ||
3501                      gameInfo.blackRating == -1)) {
3502
3503                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3504                     gameInfo.blackRating = string_to_rating(star_match[3]);
3505                     if (appData.debugMode)
3506                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3507                               gameInfo.whiteRating, gameInfo.blackRating);
3508                 }
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i,
3513               "* * match, initial time: * minute*, increment: * second")) {
3514                 /* Header for a move list -- second line */
3515                 /* Initial board will follow if this is a wild game */
3516                 if (gameInfo.event != NULL) free(gameInfo.event);
3517                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3518                 gameInfo.event = StrSave(str);
3519                 /* [HGM] we switched variant. Translate boards if needed. */
3520                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3521                 continue;
3522             }
3523
3524             if (looking_at(buf, &i, "Move  ")) {
3525                 /* Beginning of a move list */
3526                 switch (ics_getting_history) {
3527                   case H_FALSE:
3528                     /* Normally should not happen */
3529                     /* Maybe user hit reset while we were parsing */
3530                     break;
3531                   case H_REQUESTED:
3532                     /* Happens if we are ignoring a move list that is not
3533                      * the one we just requested.  Common if the user
3534                      * tries to observe two games without turning off
3535                      * getMoveList */
3536                     break;
3537                   case H_GETTING_MOVES:
3538                     /* Should not happen */
3539                     DisplayError(_("Error gathering move list: nested"), 0);
3540                     ics_getting_history = H_FALSE;
3541                     break;
3542                   case H_GOT_REQ_HEADER:
3543                     ics_getting_history = H_GETTING_MOVES;
3544                     started = STARTED_MOVES;
3545                     parse_pos = 0;
3546                     if (oldi > next_out) {
3547                         SendToPlayer(&buf[next_out], oldi - next_out);
3548                     }
3549                     break;
3550                   case H_GOT_UNREQ_HEADER:
3551                     ics_getting_history = H_GETTING_MOVES;
3552                     started = STARTED_MOVES_NOHIDE;
3553                     parse_pos = 0;
3554                     break;
3555                   case H_GOT_UNWANTED_HEADER:
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "% ") ||
3563                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3564                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3565                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3566                     soughtPending = FALSE;
3567                     seekGraphUp = TRUE;
3568                     DrawSeekGraph();
3569                 }
3570                 if(suppressKibitz) next_out = i;
3571                 savingComment = FALSE;
3572                 suppressKibitz = 0;
3573                 switch (started) {
3574                   case STARTED_MOVES:
3575                   case STARTED_MOVES_NOHIDE:
3576                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3577                     parse[parse_pos + i - oldi] = NULLCHAR;
3578                     ParseGameHistory(parse);
3579 #if ZIPPY
3580                     if (appData.zippyPlay && first.initDone) {
3581                         FeedMovesToProgram(&first, forwardMostMove);
3582                         if (gameMode == IcsPlayingWhite) {
3583                             if (WhiteOnMove(forwardMostMove)) {
3584                                 if (first.sendTime) {
3585                                   if (first.useColors) {
3586                                     SendToProgram("black\n", &first);
3587                                   }
3588                                   SendTimeRemaining(&first, TRUE);
3589                                 }
3590                                 if (first.useColors) {
3591                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3592                                 }
3593                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3594                                 first.maybeThinking = TRUE;
3595                             } else {
3596                                 if (first.usePlayother) {
3597                                   if (first.sendTime) {
3598                                     SendTimeRemaining(&first, TRUE);
3599                                   }
3600                                   SendToProgram("playother\n", &first);
3601                                   firstMove = FALSE;
3602                                 } else {
3603                                   firstMove = TRUE;
3604                                 }
3605                             }
3606                         } else if (gameMode == IcsPlayingBlack) {
3607                             if (!WhiteOnMove(forwardMostMove)) {
3608                                 if (first.sendTime) {
3609                                   if (first.useColors) {
3610                                     SendToProgram("white\n", &first);
3611                                   }
3612                                   SendTimeRemaining(&first, FALSE);
3613                                 }
3614                                 if (first.useColors) {
3615                                   SendToProgram("black\n", &first);
3616                                 }
3617                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3618                                 first.maybeThinking = TRUE;
3619                             } else {
3620                                 if (first.usePlayother) {
3621                                   if (first.sendTime) {
3622                                     SendTimeRemaining(&first, FALSE);
3623                                   }
3624                                   SendToProgram("playother\n", &first);
3625                                   firstMove = FALSE;
3626                                 } else {
3627                                   firstMove = TRUE;
3628                                 }
3629                             }
3630                         }
3631                     }
3632 #endif
3633                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3634                         /* Moves came from oldmoves or moves command
3635                            while we weren't doing anything else.
3636                            */
3637                         currentMove = forwardMostMove;
3638                         ClearHighlights();/*!!could figure this out*/
3639                         flipView = appData.flipView;
3640                         DrawPosition(TRUE, boards[currentMove]);
3641                         DisplayBothClocks();
3642                         snprintf(str, MSG_SIZ, "%s %s %s",
3643                                 gameInfo.white, _("vs."),  gameInfo.black);
3644                         DisplayTitle(str);
3645                         gameMode = IcsIdle;
3646                     } else {
3647                         /* Moves were history of an active game */
3648                         if (gameInfo.resultDetails != NULL) {
3649                             free(gameInfo.resultDetails);
3650                             gameInfo.resultDetails = NULL;
3651                         }
3652                     }
3653                     HistorySet(parseList, backwardMostMove,
3654                                forwardMostMove, currentMove-1);
3655                     DisplayMove(currentMove - 1);
3656                     if (started == STARTED_MOVES) next_out = i;
3657                     started = STARTED_NONE;
3658                     ics_getting_history = H_FALSE;
3659                     break;
3660
3661                   case STARTED_OBSERVE:
3662                     started = STARTED_NONE;
3663                     SendToICS(ics_prefix);
3664                     SendToICS("refresh\n");
3665                     break;
3666
3667                   default:
3668                     break;
3669                 }
3670                 if(bookHit) { // [HGM] book: simulate book reply
3671                     static char bookMove[MSG_SIZ]; // a bit generous?
3672
3673                     programStats.nodes = programStats.depth = programStats.time =
3674                     programStats.score = programStats.got_only_move = 0;
3675                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3676
3677                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3678                     strcat(bookMove, bookHit);
3679                     HandleMachineMove(bookMove, &first);
3680                 }
3681                 continue;
3682             }
3683
3684             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3685                  started == STARTED_HOLDINGS ||
3686                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3687                 /* Accumulate characters in move list or board */
3688                 parse[parse_pos++] = buf[i];
3689             }
3690
3691             /* Start of game messages.  Mostly we detect start of game
3692                when the first board image arrives.  On some versions
3693                of the ICS, though, we need to do a "refresh" after starting
3694                to observe in order to get the current board right away. */
3695             if (looking_at(buf, &i, "Adding game * to observation list")) {
3696                 started = STARTED_OBSERVE;
3697                 continue;
3698             }
3699
3700             /* Handle auto-observe */
3701             if (appData.autoObserve &&
3702                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3703                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3704                 char *player;
3705                 /* Choose the player that was highlighted, if any. */
3706                 if (star_match[0][0] == '\033' ||
3707                     star_match[1][0] != '\033') {
3708                     player = star_match[0];
3709                 } else {
3710                     player = star_match[2];
3711                 }
3712                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3713                         ics_prefix, StripHighlightAndTitle(player));
3714                 SendToICS(str);
3715
3716                 /* Save ratings from notify string */
3717                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3718                 player1Rating = string_to_rating(star_match[1]);
3719                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3720                 player2Rating = string_to_rating(star_match[3]);
3721
3722                 if (appData.debugMode)
3723                   fprintf(debugFP,
3724                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3725                           player1Name, player1Rating,
3726                           player2Name, player2Rating);
3727
3728                 continue;
3729             }
3730
3731             /* Deal with automatic examine mode after a game,
3732                and with IcsObserving -> IcsExamining transition */
3733             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3734                 looking_at(buf, &i, "has made you an examiner of game *")) {
3735
3736                 int gamenum = atoi(star_match[0]);
3737                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3738                     gamenum == ics_gamenum) {
3739                     /* We were already playing or observing this game;
3740                        no need to refetch history */
3741                     gameMode = IcsExamining;
3742                     if (pausing) {
3743                         pauseExamForwardMostMove = forwardMostMove;
3744                     } else if (currentMove < forwardMostMove) {
3745                         ForwardInner(forwardMostMove);
3746                     }
3747                 } else {
3748                     /* I don't think this case really can happen */
3749                     SendToICS(ics_prefix);
3750                     SendToICS("refresh\n");
3751                 }
3752                 continue;
3753             }
3754
3755             /* Error messages */
3756 //          if (ics_user_moved) {
3757             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3758                 if (looking_at(buf, &i, "Illegal move") ||
3759                     looking_at(buf, &i, "Not a legal move") ||
3760                     looking_at(buf, &i, "Your king is in check") ||
3761                     looking_at(buf, &i, "It isn't your turn") ||
3762                     looking_at(buf, &i, "It is not your move")) {
3763                     /* Illegal move */
3764                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3765                         currentMove = forwardMostMove-1;
3766                         DisplayMove(currentMove - 1); /* before DMError */
3767                         DrawPosition(FALSE, boards[currentMove]);
3768                         SwitchClocks(forwardMostMove-1); // [HGM] race
3769                         DisplayBothClocks();
3770                     }
3771                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3772                     ics_user_moved = 0;
3773                     continue;
3774                 }
3775             }
3776
3777             if (looking_at(buf, &i, "still have time") ||
3778                 looking_at(buf, &i, "not out of time") ||
3779                 looking_at(buf, &i, "either player is out of time") ||
3780                 looking_at(buf, &i, "has timeseal; checking")) {
3781                 /* We must have called his flag a little too soon */
3782                 whiteFlag = blackFlag = FALSE;
3783                 continue;
3784             }
3785
3786             if (looking_at(buf, &i, "added * seconds to") ||
3787                 looking_at(buf, &i, "seconds were added to")) {
3788                 /* Update the clocks */
3789                 SendToICS(ics_prefix);
3790                 SendToICS("refresh\n");
3791                 continue;
3792             }
3793
3794             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3795                 ics_clock_paused = TRUE;
3796                 StopClocks();
3797                 continue;
3798             }
3799
3800             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3801                 ics_clock_paused = FALSE;
3802                 StartClocks();
3803                 continue;
3804             }
3805
3806             /* Grab player ratings from the Creating: message.
3807                Note we have to check for the special case when
3808                the ICS inserts things like [white] or [black]. */
3809             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3810                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3811                 /* star_matches:
3812                    0    player 1 name (not necessarily white)
3813                    1    player 1 rating
3814                    2    empty, white, or black (IGNORED)
3815                    3    player 2 name (not necessarily black)
3816                    4    player 2 rating
3817
3818                    The names/ratings are sorted out when the game
3819                    actually starts (below).
3820                 */
3821                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3822                 player1Rating = string_to_rating(star_match[1]);
3823                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3824                 player2Rating = string_to_rating(star_match[4]);
3825
3826                 if (appData.debugMode)
3827                   fprintf(debugFP,
3828                           "Ratings from 'Creating:' %s %d, %s %d\n",
3829                           player1Name, player1Rating,
3830                           player2Name, player2Rating);
3831
3832                 continue;
3833             }
3834
3835             /* Improved generic start/end-of-game messages */
3836             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3837                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3838                 /* If tkind == 0: */
3839                 /* star_match[0] is the game number */
3840                 /*           [1] is the white player's name */
3841                 /*           [2] is the black player's name */
3842                 /* For end-of-game: */
3843                 /*           [3] is the reason for the game end */
3844                 /*           [4] is a PGN end game-token, preceded by " " */
3845                 /* For start-of-game: */
3846                 /*           [3] begins with "Creating" or "Continuing" */
3847                 /*           [4] is " *" or empty (don't care). */
3848                 int gamenum = atoi(star_match[0]);
3849                 char *whitename, *blackname, *why, *endtoken;
3850                 ChessMove endtype = EndOfFile;
3851
3852                 if (tkind == 0) {
3853                   whitename = star_match[1];
3854                   blackname = star_match[2];
3855                   why = star_match[3];
3856                   endtoken = star_match[4];
3857                 } else {
3858                   whitename = star_match[1];
3859                   blackname = star_match[3];
3860                   why = star_match[5];
3861                   endtoken = star_match[6];
3862                 }
3863
3864                 /* Game start messages */
3865                 if (strncmp(why, "Creating ", 9) == 0 ||
3866                     strncmp(why, "Continuing ", 11) == 0) {
3867                     gs_gamenum = gamenum;
3868                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3869                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3870                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3871 #if ZIPPY
3872                     if (appData.zippyPlay) {
3873                         ZippyGameStart(whitename, blackname);
3874                     }
3875 #endif /*ZIPPY*/
3876                     partnerBoardValid = FALSE; // [HGM] bughouse
3877                     continue;
3878                 }
3879
3880                 /* Game end messages */
3881                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3882                     ics_gamenum != gamenum) {
3883                     continue;
3884                 }
3885                 while (endtoken[0] == ' ') endtoken++;
3886                 switch (endtoken[0]) {
3887                   case '*':
3888                   default:
3889                     endtype = GameUnfinished;
3890                     break;
3891                   case '0':
3892                     endtype = BlackWins;
3893                     break;
3894                   case '1':
3895                     if (endtoken[1] == '/')
3896                       endtype = GameIsDrawn;
3897                     else
3898                       endtype = WhiteWins;
3899                     break;
3900                 }
3901                 GameEnds(endtype, why, GE_ICS);
3902 #if ZIPPY
3903                 if (appData.zippyPlay && first.initDone) {
3904                     ZippyGameEnd(endtype, why);
3905                     if (first.pr == NoProc) {
3906                       /* Start the next process early so that we'll
3907                          be ready for the next challenge */
3908                       StartChessProgram(&first);
3909                     }
3910                     /* Send "new" early, in case this command takes
3911                        a long time to finish, so that we'll be ready
3912                        for the next challenge. */
3913                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3914                     Reset(TRUE, TRUE);
3915                 }
3916 #endif /*ZIPPY*/
3917                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3918                 continue;
3919             }
3920
3921             if (looking_at(buf, &i, "Removing game * from observation") ||
3922                 looking_at(buf, &i, "no longer observing game *") ||
3923                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3924                 if (gameMode == IcsObserving &&
3925                     atoi(star_match[0]) == ics_gamenum)
3926                   {
3927                       /* icsEngineAnalyze */
3928                       if (appData.icsEngineAnalyze) {
3929                             ExitAnalyzeMode();
3930                             ModeHighlight();
3931                       }
3932                       StopClocks();
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             if (looking_at(buf, &i, "no longer examining game *")) {
3941                 if (gameMode == IcsExamining &&
3942                     atoi(star_match[0]) == ics_gamenum)
3943                   {
3944                       gameMode = IcsIdle;
3945                       ics_gamenum = -1;
3946                       ics_user_moved = FALSE;
3947                   }
3948                 continue;
3949             }
3950
3951             /* Advance leftover_start past any newlines we find,
3952                so only partial lines can get reparsed */
3953             if (looking_at(buf, &i, "\n")) {
3954                 prevColor = curColor;
3955                 if (curColor != ColorNormal) {
3956                     if (oldi > next_out) {
3957                         SendToPlayer(&buf[next_out], oldi - next_out);
3958                         next_out = oldi;
3959                     }
3960                     Colorize(ColorNormal, FALSE);
3961                     curColor = ColorNormal;
3962                 }
3963                 if (started == STARTED_BOARD) {
3964                     started = STARTED_NONE;
3965                     parse[parse_pos] = NULLCHAR;
3966                     ParseBoard12(parse);
3967                     ics_user_moved = 0;
3968
3969                     /* Send premove here */
3970                     if (appData.premove) {
3971                       char str[MSG_SIZ];
3972                       if (currentMove == 0 &&
3973                           gameMode == IcsPlayingWhite &&
3974                           appData.premoveWhite) {
3975                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                         SendToICS(str);
3979                       } else if (currentMove == 1 &&
3980                                  gameMode == IcsPlayingBlack &&
3981                                  appData.premoveBlack) {
3982                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                         SendToICS(str);
3986                       } else if (gotPremove) {
3987                         gotPremove = 0;
3988                         ClearPremoveHighlights();
3989                         if (appData.debugMode)
3990                           fprintf(debugFP, "Sending premove:\n");
3991                           UserMoveEvent(premoveFromX, premoveFromY,
3992                                         premoveToX, premoveToY,
3993                                         premovePromoChar);
3994                       }
3995                     }
3996
3997                     /* Usually suppress following prompt */
3998                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3999                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4000                         if (looking_at(buf, &i, "*% ")) {
4001                             savingComment = FALSE;
4002                             suppressKibitz = 0;
4003                         }
4004                     }
4005                     next_out = i;
4006                 } else if (started == STARTED_HOLDINGS) {
4007                     int gamenum;
4008                     char new_piece[MSG_SIZ];
4009                     started = STARTED_NONE;
4010                     parse[parse_pos] = NULLCHAR;
4011                     if (appData.debugMode)
4012                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4013                                                         parse, currentMove);
4014                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4015                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4016                         if (gameInfo.variant == VariantNormal) {
4017                           /* [HGM] We seem to switch variant during a game!
4018                            * Presumably no holdings were displayed, so we have
4019                            * to move the position two files to the right to
4020                            * create room for them!
4021                            */
4022                           VariantClass newVariant;
4023                           switch(gameInfo.boardWidth) { // base guess on board width
4024                                 case 9:  newVariant = VariantShogi; break;
4025                                 case 10: newVariant = VariantGreat; break;
4026                                 default: newVariant = VariantCrazyhouse; break;
4027                           }
4028                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4029                           /* Get a move list just to see the header, which
4030                              will tell us whether this is really bug or zh */
4031                           if (ics_getting_history == H_FALSE) {
4032                             ics_getting_history = H_REQUESTED;
4033                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4034                             SendToICS(str);
4035                           }
4036                         }
4037                         new_piece[0] = NULLCHAR;
4038                         sscanf(parse, "game %d white [%s black [%s <- %s",
4039                                &gamenum, white_holding, black_holding,
4040                                new_piece);
4041                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4042                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4043                         /* [HGM] copy holdings to board holdings area */
4044                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4045                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4046                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4047 #if ZIPPY
4048                         if (appData.zippyPlay && first.initDone) {
4049                             ZippyHoldings(white_holding, black_holding,
4050                                           new_piece);
4051                         }
4052 #endif /*ZIPPY*/
4053                         if (tinyLayout || smallLayout) {
4054                             char wh[16], bh[16];
4055                             PackHolding(wh, white_holding);
4056                             PackHolding(bh, black_holding);
4057                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4058                                     gameInfo.white, gameInfo.black);
4059                         } else {
4060                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4061                                     gameInfo.white, white_holding, _("vs."),
4062                                     gameInfo.black, black_holding);
4063                         }
4064                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4065                         DrawPosition(FALSE, boards[currentMove]);
4066                         DisplayTitle(str);
4067                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4068                         sscanf(parse, "game %d white [%s black [%s <- %s",
4069                                &gamenum, white_holding, black_holding,
4070                                new_piece);
4071                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4072                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4073                         /* [HGM] copy holdings to partner-board holdings area */
4074                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4075                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4076                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4077                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4078                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4079                       }
4080                     }
4081                     /* Suppress following prompt */
4082                     if (looking_at(buf, &i, "*% ")) {
4083                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4084                         savingComment = FALSE;
4085                         suppressKibitz = 0;
4086                     }
4087                     next_out = i;
4088                 }
4089                 continue;
4090             }
4091
4092             i++;                /* skip unparsed character and loop back */
4093         }
4094
4095         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4096 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4097 //          SendToPlayer(&buf[next_out], i - next_out);
4098             started != STARTED_HOLDINGS && leftover_start > next_out) {
4099             SendToPlayer(&buf[next_out], leftover_start - next_out);
4100             next_out = i;
4101         }
4102
4103         leftover_len = buf_len - leftover_start;
4104         /* if buffer ends with something we couldn't parse,
4105            reparse it after appending the next read */
4106
4107     } else if (count == 0) {
4108         RemoveInputSource(isr);
4109         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4110     } else {
4111         DisplayFatalError(_("Error reading from ICS"), error, 1);
4112     }
4113 }
4114
4115
4116 /* Board style 12 looks like this:
4117
4118    <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
4119
4120  * The "<12> " is stripped before it gets to this routine.  The two
4121  * trailing 0's (flip state and clock ticking) are later addition, and
4122  * some chess servers may not have them, or may have only the first.
4123  * Additional trailing fields may be added in the future.
4124  */
4125
4126 #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"
4127
4128 #define RELATION_OBSERVING_PLAYED    0
4129 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4130 #define RELATION_PLAYING_MYMOVE      1
4131 #define RELATION_PLAYING_NOTMYMOVE  -1
4132 #define RELATION_EXAMINING           2
4133 #define RELATION_ISOLATED_BOARD     -3
4134 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4135
4136 void
4137 ParseBoard12 (char *string)
4138 {
4139     GameMode newGameMode;
4140     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4141     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4142     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4143     char to_play, board_chars[200];
4144     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4145     char black[32], white[32];
4146     Board board;
4147     int prevMove = currentMove;
4148     int ticking = 2;
4149     ChessMove moveType;
4150     int fromX, fromY, toX, toY;
4151     char promoChar;
4152     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4153     char *bookHit = NULL; // [HGM] book
4154     Boolean weird = FALSE, reqFlag = FALSE;
4155
4156     fromX = fromY = toX = toY = -1;
4157
4158     newGame = FALSE;
4159
4160     if (appData.debugMode)
4161       fprintf(debugFP, _("Parsing board: %s\n"), string);
4162
4163     move_str[0] = NULLCHAR;
4164     elapsed_time[0] = NULLCHAR;
4165     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4166         int  i = 0, j;
4167         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4168             if(string[i] == ' ') { ranks++; files = 0; }
4169             else files++;
4170             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4171             i++;
4172         }
4173         for(j = 0; j <i; j++) board_chars[j] = string[j];
4174         board_chars[i] = '\0';
4175         string += i + 1;
4176     }
4177     n = sscanf(string, PATTERN, &to_play, &double_push,
4178                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4179                &gamenum, white, black, &relation, &basetime, &increment,
4180                &white_stren, &black_stren, &white_time, &black_time,
4181                &moveNum, str, elapsed_time, move_str, &ics_flip,
4182                &ticking);
4183
4184     if (n < 21) {
4185         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4186         DisplayError(str, 0);
4187         return;
4188     }
4189
4190     /* Convert the move number to internal form */
4191     moveNum = (moveNum - 1) * 2;
4192     if (to_play == 'B') moveNum++;
4193     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4194       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4195                         0, 1);
4196       return;
4197     }
4198
4199     switch (relation) {
4200       case RELATION_OBSERVING_PLAYED:
4201       case RELATION_OBSERVING_STATIC:
4202         if (gamenum == -1) {
4203             /* Old ICC buglet */
4204             relation = RELATION_OBSERVING_STATIC;
4205         }
4206         newGameMode = IcsObserving;
4207         break;
4208       case RELATION_PLAYING_MYMOVE:
4209       case RELATION_PLAYING_NOTMYMOVE:
4210         newGameMode =
4211           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4212             IcsPlayingWhite : IcsPlayingBlack;
4213         break;
4214       case RELATION_EXAMINING:
4215         newGameMode = IcsExamining;
4216         break;
4217       case RELATION_ISOLATED_BOARD:
4218       default:
4219         /* Just display this board.  If user was doing something else,
4220            we will forget about it until the next board comes. */
4221         newGameMode = IcsIdle;
4222         break;
4223       case RELATION_STARTING_POSITION:
4224         newGameMode = gameMode;
4225         break;
4226     }
4227
4228     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4229          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4230       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4231       char *toSqr;
4232       for (k = 0; k < ranks; k++) {
4233         for (j = 0; j < files; j++)
4234           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4235         if(gameInfo.holdingsWidth > 1) {
4236              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4237              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4238         }
4239       }
4240       CopyBoard(partnerBoard, board);
4241       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4242         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4243         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4244       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4245       if(toSqr = strchr(str, '-')) {
4246         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4247         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4248       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4249       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4250       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4251       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4252       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4253       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4254                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4255       DisplayMessage(partnerStatus, "");
4256         partnerBoardValid = TRUE;
4257       return;
4258     }
4259
4260     /* Modify behavior for initial board display on move listing
4261        of wild games.
4262        */
4263     switch (ics_getting_history) {
4264       case H_FALSE:
4265       case H_REQUESTED:
4266         break;
4267       case H_GOT_REQ_HEADER:
4268       case H_GOT_UNREQ_HEADER:
4269         /* This is the initial position of the current game */
4270         gamenum = ics_gamenum;
4271         moveNum = 0;            /* old ICS bug workaround */
4272         if (to_play == 'B') {
4273           startedFromSetupPosition = TRUE;
4274           blackPlaysFirst = TRUE;
4275           moveNum = 1;
4276           if (forwardMostMove == 0) forwardMostMove = 1;
4277           if (backwardMostMove == 0) backwardMostMove = 1;
4278           if (currentMove == 0) currentMove = 1;
4279         }
4280         newGameMode = gameMode;
4281         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4282         break;
4283       case H_GOT_UNWANTED_HEADER:
4284         /* This is an initial board that we don't want */
4285         return;
4286       case H_GETTING_MOVES:
4287         /* Should not happen */
4288         DisplayError(_("Error gathering move list: extra board"), 0);
4289         ics_getting_history = H_FALSE;
4290         return;
4291     }
4292
4293    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4294                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4295      /* [HGM] We seem to have switched variant unexpectedly
4296       * Try to guess new variant from board size
4297       */
4298           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4299           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4300           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4301           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4302           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4303           if(!weird) newVariant = VariantNormal;
4304           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4305           /* Get a move list just to see the header, which
4306              will tell us whether this is really bug or zh */
4307           if (ics_getting_history == H_FALSE) {
4308             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4309             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4310             SendToICS(str);
4311           }
4312     }
4313
4314     /* Take action if this is the first board of a new game, or of a
4315        different game than is currently being displayed.  */
4316     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4317         relation == RELATION_ISOLATED_BOARD) {
4318
4319         /* Forget the old game and get the history (if any) of the new one */
4320         if (gameMode != BeginningOfGame) {
4321           Reset(TRUE, TRUE);
4322         }
4323         newGame = TRUE;
4324         if (appData.autoRaiseBoard) BoardToTop();
4325         prevMove = -3;
4326         if (gamenum == -1) {
4327             newGameMode = IcsIdle;
4328         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4329                    appData.getMoveList && !reqFlag) {
4330             /* Need to get game history */
4331             ics_getting_history = H_REQUESTED;
4332             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4333             SendToICS(str);
4334         }
4335
4336         /* Initially flip the board to have black on the bottom if playing
4337            black or if the ICS flip flag is set, but let the user change
4338            it with the Flip View button. */
4339         flipView = appData.autoFlipView ?
4340           (newGameMode == IcsPlayingBlack) || ics_flip :
4341           appData.flipView;
4342
4343         /* Done with values from previous mode; copy in new ones */
4344         gameMode = newGameMode;
4345         ModeHighlight();
4346         ics_gamenum = gamenum;
4347         if (gamenum == gs_gamenum) {
4348             int klen = strlen(gs_kind);
4349             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4350             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4351             gameInfo.event = StrSave(str);
4352         } else {
4353             gameInfo.event = StrSave("ICS game");
4354         }
4355         gameInfo.site = StrSave(appData.icsHost);
4356         gameInfo.date = PGNDate();
4357         gameInfo.round = StrSave("-");
4358         gameInfo.white = StrSave(white);
4359         gameInfo.black = StrSave(black);
4360         timeControl = basetime * 60 * 1000;
4361         timeControl_2 = 0;
4362         timeIncrement = increment * 1000;
4363         movesPerSession = 0;
4364         gameInfo.timeControl = TimeControlTagValue();
4365         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4366   if (appData.debugMode) {
4367     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4368     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4369     setbuf(debugFP, NULL);
4370   }
4371
4372         gameInfo.outOfBook = NULL;
4373
4374         /* Do we have the ratings? */
4375         if (strcmp(player1Name, white) == 0 &&
4376             strcmp(player2Name, black) == 0) {
4377             if (appData.debugMode)
4378               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4379                       player1Rating, player2Rating);
4380             gameInfo.whiteRating = player1Rating;
4381             gameInfo.blackRating = player2Rating;
4382         } else if (strcmp(player2Name, white) == 0 &&
4383                    strcmp(player1Name, black) == 0) {
4384             if (appData.debugMode)
4385               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4386                       player2Rating, player1Rating);
4387             gameInfo.whiteRating = player2Rating;
4388             gameInfo.blackRating = player1Rating;
4389         }
4390         player1Name[0] = player2Name[0] = NULLCHAR;
4391
4392         /* Silence shouts if requested */
4393         if (appData.quietPlay &&
4394             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4395             SendToICS(ics_prefix);
4396             SendToICS("set shout 0\n");
4397         }
4398     }
4399
4400     /* Deal with midgame name changes */
4401     if (!newGame) {
4402         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4403             if (gameInfo.white) free(gameInfo.white);
4404             gameInfo.white = StrSave(white);
4405         }
4406         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4407             if (gameInfo.black) free(gameInfo.black);
4408             gameInfo.black = StrSave(black);
4409         }
4410     }
4411
4412     /* Throw away game result if anything actually changes in examine mode */
4413     if (gameMode == IcsExamining && !newGame) {
4414         gameInfo.result = GameUnfinished;
4415         if (gameInfo.resultDetails != NULL) {
4416             free(gameInfo.resultDetails);
4417             gameInfo.resultDetails = NULL;
4418         }
4419     }
4420
4421     /* In pausing && IcsExamining mode, we ignore boards coming
4422        in if they are in a different variation than we are. */
4423     if (pauseExamInvalid) return;
4424     if (pausing && gameMode == IcsExamining) {
4425         if (moveNum <= pauseExamForwardMostMove) {
4426             pauseExamInvalid = TRUE;
4427             forwardMostMove = pauseExamForwardMostMove;
4428             return;
4429         }
4430     }
4431
4432   if (appData.debugMode) {
4433     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4434   }
4435     /* Parse the board */
4436     for (k = 0; k < ranks; k++) {
4437       for (j = 0; j < files; j++)
4438         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4439       if(gameInfo.holdingsWidth > 1) {
4440            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4441            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4442       }
4443     }
4444     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4445       board[5][BOARD_RGHT+1] = WhiteAngel;
4446       board[6][BOARD_RGHT+1] = WhiteMarshall;
4447       board[1][0] = BlackMarshall;
4448       board[2][0] = BlackAngel;
4449       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4450     }
4451     CopyBoard(boards[moveNum], board);
4452     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4453     if (moveNum == 0) {
4454         startedFromSetupPosition =
4455           !CompareBoards(board, initialPosition);
4456         if(startedFromSetupPosition)
4457             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4458     }
4459
4460     /* [HGM] Set castling rights. Take the outermost Rooks,
4461        to make it also work for FRC opening positions. Note that board12
4462        is really defective for later FRC positions, as it has no way to
4463        indicate which Rook can castle if they are on the same side of King.
4464        For the initial position we grant rights to the outermost Rooks,
4465        and remember thos rights, and we then copy them on positions
4466        later in an FRC game. This means WB might not recognize castlings with
4467        Rooks that have moved back to their original position as illegal,
4468        but in ICS mode that is not its job anyway.
4469     */
4470     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4471     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4472
4473         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474             if(board[0][i] == WhiteRook) j = i;
4475         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477             if(board[0][i] == WhiteRook) j = i;
4478         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4480             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4481         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4482         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4483             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4484         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4485
4486         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4487         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4488         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4490         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4491             if(board[BOARD_HEIGHT-1][k] == bKing)
4492                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4493         if(gameInfo.variant == VariantTwoKings) {
4494             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4495             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4496             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4497         }
4498     } else { int r;
4499         r = boards[moveNum][CASTLING][0] = initialRights[0];
4500         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4501         r = boards[moveNum][CASTLING][1] = initialRights[1];
4502         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4503         r = boards[moveNum][CASTLING][3] = initialRights[3];
4504         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4505         r = boards[moveNum][CASTLING][4] = initialRights[4];
4506         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4507         /* wildcastle kludge: always assume King has rights */
4508         r = boards[moveNum][CASTLING][2] = initialRights[2];
4509         r = boards[moveNum][CASTLING][5] = initialRights[5];
4510     }
4511     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4512     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4513
4514
4515     if (ics_getting_history == H_GOT_REQ_HEADER ||
4516         ics_getting_history == H_GOT_UNREQ_HEADER) {
4517         /* This was an initial position from a move list, not
4518            the current position */
4519         return;
4520     }
4521
4522     /* Update currentMove and known move number limits */
4523     newMove = newGame || moveNum > forwardMostMove;
4524
4525     if (newGame) {
4526         forwardMostMove = backwardMostMove = currentMove = moveNum;
4527         if (gameMode == IcsExamining && moveNum == 0) {
4528           /* Workaround for ICS limitation: we are not told the wild
4529              type when starting to examine a game.  But if we ask for
4530              the move list, the move list header will tell us */
4531             ics_getting_history = H_REQUESTED;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534         }
4535     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4536                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4537 #if ZIPPY
4538         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4539         /* [HGM] applied this also to an engine that is silently watching        */
4540         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4541             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4542             gameInfo.variant == currentlyInitializedVariant) {
4543           takeback = forwardMostMove - moveNum;
4544           for (i = 0; i < takeback; i++) {
4545             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4546             SendToProgram("undo\n", &first);
4547           }
4548         }
4549 #endif
4550
4551         forwardMostMove = moveNum;
4552         if (!pausing || currentMove > forwardMostMove)
4553           currentMove = forwardMostMove;
4554     } else {
4555         /* New part of history that is not contiguous with old part */
4556         if (pausing && gameMode == IcsExamining) {
4557             pauseExamInvalid = TRUE;
4558             forwardMostMove = pauseExamForwardMostMove;
4559             return;
4560         }
4561         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4562 #if ZIPPY
4563             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4564                 // [HGM] when we will receive the move list we now request, it will be
4565                 // fed to the engine from the first move on. So if the engine is not
4566                 // in the initial position now, bring it there.
4567                 InitChessProgram(&first, 0);
4568             }
4569 #endif
4570             ics_getting_history = H_REQUESTED;
4571             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4572             SendToICS(str);
4573         }
4574         forwardMostMove = backwardMostMove = currentMove = moveNum;
4575     }
4576
4577     /* Update the clocks */
4578     if (strchr(elapsed_time, '.')) {
4579       /* Time is in ms */
4580       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4581       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4582     } else {
4583       /* Time is in seconds */
4584       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4585       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4586     }
4587
4588
4589 #if ZIPPY
4590     if (appData.zippyPlay && newGame &&
4591         gameMode != IcsObserving && gameMode != IcsIdle &&
4592         gameMode != IcsExamining)
4593       ZippyFirstBoard(moveNum, basetime, increment);
4594 #endif
4595
4596     /* Put the move on the move list, first converting
4597        to canonical algebraic form. */
4598     if (moveNum > 0) {
4599   if (appData.debugMode) {
4600     if (appData.debugMode) { int f = forwardMostMove;
4601         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4602                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4603                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4604     }
4605     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4606     fprintf(debugFP, "moveNum = %d\n", moveNum);
4607     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4608     setbuf(debugFP, NULL);
4609   }
4610         if (moveNum <= backwardMostMove) {
4611             /* We don't know what the board looked like before
4612                this move.  Punt. */
4613           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4614             strcat(parseList[moveNum - 1], " ");
4615             strcat(parseList[moveNum - 1], elapsed_time);
4616             moveList[moveNum - 1][0] = NULLCHAR;
4617         } else if (strcmp(move_str, "none") == 0) {
4618             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4619             /* Again, we don't know what the board looked like;
4620                this is really the start of the game. */
4621             parseList[moveNum - 1][0] = NULLCHAR;
4622             moveList[moveNum - 1][0] = NULLCHAR;
4623             backwardMostMove = moveNum;
4624             startedFromSetupPosition = TRUE;
4625             fromX = fromY = toX = toY = -1;
4626         } else {
4627           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4628           //                 So we parse the long-algebraic move string in stead of the SAN move
4629           int valid; char buf[MSG_SIZ], *prom;
4630
4631           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4632                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4633           // str looks something like "Q/a1-a2"; kill the slash
4634           if(str[1] == '/')
4635             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4636           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4637           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4638                 strcat(buf, prom); // long move lacks promo specification!
4639           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4640                 if(appData.debugMode)
4641                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4642                 safeStrCpy(move_str, buf, MSG_SIZ);
4643           }
4644           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4645                                 &fromX, &fromY, &toX, &toY, &promoChar)
4646                || ParseOneMove(buf, moveNum - 1, &moveType,
4647                                 &fromX, &fromY, &toX, &toY, &promoChar);
4648           // end of long SAN patch
4649           if (valid) {
4650             (void) CoordsToAlgebraic(boards[moveNum - 1],
4651                                      PosFlags(moveNum - 1),
4652                                      fromY, fromX, toY, toX, promoChar,
4653                                      parseList[moveNum-1]);
4654             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4655               case MT_NONE:
4656               case MT_STALEMATE:
4657               default:
4658                 break;
4659               case MT_CHECK:
4660                 if(gameInfo.variant != VariantShogi)
4661                     strcat(parseList[moveNum - 1], "+");
4662                 break;
4663               case MT_CHECKMATE:
4664               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4665                 strcat(parseList[moveNum - 1], "#");
4666                 break;
4667             }
4668             strcat(parseList[moveNum - 1], " ");
4669             strcat(parseList[moveNum - 1], elapsed_time);
4670             /* currentMoveString is set as a side-effect of ParseOneMove */
4671             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4672             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4673             strcat(moveList[moveNum - 1], "\n");
4674
4675             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4676                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4677               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4678                 ChessSquare old, new = boards[moveNum][k][j];
4679                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4680                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4681                   if(old == new) continue;
4682                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4683                   else if(new == WhiteWazir || new == BlackWazir) {
4684                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4685                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4686                       else boards[moveNum][k][j] = old; // preserve type of Gold
4687                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4688                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4689               }
4690           } else {
4691             /* Move from ICS was illegal!?  Punt. */
4692             if (appData.debugMode) {
4693               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4694               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4695             }
4696             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4697             strcat(parseList[moveNum - 1], " ");
4698             strcat(parseList[moveNum - 1], elapsed_time);
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700             fromX = fromY = toX = toY = -1;
4701           }
4702         }
4703   if (appData.debugMode) {
4704     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4705     setbuf(debugFP, NULL);
4706   }
4707
4708 #if ZIPPY
4709         /* Send move to chess program (BEFORE animating it). */
4710         if (appData.zippyPlay && !newGame && newMove &&
4711            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4712
4713             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4714                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4715                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4716                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4717                             move_str);
4718                     DisplayError(str, 0);
4719                 } else {
4720                     if (first.sendTime) {
4721                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4722                     }
4723                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4724                     if (firstMove && !bookHit) {
4725                         firstMove = FALSE;
4726                         if (first.useColors) {
4727                           SendToProgram(gameMode == IcsPlayingWhite ?
4728                                         "white\ngo\n" :
4729                                         "black\ngo\n", &first);
4730                         } else {
4731                           SendToProgram("go\n", &first);
4732                         }
4733                         first.maybeThinking = TRUE;
4734                     }
4735                 }
4736             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4737               if (moveList[moveNum - 1][0] == NULLCHAR) {
4738                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4739                 DisplayError(str, 0);
4740               } else {
4741                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4742                 SendMoveToProgram(moveNum - 1, &first);
4743               }
4744             }
4745         }
4746 #endif
4747     }
4748
4749     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4750         /* If move comes from a remote source, animate it.  If it
4751            isn't remote, it will have already been animated. */
4752         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4753             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4754         }
4755         if (!pausing && appData.highlightLastMove) {
4756             SetHighlights(fromX, fromY, toX, toY);
4757         }
4758     }
4759
4760     /* Start the clocks */
4761     whiteFlag = blackFlag = FALSE;
4762     appData.clockMode = !(basetime == 0 && increment == 0);
4763     if (ticking == 0) {
4764       ics_clock_paused = TRUE;
4765       StopClocks();
4766     } else if (ticking == 1) {
4767       ics_clock_paused = FALSE;
4768     }
4769     if (gameMode == IcsIdle ||
4770         relation == RELATION_OBSERVING_STATIC ||
4771         relation == RELATION_EXAMINING ||
4772         ics_clock_paused)
4773       DisplayBothClocks();
4774     else
4775       StartClocks();
4776
4777     /* Display opponents and material strengths */
4778     if (gameInfo.variant != VariantBughouse &&
4779         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4780         if (tinyLayout || smallLayout) {
4781             if(gameInfo.variant == VariantNormal)
4782               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4783                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4784                     basetime, increment);
4785             else
4786               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4787                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4788                     basetime, increment, (int) gameInfo.variant);
4789         } else {
4790             if(gameInfo.variant == VariantNormal)
4791               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4792                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4793                     basetime, increment);
4794             else
4795               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4796                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4797                     basetime, increment, VariantName(gameInfo.variant));
4798         }
4799         DisplayTitle(str);
4800   if (appData.debugMode) {
4801     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4802   }
4803     }
4804
4805
4806     /* Display the board */
4807     if (!pausing && !appData.noGUI) {
4808
4809       if (appData.premove)
4810           if (!gotPremove ||
4811              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4812              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4813               ClearPremoveHighlights();
4814
4815       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4816         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4817       DrawPosition(j, boards[currentMove]);
4818
4819       DisplayMove(moveNum - 1);
4820       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4821             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4822               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4823         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4824       }
4825     }
4826
4827     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4828 #if ZIPPY
4829     if(bookHit) { // [HGM] book: simulate book reply
4830         static char bookMove[MSG_SIZ]; // a bit generous?
4831
4832         programStats.nodes = programStats.depth = programStats.time =
4833         programStats.score = programStats.got_only_move = 0;
4834         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4835
4836         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4837         strcat(bookMove, bookHit);
4838         HandleMachineMove(bookMove, &first);
4839     }
4840 #endif
4841 }
4842
4843 void
4844 GetMoveListEvent ()
4845 {
4846     char buf[MSG_SIZ];
4847     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4848         ics_getting_history = H_REQUESTED;
4849         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4850         SendToICS(buf);
4851     }
4852 }
4853
4854 void
4855 AnalysisPeriodicEvent (int force)
4856 {
4857     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4858          && !force) || !appData.periodicUpdates)
4859       return;
4860
4861     /* Send . command to Crafty to collect stats */
4862     SendToProgram(".\n", &first);
4863
4864     /* Don't send another until we get a response (this makes
4865        us stop sending to old Crafty's which don't understand
4866        the "." command (sending illegal cmds resets node count & time,
4867        which looks bad)) */
4868     programStats.ok_to_send = 0;
4869 }
4870
4871 void
4872 ics_update_width (int new_width)
4873 {
4874         ics_printf("set width %d\n", new_width);
4875 }
4876
4877 void
4878 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4879 {
4880     char buf[MSG_SIZ];
4881
4882     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4883         // null move in variant where engine does not understand it (for analysis purposes)
4884         SendBoard(cps, moveNum + 1); // send position after move in stead.
4885         return;
4886     }
4887     if (cps->useUsermove) {
4888       SendToProgram("usermove ", cps);
4889     }
4890     if (cps->useSAN) {
4891       char *space;
4892       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4893         int len = space - parseList[moveNum];
4894         memcpy(buf, parseList[moveNum], len);
4895         buf[len++] = '\n';
4896         buf[len] = NULLCHAR;
4897       } else {
4898         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4899       }
4900       SendToProgram(buf, cps);
4901     } else {
4902       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4903         AlphaRank(moveList[moveNum], 4);
4904         SendToProgram(moveList[moveNum], cps);
4905         AlphaRank(moveList[moveNum], 4); // and back
4906       } else
4907       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4908        * the engine. It would be nice to have a better way to identify castle
4909        * moves here. */
4910       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4911                                                                          && cps->useOOCastle) {
4912         int fromX = moveList[moveNum][0] - AAA;
4913         int fromY = moveList[moveNum][1] - ONE;
4914         int toX = moveList[moveNum][2] - AAA;
4915         int toY = moveList[moveNum][3] - ONE;
4916         if((boards[moveNum][fromY][fromX] == WhiteKing
4917             && boards[moveNum][toY][toX] == WhiteRook)
4918            || (boards[moveNum][fromY][fromX] == BlackKing
4919                && boards[moveNum][toY][toX] == BlackRook)) {
4920           if(toX > fromX) SendToProgram("O-O\n", cps);
4921           else SendToProgram("O-O-O\n", cps);
4922         }
4923         else SendToProgram(moveList[moveNum], cps);
4924       } else
4925       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4926         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4927           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4928           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4929                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4930         } else
4931           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4932                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4933         SendToProgram(buf, cps);
4934       }
4935       else SendToProgram(moveList[moveNum], cps);
4936       /* End of additions by Tord */
4937     }
4938
4939     /* [HGM] setting up the opening has brought engine in force mode! */
4940     /*       Send 'go' if we are in a mode where machine should play. */
4941     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4942         (gameMode == TwoMachinesPlay   ||
4943 #if ZIPPY
4944          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4945 #endif
4946          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4947         SendToProgram("go\n", cps);
4948   if (appData.debugMode) {
4949     fprintf(debugFP, "(extra)\n");
4950   }
4951     }
4952     setboardSpoiledMachineBlack = 0;
4953 }
4954
4955 void
4956 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4957 {
4958     char user_move[MSG_SIZ];
4959     char suffix[4];
4960
4961     if(gameInfo.variant == VariantSChess && promoChar) {
4962         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4963         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4964     } else suffix[0] = NULLCHAR;
4965
4966     switch (moveType) {
4967       default:
4968         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4969                 (int)moveType, fromX, fromY, toX, toY);
4970         DisplayError(user_move + strlen("say "), 0);
4971         break;
4972       case WhiteKingSideCastle:
4973       case BlackKingSideCastle:
4974       case WhiteQueenSideCastleWild:
4975       case BlackQueenSideCastleWild:
4976       /* PUSH Fabien */
4977       case WhiteHSideCastleFR:
4978       case BlackHSideCastleFR:
4979       /* POP Fabien */
4980         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4981         break;
4982       case WhiteQueenSideCastle:
4983       case BlackQueenSideCastle:
4984       case WhiteKingSideCastleWild:
4985       case BlackKingSideCastleWild:
4986       /* PUSH Fabien */
4987       case WhiteASideCastleFR:
4988       case BlackASideCastleFR:
4989       /* POP Fabien */
4990         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4991         break;
4992       case WhiteNonPromotion:
4993       case BlackNonPromotion:
4994         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4995         break;
4996       case WhitePromotion:
4997       case BlackPromotion:
4998         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4999           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5000                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001                 PieceToChar(WhiteFerz));
5002         else if(gameInfo.variant == VariantGreat)
5003           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005                 PieceToChar(WhiteMan));
5006         else
5007           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5008                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5009                 promoChar);
5010         break;
5011       case WhiteDrop:
5012       case BlackDrop:
5013       drop:
5014         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5015                  ToUpper(PieceToChar((ChessSquare) fromX)),
5016                  AAA + toX, ONE + toY);
5017         break;
5018       case IllegalMove:  /* could be a variant we don't quite understand */
5019         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5020       case NormalMove:
5021       case WhiteCapturesEnPassant:
5022       case BlackCapturesEnPassant:
5023         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5024                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5025         break;
5026     }
5027     SendToICS(user_move);
5028     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5029         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5030 }
5031
5032 void
5033 UploadGameEvent ()
5034 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5035     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5036     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5037     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5038       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5039       return;
5040     }
5041     if(gameMode != IcsExamining) { // is this ever not the case?
5042         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5043
5044         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5045           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5046         } else { // on FICS we must first go to general examine mode
5047           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5048         }
5049         if(gameInfo.variant != VariantNormal) {
5050             // try figure out wild number, as xboard names are not always valid on ICS
5051             for(i=1; i<=36; i++) {
5052               snprintf(buf, MSG_SIZ, "wild/%d", i);
5053                 if(StringToVariant(buf) == gameInfo.variant) break;
5054             }
5055             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5056             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5057             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5058         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5059         SendToICS(ics_prefix);
5060         SendToICS(buf);
5061         if(startedFromSetupPosition || backwardMostMove != 0) {
5062           fen = PositionToFEN(backwardMostMove, NULL);
5063           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5064             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5065             SendToICS(buf);
5066           } else { // FICS: everything has to set by separate bsetup commands
5067             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5068             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5069             SendToICS(buf);
5070             if(!WhiteOnMove(backwardMostMove)) {
5071                 SendToICS("bsetup tomove black\n");
5072             }
5073             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5074             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5075             SendToICS(buf);
5076             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5077             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5078             SendToICS(buf);
5079             i = boards[backwardMostMove][EP_STATUS];
5080             if(i >= 0) { // set e.p.
5081               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5082                 SendToICS(buf);
5083             }
5084             bsetup++;
5085           }
5086         }
5087       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5088             SendToICS("bsetup done\n"); // switch to normal examining.
5089     }
5090     for(i = backwardMostMove; i<last; i++) {
5091         char buf[20];
5092         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5093         SendToICS(buf);
5094     }
5095     SendToICS(ics_prefix);
5096     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5097 }
5098
5099 void
5100 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5101 {
5102     if (rf == DROP_RANK) {
5103       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5104       sprintf(move, "%c@%c%c\n",
5105                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5106     } else {
5107         if (promoChar == 'x' || promoChar == NULLCHAR) {
5108           sprintf(move, "%c%c%c%c\n",
5109                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5110         } else {
5111             sprintf(move, "%c%c%c%c%c\n",
5112                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5113         }
5114     }
5115 }
5116
5117 void
5118 ProcessICSInitScript (FILE *f)
5119 {
5120     char buf[MSG_SIZ];
5121
5122     while (fgets(buf, MSG_SIZ, f)) {
5123         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5124     }
5125
5126     fclose(f);
5127 }
5128
5129
5130 static int lastX, lastY, selectFlag, dragging;
5131
5132 void
5133 Sweep (int step)
5134 {
5135     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5136     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5137     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5138     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5139     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5140     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5141     do {
5142         promoSweep -= step;
5143         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5144         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5145         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5146         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5147         if(!step) step = -1;
5148     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5149             appData.testLegality && (promoSweep == king ||
5150             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5151     ChangeDragPiece(promoSweep);
5152 }
5153
5154 int
5155 PromoScroll (int x, int y)
5156 {
5157   int step = 0;
5158
5159   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5160   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5161   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5162   if(!step) return FALSE;
5163   lastX = x; lastY = y;
5164   if((promoSweep < BlackPawn) == flipView) step = -step;
5165   if(step > 0) selectFlag = 1;
5166   if(!selectFlag) Sweep(step);
5167   return FALSE;
5168 }
5169
5170 void
5171 NextPiece (int step)
5172 {
5173     ChessSquare piece = boards[currentMove][toY][toX];
5174     do {
5175         pieceSweep -= step;
5176         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5177         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5178         if(!step) step = -1;
5179     } while(PieceToChar(pieceSweep) == '.');
5180     boards[currentMove][toY][toX] = pieceSweep;
5181     DrawPosition(FALSE, boards[currentMove]);
5182     boards[currentMove][toY][toX] = piece;
5183 }
5184 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5185 void
5186 AlphaRank (char *move, int n)
5187 {
5188 //    char *p = move, c; int x, y;
5189
5190     if (appData.debugMode) {
5191         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5192     }
5193
5194     if(move[1]=='*' &&
5195        move[2]>='0' && move[2]<='9' &&
5196        move[3]>='a' && move[3]<='x'    ) {
5197         move[1] = '@';
5198         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5200     } else
5201     if(move[0]>='0' && move[0]<='9' &&
5202        move[1]>='a' && move[1]<='x' &&
5203        move[2]>='0' && move[2]<='9' &&
5204        move[3]>='a' && move[3]<='x'    ) {
5205         /* input move, Shogi -> normal */
5206         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5207         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5208         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5210     } else
5211     if(move[1]=='@' &&
5212        move[3]>='0' && move[3]<='9' &&
5213        move[2]>='a' && move[2]<='x'    ) {
5214         move[1] = '*';
5215         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5216         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5217     } else
5218     if(
5219        move[0]>='a' && move[0]<='x' &&
5220        move[3]>='0' && move[3]<='9' &&
5221        move[2]>='a' && move[2]<='x'    ) {
5222          /* output move, normal -> Shogi */
5223         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5224         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5225         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5226         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5227         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5228     }
5229     if (appData.debugMode) {
5230         fprintf(debugFP, "   out = '%s'\n", move);
5231     }
5232 }
5233
5234 char yy_textstr[8000];
5235
5236 /* Parser for moves from gnuchess, ICS, or user typein box */
5237 Boolean
5238 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5239 {
5240     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5241
5242     switch (*moveType) {
5243       case WhitePromotion:
5244       case BlackPromotion:
5245       case WhiteNonPromotion:
5246       case BlackNonPromotion:
5247       case NormalMove:
5248       case WhiteCapturesEnPassant:
5249       case BlackCapturesEnPassant:
5250       case WhiteKingSideCastle:
5251       case WhiteQueenSideCastle:
5252       case BlackKingSideCastle:
5253       case BlackQueenSideCastle:
5254       case WhiteKingSideCastleWild:
5255       case WhiteQueenSideCastleWild:
5256       case BlackKingSideCastleWild:
5257       case BlackQueenSideCastleWild:
5258       /* Code added by Tord: */
5259       case WhiteHSideCastleFR:
5260       case WhiteASideCastleFR:
5261       case BlackHSideCastleFR:
5262       case BlackASideCastleFR:
5263       /* End of code added by Tord */
5264       case IllegalMove:         /* bug or odd chess variant */
5265         *fromX = currentMoveString[0] - AAA;
5266         *fromY = currentMoveString[1] - ONE;
5267         *toX = currentMoveString[2] - AAA;
5268         *toY = currentMoveString[3] - ONE;
5269         *promoChar = currentMoveString[4];
5270         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5271             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5272     if (appData.debugMode) {
5273         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5274     }
5275             *fromX = *fromY = *toX = *toY = 0;
5276             return FALSE;
5277         }
5278         if (appData.testLegality) {
5279           return (*moveType != IllegalMove);
5280         } else {
5281           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5282                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5283         }
5284
5285       case WhiteDrop:
5286       case BlackDrop:
5287         *fromX = *moveType == WhiteDrop ?
5288           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5289           (int) CharToPiece(ToLower(currentMoveString[0]));
5290         *fromY = DROP_RANK;
5291         *toX = currentMoveString[2] - AAA;
5292         *toY = currentMoveString[3] - ONE;
5293         *promoChar = NULLCHAR;
5294         return TRUE;
5295
5296       case AmbiguousMove:
5297       case ImpossibleMove:
5298       case EndOfFile:
5299       case ElapsedTime:
5300       case Comment:
5301       case PGNTag:
5302       case NAG:
5303       case WhiteWins:
5304       case BlackWins:
5305       case GameIsDrawn:
5306       default:
5307     if (appData.debugMode) {
5308         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5309     }
5310         /* bug? */
5311         *fromX = *fromY = *toX = *toY = 0;
5312         *promoChar = NULLCHAR;
5313         return FALSE;
5314     }
5315 }
5316
5317 Boolean pushed = FALSE;
5318 char *lastParseAttempt;
5319
5320 void
5321 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5322 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5323   int fromX, fromY, toX, toY; char promoChar;
5324   ChessMove moveType;
5325   Boolean valid;
5326   int nr = 0;
5327
5328   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5329     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5330     pushed = TRUE;
5331   }
5332   endPV = forwardMostMove;
5333   do {
5334     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5335     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5336     lastParseAttempt = pv;
5337     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5338     if(!valid && nr == 0 &&
5339        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5340         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5341         // Hande case where played move is different from leading PV move
5342         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5343         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5344         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5345         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5346           endPV += 2; // if position different, keep this
5347           moveList[endPV-1][0] = fromX + AAA;
5348           moveList[endPV-1][1] = fromY + ONE;
5349           moveList[endPV-1][2] = toX + AAA;
5350           moveList[endPV-1][3] = toY + ONE;
5351           parseList[endPV-1][0] = NULLCHAR;
5352           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5353         }
5354       }
5355     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5356     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5357     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5358     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5359         valid++; // allow comments in PV
5360         continue;
5361     }
5362     nr++;
5363     if(endPV+1 > framePtr) break; // no space, truncate
5364     if(!valid) break;
5365     endPV++;
5366     CopyBoard(boards[endPV], boards[endPV-1]);
5367     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5368     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5369     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5370     CoordsToAlgebraic(boards[endPV - 1],
5371                              PosFlags(endPV - 1),
5372                              fromY, fromX, toY, toX, promoChar,
5373                              parseList[endPV - 1]);
5374   } while(valid);
5375   if(atEnd == 2) return; // used hidden, for PV conversion
5376   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5377   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380   DrawPosition(TRUE, boards[currentMove]);
5381 }
5382
5383 int
5384 MultiPV (ChessProgramState *cps)
5385 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5386         int i;
5387         for(i=0; i<cps->nrOptions; i++)
5388             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5389                 return i;
5390         return -1;
5391 }
5392
5393 Boolean
5394 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5395 {
5396         int startPV, multi, lineStart, origIndex = index;
5397         char *p, buf2[MSG_SIZ];
5398
5399         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5400         lastX = x; lastY = y;
5401         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5402         lineStart = startPV = index;
5403         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5404         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5405         index = startPV;
5406         do{ while(buf[index] && buf[index] != '\n') index++;
5407         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5408         buf[index] = 0;
5409         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5410                 int n = first.option[multi].value;
5411                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5412                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5413                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5414                 first.option[multi].value = n;
5415                 *start = *end = 0;
5416                 return FALSE;
5417         }
5418         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5419         *start = startPV; *end = index-1;
5420         return TRUE;
5421 }
5422
5423 char *
5424 PvToSAN (char *pv)
5425 {
5426         static char buf[10*MSG_SIZ];
5427         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5428         *buf = NULLCHAR;
5429         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5430         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5431         for(i = forwardMostMove; i<endPV; i++){
5432             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5433             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5434             k += strlen(buf+k);
5435         }
5436         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5437         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5438         endPV = savedEnd;
5439         return buf;
5440 }
5441
5442 Boolean
5443 LoadPV (int x, int y)
5444 { // called on right mouse click to load PV
5445   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5446   lastX = x; lastY = y;
5447   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5448   return TRUE;
5449 }
5450
5451 void
5452 UnLoadPV ()
5453 {
5454   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5455   if(endPV < 0) return;
5456   if(appData.autoCopyPV) CopyFENToClipboard();
5457   endPV = -1;
5458   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5459         Boolean saveAnimate = appData.animate;
5460         if(pushed) {
5461             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5462                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5463             } else storedGames--; // abandon shelved tail of original game
5464         }
5465         pushed = FALSE;
5466         forwardMostMove = currentMove;
5467         currentMove = oldFMM;
5468         appData.animate = FALSE;
5469         ToNrEvent(forwardMostMove);
5470         appData.animate = saveAnimate;
5471   }
5472   currentMove = forwardMostMove;
5473   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5474   ClearPremoveHighlights();
5475   DrawPosition(TRUE, boards[currentMove]);
5476 }
5477
5478 void
5479 MovePV (int x, int y, int h)
5480 { // step through PV based on mouse coordinates (called on mouse move)
5481   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5482
5483   // we must somehow check if right button is still down (might be released off board!)
5484   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5485   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5486   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5487   if(!step) return;
5488   lastX = x; lastY = y;
5489
5490   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5491   if(endPV < 0) return;
5492   if(y < margin) step = 1; else
5493   if(y > h - margin) step = -1;
5494   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5495   currentMove += step;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(FALSE, boards[currentMove]);
5500 }
5501
5502
5503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5504 // All positions will have equal probability, but the current method will not provide a unique
5505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5506 #define DARK 1
5507 #define LITE 2
5508 #define ANY 3
5509
5510 int squaresLeft[4];
5511 int piecesLeft[(int)BlackPawn];
5512 int seed, nrOfShuffles;
5513
5514 void
5515 GetPositionNumber ()
5516 {       // sets global variable seed
5517         int i;
5518
5519         seed = appData.defaultFrcPosition;
5520         if(seed < 0) { // randomize based on time for negative FRC position numbers
5521                 for(i=0; i<50; i++) seed += random();
5522                 seed = random() ^ random() >> 8 ^ random() << 8;
5523                 if(seed<0) seed = -seed;
5524         }
5525 }
5526
5527 int
5528 put (Board board, int pieceType, int rank, int n, int shade)
5529 // put the piece on the (n-1)-th empty squares of the given shade
5530 {
5531         int i;
5532
5533         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5534                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5535                         board[rank][i] = (ChessSquare) pieceType;
5536                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5537                         squaresLeft[ANY]--;
5538                         piecesLeft[pieceType]--;
5539                         return i;
5540                 }
5541         }
5542         return -1;
5543 }
5544
5545
5546 void
5547 AddOnePiece (Board board, int pieceType, int rank, int shade)
5548 // calculate where the next piece goes, (any empty square), and put it there
5549 {
5550         int i;
5551
5552         i = seed % squaresLeft[shade];
5553         nrOfShuffles *= squaresLeft[shade];
5554         seed /= squaresLeft[shade];
5555         put(board, pieceType, rank, i, shade);
5556 }
5557
5558 void
5559 AddTwoPieces (Board board, int pieceType, int rank)
5560 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5561 {
5562         int i, n=squaresLeft[ANY], j=n-1, k;
5563
5564         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5565         i = seed % k;  // pick one
5566         nrOfShuffles *= k;
5567         seed /= k;
5568         while(i >= j) i -= j--;
5569         j = n - 1 - j; i += j;
5570         put(board, pieceType, rank, j, ANY);
5571         put(board, pieceType, rank, i, ANY);
5572 }
5573
5574 void
5575 SetUpShuffle (Board board, int number)
5576 {
5577         int i, p, first=1;
5578
5579         GetPositionNumber(); nrOfShuffles = 1;
5580
5581         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5582         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5583         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5584
5585         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5586
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5588             p = (int) board[0][i];
5589             if(p < (int) BlackPawn) piecesLeft[p] ++;
5590             board[0][i] = EmptySquare;
5591         }
5592
5593         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5594             // shuffles restricted to allow normal castling put KRR first
5595             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5596                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5598                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5600                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5601             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5602                 put(board, WhiteRook, 0, 0, ANY);
5603             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5604         }
5605
5606         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5607             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5608             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5609                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5610                 while(piecesLeft[p] >= 2) {
5611                     AddOnePiece(board, p, 0, LITE);
5612                     AddOnePiece(board, p, 0, DARK);
5613                 }
5614                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5615             }
5616
5617         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5618             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5619             // but we leave King and Rooks for last, to possibly obey FRC restriction
5620             if(p == (int)WhiteRook) continue;
5621             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5622             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5623         }
5624
5625         // now everything is placed, except perhaps King (Unicorn) and Rooks
5626
5627         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5628             // Last King gets castling rights
5629             while(piecesLeft[(int)WhiteUnicorn]) {
5630                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634             while(piecesLeft[(int)WhiteKing]) {
5635                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5636                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5637             }
5638
5639
5640         } else {
5641             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5642             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5643         }
5644
5645         // Only Rooks can be left; simply place them all
5646         while(piecesLeft[(int)WhiteRook]) {
5647                 i = put(board, WhiteRook, 0, 0, ANY);
5648                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5649                         if(first) {
5650                                 first=0;
5651                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5652                         }
5653                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5654                 }
5655         }
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5657             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5658         }
5659
5660         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5661 }
5662
5663 int
5664 SetCharTable (char *table, const char * map)
5665 /* [HGM] moved here from winboard.c because of its general usefulness */
5666 /*       Basically a safe strcpy that uses the last character as King */
5667 {
5668     int result = FALSE; int NrPieces;
5669
5670     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5671                     && NrPieces >= 12 && !(NrPieces&1)) {
5672         int i; /* [HGM] Accept even length from 12 to 34 */
5673
5674         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5675         for( i=0; i<NrPieces/2-1; i++ ) {
5676             table[i] = map[i];
5677             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5678         }
5679         table[(int) WhiteKing]  = map[NrPieces/2-1];
5680         table[(int) BlackKing]  = map[NrPieces-1];
5681
5682         result = TRUE;
5683     }
5684
5685     return result;
5686 }
5687
5688 void
5689 Prelude (Board board)
5690 {       // [HGM] superchess: random selection of exo-pieces
5691         int i, j, k; ChessSquare p;
5692         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5693
5694         GetPositionNumber(); // use FRC position number
5695
5696         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5697             SetCharTable(pieceToChar, appData.pieceToCharTable);
5698             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5699                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5700         }
5701
5702         j = seed%4;                 seed /= 4;
5703         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5704         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5705         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5706         j = seed%3 + (seed%3 >= j); seed /= 3;
5707         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5710         j = seed%3;                 seed /= 3;
5711         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5712         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5713         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5714         j = seed%2 + (seed%2 >= j); seed /= 2;
5715         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5716         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5717         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5718         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5719         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5720         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5721         put(board, exoPieces[0],    0, 0, ANY);
5722         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5723 }
5724
5725 void
5726 InitPosition (int redraw)
5727 {
5728     ChessSquare (* pieces)[BOARD_FILES];
5729     int i, j, pawnRow, overrule,
5730     oldx = gameInfo.boardWidth,
5731     oldy = gameInfo.boardHeight,
5732     oldh = gameInfo.holdingsWidth;
5733     static int oldv;
5734
5735     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5736
5737     /* [AS] Initialize pv info list [HGM] and game status */
5738     {
5739         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5740             pvInfoList[i].depth = 0;
5741             boards[i][EP_STATUS] = EP_NONE;
5742             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5743         }
5744
5745         initialRulePlies = 0; /* 50-move counter start */
5746
5747         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5748         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5749     }
5750
5751
5752     /* [HGM] logic here is completely changed. In stead of full positions */
5753     /* the initialized data only consist of the two backranks. The switch */
5754     /* selects which one we will use, which is than copied to the Board   */
5755     /* initialPosition, which for the rest is initialized by Pawns and    */
5756     /* empty squares. This initial position is then copied to boards[0],  */
5757     /* possibly after shuffling, so that it remains available.            */
5758
5759     gameInfo.holdingsWidth = 0; /* default board sizes */
5760     gameInfo.boardWidth    = 8;
5761     gameInfo.boardHeight   = 8;
5762     gameInfo.holdingsSize  = 0;
5763     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5764     for(i=0; i<BOARD_FILES-2; i++)
5765       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5766     initialPosition[EP_STATUS] = EP_NONE;
5767     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5768     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5769          SetCharTable(pieceNickName, appData.pieceNickNames);
5770     else SetCharTable(pieceNickName, "............");
5771     pieces = FIDEArray;
5772
5773     switch (gameInfo.variant) {
5774     case VariantFischeRandom:
5775       shuffleOpenings = TRUE;
5776     default:
5777       break;
5778     case VariantShatranj:
5779       pieces = ShatranjArray;
5780       nrCastlingRights = 0;
5781       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5782       break;
5783     case VariantMakruk:
5784       pieces = makrukArray;
5785       nrCastlingRights = 0;
5786       startedFromSetupPosition = TRUE;
5787       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5788       break;
5789     case VariantTwoKings:
5790       pieces = twoKingsArray;
5791       break;
5792     case VariantGrand:
5793       pieces = GrandArray;
5794       nrCastlingRights = 0;
5795       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5796       gameInfo.boardWidth = 10;
5797       gameInfo.boardHeight = 10;
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantCapaRandom:
5801       shuffleOpenings = TRUE;
5802     case VariantCapablanca:
5803       pieces = CapablancaArray;
5804       gameInfo.boardWidth = 10;
5805       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5806       break;
5807     case VariantGothic:
5808       pieces = GothicArray;
5809       gameInfo.boardWidth = 10;
5810       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5811       break;
5812     case VariantSChess:
5813       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5814       gameInfo.holdingsSize = 7;
5815       break;
5816     case VariantJanus:
5817       pieces = JanusArray;
5818       gameInfo.boardWidth = 10;
5819       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5820       nrCastlingRights = 6;
5821         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5822         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5823         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5824         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5825         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5826         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5827       break;
5828     case VariantFalcon:
5829       pieces = FalconArray;
5830       gameInfo.boardWidth = 10;
5831       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5832       break;
5833     case VariantXiangqi:
5834       pieces = XiangqiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 10;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5839       break;
5840     case VariantShogi:
5841       pieces = ShogiArray;
5842       gameInfo.boardWidth  = 9;
5843       gameInfo.boardHeight = 9;
5844       gameInfo.holdingsSize = 7;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5847       break;
5848     case VariantCourier:
5849       pieces = CourierArray;
5850       gameInfo.boardWidth  = 12;
5851       nrCastlingRights = 0;
5852       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5853       break;
5854     case VariantKnightmate:
5855       pieces = KnightmateArray;
5856       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5857       break;
5858     case VariantSpartan:
5859       pieces = SpartanArray;
5860       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5861       break;
5862     case VariantFairy:
5863       pieces = fairyArray;
5864       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5865       break;
5866     case VariantGreat:
5867       pieces = GreatArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5870       gameInfo.holdingsSize = 8;
5871       break;
5872     case VariantSuper:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5875       gameInfo.holdingsSize = 8;
5876       startedFromSetupPosition = TRUE;
5877       break;
5878     case VariantCrazyhouse:
5879     case VariantBughouse:
5880       pieces = FIDEArray;
5881       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5882       gameInfo.holdingsSize = 5;
5883       break;
5884     case VariantWildCastle:
5885       pieces = FIDEArray;
5886       /* !!?shuffle with kings guaranteed to be on d or e file */
5887       shuffleOpenings = 1;
5888       break;
5889     case VariantNoCastle:
5890       pieces = FIDEArray;
5891       nrCastlingRights = 0;
5892       /* !!?unconstrained back-rank shuffle */
5893       shuffleOpenings = 1;
5894       break;
5895     }
5896
5897     overrule = 0;
5898     if(appData.NrFiles >= 0) {
5899         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5900         gameInfo.boardWidth = appData.NrFiles;
5901     }
5902     if(appData.NrRanks >= 0) {
5903         gameInfo.boardHeight = appData.NrRanks;
5904     }
5905     if(appData.holdingsSize >= 0) {
5906         i = appData.holdingsSize;
5907         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5908         gameInfo.holdingsSize = i;
5909     }
5910     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5911     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5912         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5913
5914     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5915     if(pawnRow < 1) pawnRow = 1;
5916     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5917
5918     /* User pieceToChar list overrules defaults */
5919     if(appData.pieceToCharTable != NULL)
5920         SetCharTable(pieceToChar, appData.pieceToCharTable);
5921
5922     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5923
5924         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5925             s = (ChessSquare) 0; /* account holding counts in guard band */
5926         for( i=0; i<BOARD_HEIGHT; i++ )
5927             initialPosition[i][j] = s;
5928
5929         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5930         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5931         initialPosition[pawnRow][j] = WhitePawn;
5932         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5933         if(gameInfo.variant == VariantXiangqi) {
5934             if(j&1) {
5935                 initialPosition[pawnRow][j] =
5936                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5937                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5938                    initialPosition[2][j] = WhiteCannon;
5939                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5940                 }
5941             }
5942         }
5943         if(gameInfo.variant == VariantGrand) {
5944             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5945                initialPosition[0][j] = WhiteRook;
5946                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5947             }
5948         }
5949         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5950     }
5951     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5952
5953             j=BOARD_LEFT+1;
5954             initialPosition[1][j] = WhiteBishop;
5955             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5956             j=BOARD_RGHT-2;
5957             initialPosition[1][j] = WhiteRook;
5958             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5959     }
5960
5961     if( nrCastlingRights == -1) {
5962         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5963         /*       This sets default castling rights from none to normal corners   */
5964         /* Variants with other castling rights must set them themselves above    */
5965         nrCastlingRights = 6;
5966
5967         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5968         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5969         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5970         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5971         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5972         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5973      }
5974
5975      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5976      if(gameInfo.variant == VariantGreat) { // promotion commoners
5977         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5978         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5979         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5980         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5981      }
5982      if( gameInfo.variant == VariantSChess ) {
5983       initialPosition[1][0] = BlackMarshall;
5984       initialPosition[2][0] = BlackAngel;
5985       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5986       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5987       initialPosition[1][1] = initialPosition[2][1] = 
5988       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5989      }
5990   if (appData.debugMode) {
5991     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5992   }
5993     if(shuffleOpenings) {
5994         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5995         startedFromSetupPosition = TRUE;
5996     }
5997     if(startedFromPositionFile) {
5998       /* [HGM] loadPos: use PositionFile for every new game */
5999       CopyBoard(initialPosition, filePosition);
6000       for(i=0; i<nrCastlingRights; i++)
6001           initialRights[i] = filePosition[CASTLING][i];
6002       startedFromSetupPosition = TRUE;
6003     }
6004
6005     CopyBoard(boards[0], initialPosition);
6006
6007     if(oldx != gameInfo.boardWidth ||
6008        oldy != gameInfo.boardHeight ||
6009        oldv != gameInfo.variant ||
6010        oldh != gameInfo.holdingsWidth
6011                                          )
6012             InitDrawingSizes(-2 ,0);
6013
6014     oldv = gameInfo.variant;
6015     if (redraw)
6016       DrawPosition(TRUE, boards[currentMove]);
6017 }
6018
6019 void
6020 SendBoard (ChessProgramState *cps, int moveNum)
6021 {
6022     char message[MSG_SIZ];
6023
6024     if (cps->useSetboard) {
6025       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6026       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6027       SendToProgram(message, cps);
6028       free(fen);
6029
6030     } else {
6031       ChessSquare *bp;
6032       int i, j, left=0, right=BOARD_WIDTH;
6033       /* Kludge to set black to move, avoiding the troublesome and now
6034        * deprecated "black" command.
6035        */
6036       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6037         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6038
6039       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6040
6041       SendToProgram("edit\n", cps);
6042       SendToProgram("#\n", cps);
6043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6044         bp = &boards[moveNum][i][left];
6045         for (j = left; j < right; j++, bp++) {
6046           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6047           if ((int) *bp < (int) BlackPawn) {
6048             if(j == BOARD_RGHT+1)
6049                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6050             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6051             if(message[0] == '+' || message[0] == '~') {
6052               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6053                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6054                         AAA + j, ONE + i);
6055             }
6056             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6057                 message[1] = BOARD_RGHT   - 1 - j + '1';
6058                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6059             }
6060             SendToProgram(message, cps);
6061           }
6062         }
6063       }
6064
6065       SendToProgram("c\n", cps);
6066       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6067         bp = &boards[moveNum][i][left];
6068         for (j = left; j < right; j++, bp++) {
6069           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6070           if (((int) *bp != (int) EmptySquare)
6071               && ((int) *bp >= (int) BlackPawn)) {
6072             if(j == BOARD_LEFT-2)
6073                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6074             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6075                     AAA + j, ONE + i);
6076             if(message[0] == '+' || message[0] == '~') {
6077               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6079                         AAA + j, ONE + i);
6080             }
6081             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082                 message[1] = BOARD_RGHT   - 1 - j + '1';
6083                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6084             }
6085             SendToProgram(message, cps);
6086           }
6087         }
6088       }
6089
6090       SendToProgram(".\n", cps);
6091     }
6092     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6093 }
6094
6095 ChessSquare
6096 DefaultPromoChoice (int white)
6097 {
6098     ChessSquare result;
6099     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6100         result = WhiteFerz; // no choice
6101     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6102         result= WhiteKing; // in Suicide Q is the last thing we want
6103     else if(gameInfo.variant == VariantSpartan)
6104         result = white ? WhiteQueen : WhiteAngel;
6105     else result = WhiteQueen;
6106     if(!white) result = WHITE_TO_BLACK result;
6107     return result;
6108 }
6109
6110 static int autoQueen; // [HGM] oneclick
6111
6112 int
6113 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6114 {
6115     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6116     /* [HGM] add Shogi promotions */
6117     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6118     ChessSquare piece;
6119     ChessMove moveType;
6120     Boolean premove;
6121
6122     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6123     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6124
6125     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6126       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6127         return FALSE;
6128
6129     piece = boards[currentMove][fromY][fromX];
6130     if(gameInfo.variant == VariantShogi) {
6131         promotionZoneSize = BOARD_HEIGHT/3;
6132         highestPromotingPiece = (int)WhiteFerz;
6133     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6134         promotionZoneSize = 3;
6135     }
6136
6137     // Treat Lance as Pawn when it is not representing Amazon
6138     if(gameInfo.variant != VariantSuper) {
6139         if(piece == WhiteLance) piece = WhitePawn; else
6140         if(piece == BlackLance) piece = BlackPawn;
6141     }
6142
6143     // next weed out all moves that do not touch the promotion zone at all
6144     if((int)piece >= BlackPawn) {
6145         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6146              return FALSE;
6147         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6148     } else {
6149         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6150            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6151     }
6152
6153     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6154
6155     // weed out mandatory Shogi promotions
6156     if(gameInfo.variant == VariantShogi) {
6157         if(piece >= BlackPawn) {
6158             if(toY == 0 && piece == BlackPawn ||
6159                toY == 0 && piece == BlackQueen ||
6160                toY <= 1 && piece == BlackKnight) {
6161                 *promoChoice = '+';
6162                 return FALSE;
6163             }
6164         } else {
6165             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6166                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6167                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6168                 *promoChoice = '+';
6169                 return FALSE;
6170             }
6171         }
6172     }
6173
6174     // weed out obviously illegal Pawn moves
6175     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6176         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6177         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6178         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6179         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6180         // note we are not allowed to test for valid (non-)capture, due to premove
6181     }
6182
6183     // we either have a choice what to promote to, or (in Shogi) whether to promote
6184     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6185         *promoChoice = PieceToChar(BlackFerz);  // no choice
6186         return FALSE;
6187     }
6188     // no sense asking what we must promote to if it is going to explode...
6189     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6190         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6191         return FALSE;
6192     }
6193     // give caller the default choice even if we will not make it
6194     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6195     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6196     if(        sweepSelect && gameInfo.variant != VariantGreat
6197                            && gameInfo.variant != VariantGrand
6198                            && gameInfo.variant != VariantSuper) return FALSE;
6199     if(autoQueen) return FALSE; // predetermined
6200
6201     // suppress promotion popup on illegal moves that are not premoves
6202     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6203               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6204     if(appData.testLegality && !premove) {
6205         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6206                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6207         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6208             return FALSE;
6209     }
6210
6211     return TRUE;
6212 }
6213
6214 int
6215 InPalace (int row, int column)
6216 {   /* [HGM] for Xiangqi */
6217     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6218          column < (BOARD_WIDTH + 4)/2 &&
6219          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6220     return FALSE;
6221 }
6222
6223 int
6224 PieceForSquare (int x, int y)
6225 {
6226   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6227      return -1;
6228   else
6229      return boards[currentMove][y][x];
6230 }
6231
6232 int
6233 OKToStartUserMove (int x, int y)
6234 {
6235     ChessSquare from_piece;
6236     int white_piece;
6237
6238     if (matchMode) return FALSE;
6239     if (gameMode == EditPosition) return TRUE;
6240
6241     if (x >= 0 && y >= 0)
6242       from_piece = boards[currentMove][y][x];
6243     else
6244       from_piece = EmptySquare;
6245
6246     if (from_piece == EmptySquare) return FALSE;
6247
6248     white_piece = (int)from_piece >= (int)WhitePawn &&
6249       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6250
6251     switch (gameMode) {
6252       case AnalyzeFile:
6253       case TwoMachinesPlay:
6254       case EndOfGame:
6255         return FALSE;
6256
6257       case IcsObserving:
6258       case IcsIdle:
6259         return FALSE;
6260
6261       case MachinePlaysWhite:
6262       case IcsPlayingBlack:
6263         if (appData.zippyPlay) return FALSE;
6264         if (white_piece) {
6265             DisplayMoveError(_("You are playing Black"));
6266             return FALSE;
6267         }
6268         break;
6269
6270       case MachinePlaysBlack:
6271       case IcsPlayingWhite:
6272         if (appData.zippyPlay) return FALSE;
6273         if (!white_piece) {
6274             DisplayMoveError(_("You are playing White"));
6275             return FALSE;
6276         }
6277         break;
6278
6279       case PlayFromGameFile:
6280             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6281       case EditGame:
6282         if (!white_piece && WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is White's turn"));
6284             return FALSE;
6285         }
6286         if (white_piece && !WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is Black's turn"));
6288             return FALSE;
6289         }
6290         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6291             /* Editing correspondence game history */
6292             /* Could disallow this or prompt for confirmation */
6293             cmailOldMove = -1;
6294         }
6295         break;
6296
6297       case BeginningOfGame:
6298         if (appData.icsActive) return FALSE;
6299         if (!appData.noChessProgram) {
6300             if (!white_piece) {
6301                 DisplayMoveError(_("You are playing White"));
6302                 return FALSE;
6303             }
6304         }
6305         break;
6306
6307       case Training:
6308         if (!white_piece && WhiteOnMove(currentMove)) {
6309             DisplayMoveError(_("It is White's turn"));
6310             return FALSE;
6311         }
6312         if (white_piece && !WhiteOnMove(currentMove)) {
6313             DisplayMoveError(_("It is Black's turn"));
6314             return FALSE;
6315         }
6316         break;
6317
6318       default:
6319       case IcsExamining:
6320         break;
6321     }
6322     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6323         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6324         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6325         && gameMode != AnalyzeFile && gameMode != Training) {
6326         DisplayMoveError(_("Displayed position is not current"));
6327         return FALSE;
6328     }
6329     return TRUE;
6330 }
6331
6332 Boolean
6333 OnlyMove (int *x, int *y, Boolean captures) 
6334 {
6335     DisambiguateClosure cl;
6336     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6337     switch(gameMode) {
6338       case MachinePlaysBlack:
6339       case IcsPlayingWhite:
6340       case BeginningOfGame:
6341         if(!WhiteOnMove(currentMove)) return FALSE;
6342         break;
6343       case MachinePlaysWhite:
6344       case IcsPlayingBlack:
6345         if(WhiteOnMove(currentMove)) return FALSE;
6346         break;
6347       case EditGame:
6348         break;
6349       default:
6350         return FALSE;
6351     }
6352     cl.pieceIn = EmptySquare;
6353     cl.rfIn = *y;
6354     cl.ffIn = *x;
6355     cl.rtIn = -1;
6356     cl.ftIn = -1;
6357     cl.promoCharIn = NULLCHAR;
6358     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6359     if( cl.kind == NormalMove ||
6360         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6361         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6362         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6363       fromX = cl.ff;
6364       fromY = cl.rf;
6365       *x = cl.ft;
6366       *y = cl.rt;
6367       return TRUE;
6368     }
6369     if(cl.kind != ImpossibleMove) return FALSE;
6370     cl.pieceIn = EmptySquare;
6371     cl.rfIn = -1;
6372     cl.ffIn = -1;
6373     cl.rtIn = *y;
6374     cl.ftIn = *x;
6375     cl.promoCharIn = NULLCHAR;
6376     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6377     if( cl.kind == NormalMove ||
6378         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6379         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6380         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6381       fromX = cl.ff;
6382       fromY = cl.rf;
6383       *x = cl.ft;
6384       *y = cl.rt;
6385       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6386       return TRUE;
6387     }
6388     return FALSE;
6389 }
6390
6391 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6392 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6393 int lastLoadGameUseList = FALSE;
6394 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6395 ChessMove lastLoadGameStart = EndOfFile;
6396
6397 void
6398 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6399 {
6400     ChessMove moveType;
6401     ChessSquare pdown, pup;
6402
6403     /* Check if the user is playing in turn.  This is complicated because we
6404        let the user "pick up" a piece before it is his turn.  So the piece he
6405        tried to pick up may have been captured by the time he puts it down!
6406        Therefore we use the color the user is supposed to be playing in this
6407        test, not the color of the piece that is currently on the starting
6408        square---except in EditGame mode, where the user is playing both
6409        sides; fortunately there the capture race can't happen.  (It can
6410        now happen in IcsExamining mode, but that's just too bad.  The user
6411        will get a somewhat confusing message in that case.)
6412        */
6413
6414     switch (gameMode) {
6415       case AnalyzeFile:
6416       case TwoMachinesPlay:
6417       case EndOfGame:
6418       case IcsObserving:
6419       case IcsIdle:
6420         /* We switched into a game mode where moves are not accepted,
6421            perhaps while the mouse button was down. */
6422         return;
6423
6424       case MachinePlaysWhite:
6425         /* User is moving for Black */
6426         if (WhiteOnMove(currentMove)) {
6427             DisplayMoveError(_("It is White's turn"));
6428             return;
6429         }
6430         break;
6431
6432       case MachinePlaysBlack:
6433         /* User is moving for White */
6434         if (!WhiteOnMove(currentMove)) {
6435             DisplayMoveError(_("It is Black's turn"));
6436             return;
6437         }
6438         break;
6439
6440       case PlayFromGameFile:
6441             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6442       case EditGame:
6443       case IcsExamining:
6444       case BeginningOfGame:
6445       case AnalyzeMode:
6446       case Training:
6447         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6448         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6449             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6450             /* User is moving for Black */
6451             if (WhiteOnMove(currentMove)) {
6452                 DisplayMoveError(_("It is White's turn"));
6453                 return;
6454             }
6455         } else {
6456             /* User is moving for White */
6457             if (!WhiteOnMove(currentMove)) {
6458                 DisplayMoveError(_("It is Black's turn"));
6459                 return;
6460             }
6461         }
6462         break;
6463
6464       case IcsPlayingBlack:
6465         /* User is moving for Black */
6466         if (WhiteOnMove(currentMove)) {
6467             if (!appData.premove) {
6468                 DisplayMoveError(_("It is White's turn"));
6469             } else if (toX >= 0 && toY >= 0) {
6470                 premoveToX = toX;
6471                 premoveToY = toY;
6472                 premoveFromX = fromX;
6473                 premoveFromY = fromY;
6474                 premovePromoChar = promoChar;
6475                 gotPremove = 1;
6476                 if (appData.debugMode)
6477                     fprintf(debugFP, "Got premove: fromX %d,"
6478                             "fromY %d, toX %d, toY %d\n",
6479                             fromX, fromY, toX, toY);
6480             }
6481             return;
6482         }
6483         break;
6484
6485       case IcsPlayingWhite:
6486         /* User is moving for White */
6487         if (!WhiteOnMove(currentMove)) {
6488             if (!appData.premove) {
6489                 DisplayMoveError(_("It is Black's turn"));
6490             } else if (toX >= 0 && toY >= 0) {
6491                 premoveToX = toX;
6492                 premoveToY = toY;
6493                 premoveFromX = fromX;
6494                 premoveFromY = fromY;
6495                 premovePromoChar = promoChar;
6496                 gotPremove = 1;
6497                 if (appData.debugMode)
6498                     fprintf(debugFP, "Got premove: fromX %d,"
6499                             "fromY %d, toX %d, toY %d\n",
6500                             fromX, fromY, toX, toY);
6501             }
6502             return;
6503         }
6504         break;
6505
6506       default:
6507         break;
6508
6509       case EditPosition:
6510         /* EditPosition, empty square, or different color piece;
6511            click-click move is possible */
6512         if (toX == -2 || toY == -2) {
6513             boards[0][fromY][fromX] = EmptySquare;
6514             DrawPosition(FALSE, boards[currentMove]);
6515             return;
6516         } else if (toX >= 0 && toY >= 0) {
6517             boards[0][toY][toX] = boards[0][fromY][fromX];
6518             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6519                 if(boards[0][fromY][0] != EmptySquare) {
6520                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6521                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6522                 }
6523             } else
6524             if(fromX == BOARD_RGHT+1) {
6525                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6526                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6527                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6528                 }
6529             } else
6530             boards[0][fromY][fromX] = EmptySquare;
6531             DrawPosition(FALSE, boards[currentMove]);
6532             return;
6533         }
6534         return;
6535     }
6536
6537     if(toX < 0 || toY < 0) return;
6538     pdown = boards[currentMove][fromY][fromX];
6539     pup = boards[currentMove][toY][toX];
6540
6541     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6542     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6543          if( pup != EmptySquare ) return;
6544          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6545            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6546                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6547            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6548            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6549            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6550            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6551          fromY = DROP_RANK;
6552     }
6553
6554     /* [HGM] always test for legality, to get promotion info */
6555     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6556                                          fromY, fromX, toY, toX, promoChar);
6557
6558     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6559
6560     /* [HGM] but possibly ignore an IllegalMove result */
6561     if (appData.testLegality) {
6562         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6563             DisplayMoveError(_("Illegal move"));
6564             return;
6565         }
6566     }
6567
6568     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6569 }
6570
6571 /* Common tail of UserMoveEvent and DropMenuEvent */
6572 int
6573 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6574 {
6575     char *bookHit = 0;
6576
6577     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6578         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6579         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6580         if(WhiteOnMove(currentMove)) {
6581             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6582         } else {
6583             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6584         }
6585     }
6586
6587     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6588        move type in caller when we know the move is a legal promotion */
6589     if(moveType == NormalMove && promoChar)
6590         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6591
6592     /* [HGM] <popupFix> The following if has been moved here from
6593        UserMoveEvent(). Because it seemed to belong here (why not allow
6594        piece drops in training games?), and because it can only be
6595        performed after it is known to what we promote. */
6596     if (gameMode == Training) {
6597       /* compare the move played on the board to the next move in the
6598        * game. If they match, display the move and the opponent's response.
6599        * If they don't match, display an error message.
6600        */
6601       int saveAnimate;
6602       Board testBoard;
6603       CopyBoard(testBoard, boards[currentMove]);
6604       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6605
6606       if (CompareBoards(testBoard, boards[currentMove+1])) {
6607         ForwardInner(currentMove+1);
6608
6609         /* Autoplay the opponent's response.
6610          * if appData.animate was TRUE when Training mode was entered,
6611          * the response will be animated.
6612          */
6613         saveAnimate = appData.animate;
6614         appData.animate = animateTraining;
6615         ForwardInner(currentMove+1);
6616         appData.animate = saveAnimate;
6617
6618         /* check for the end of the game */
6619         if (currentMove >= forwardMostMove) {
6620           gameMode = PlayFromGameFile;
6621           ModeHighlight();
6622           SetTrainingModeOff();
6623           DisplayInformation(_("End of game"));
6624         }
6625       } else {
6626         DisplayError(_("Incorrect move"), 0);
6627       }
6628       return 1;
6629     }
6630
6631   /* Ok, now we know that the move is good, so we can kill
6632      the previous line in Analysis Mode */
6633   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6634                                 && currentMove < forwardMostMove) {
6635     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6636     else forwardMostMove = currentMove;
6637   }
6638
6639   /* If we need the chess program but it's dead, restart it */
6640   ResurrectChessProgram();
6641
6642   /* A user move restarts a paused game*/
6643   if (pausing)
6644     PauseEvent();
6645
6646   thinkOutput[0] = NULLCHAR;
6647
6648   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6649
6650   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6651     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6652     return 1;
6653   }
6654
6655   if (gameMode == BeginningOfGame) {
6656     if (appData.noChessProgram) {
6657       gameMode = EditGame;
6658       SetGameInfo();
6659     } else {
6660       char buf[MSG_SIZ];
6661       gameMode = MachinePlaysBlack;
6662       StartClocks();
6663       SetGameInfo();
6664       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6665       DisplayTitle(buf);
6666       if (first.sendName) {
6667         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6668         SendToProgram(buf, &first);
6669       }
6670       StartClocks();
6671     }
6672     ModeHighlight();
6673   }
6674
6675   /* Relay move to ICS or chess engine */
6676   if (appData.icsActive) {
6677     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6678         gameMode == IcsExamining) {
6679       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6680         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6681         SendToICS("draw ");
6682         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       }
6684       // also send plain move, in case ICS does not understand atomic claims
6685       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6686       ics_user_moved = 1;
6687     }
6688   } else {
6689     if (first.sendTime && (gameMode == BeginningOfGame ||
6690                            gameMode == MachinePlaysWhite ||
6691                            gameMode == MachinePlaysBlack)) {
6692       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6693     }
6694     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6695          // [HGM] book: if program might be playing, let it use book
6696         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6697         first.maybeThinking = TRUE;
6698     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6699         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6700         SendBoard(&first, currentMove+1);
6701     } else SendMoveToProgram(forwardMostMove-1, &first);
6702     if (currentMove == cmailOldMove + 1) {
6703       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6704     }
6705   }
6706
6707   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6708
6709   switch (gameMode) {
6710   case EditGame:
6711     if(appData.testLegality)
6712     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6713     case MT_NONE:
6714     case MT_CHECK:
6715       break;
6716     case MT_CHECKMATE:
6717     case MT_STAINMATE:
6718       if (WhiteOnMove(currentMove)) {
6719         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6720       } else {
6721         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6722       }
6723       break;
6724     case MT_STALEMATE:
6725       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6726       break;
6727     }
6728     break;
6729
6730   case MachinePlaysBlack:
6731   case MachinePlaysWhite:
6732     /* disable certain menu options while machine is thinking */
6733     SetMachineThinkingEnables();
6734     break;
6735
6736   default:
6737     break;
6738   }
6739
6740   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6741   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6742
6743   if(bookHit) { // [HGM] book: simulate book reply
6744         static char bookMove[MSG_SIZ]; // a bit generous?
6745
6746         programStats.nodes = programStats.depth = programStats.time =
6747         programStats.score = programStats.got_only_move = 0;
6748         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6749
6750         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6751         strcat(bookMove, bookHit);
6752         HandleMachineMove(bookMove, &first);
6753   }
6754   return 1;
6755 }
6756
6757 void
6758 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6759 {
6760     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6761     Markers *m = (Markers *) closure;
6762     if(rf == fromY && ff == fromX)
6763         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6764                          || kind == WhiteCapturesEnPassant
6765                          || kind == BlackCapturesEnPassant);
6766     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6767 }
6768
6769 void
6770 MarkTargetSquares (int clear)
6771 {
6772   int x, y;
6773   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6774      !appData.testLegality || gameMode == EditPosition) return;
6775   if(clear) {
6776     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6777   } else {
6778     int capt = 0;
6779     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6780     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6782       if(capt)
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6784     }
6785   }
6786   DrawPosition(TRUE, NULL);
6787 }
6788
6789 int
6790 Explode (Board board, int fromX, int fromY, int toX, int toY)
6791 {
6792     if(gameInfo.variant == VariantAtomic &&
6793        (board[toY][toX] != EmptySquare ||                     // capture?
6794         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6795                          board[fromY][fromX] == BlackPawn   )
6796       )) {
6797         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6798         return TRUE;
6799     }
6800     return FALSE;
6801 }
6802
6803 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6804
6805 int
6806 CanPromote (ChessSquare piece, int y)
6807 {
6808         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6809         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6810         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6811            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6812            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6813                                                   gameInfo.variant == VariantMakruk) return FALSE;
6814         return (piece == BlackPawn && y == 1 ||
6815                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6816                 piece == BlackLance && y == 1 ||
6817                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6818 }
6819
6820 void
6821 LeftClick (ClickType clickType, int xPix, int yPix)
6822 {
6823     int x, y;
6824     Boolean saveAnimate;
6825     static int second = 0, promotionChoice = 0, clearFlag = 0;
6826     char promoChoice = NULLCHAR;
6827     ChessSquare piece;
6828
6829     if(appData.seekGraph && appData.icsActive && loggedOn &&
6830         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6831         SeekGraphClick(clickType, xPix, yPix, 0);
6832         return;
6833     }
6834
6835     if (clickType == Press) ErrorPopDown();
6836
6837     x = EventToSquare(xPix, BOARD_WIDTH);
6838     y = EventToSquare(yPix, BOARD_HEIGHT);
6839     if (!flipView && y >= 0) {
6840         y = BOARD_HEIGHT - 1 - y;
6841     }
6842     if (flipView && x >= 0) {
6843         x = BOARD_WIDTH - 1 - x;
6844     }
6845
6846     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6847         defaultPromoChoice = promoSweep;
6848         promoSweep = EmptySquare;   // terminate sweep
6849         promoDefaultAltered = TRUE;
6850         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6851     }
6852
6853     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6854         if(clickType == Release) return; // ignore upclick of click-click destination
6855         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6856         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6857         if(gameInfo.holdingsWidth &&
6858                 (WhiteOnMove(currentMove)
6859                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6860                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6861             // click in right holdings, for determining promotion piece
6862             ChessSquare p = boards[currentMove][y][x];
6863             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6864             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6865             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6866                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6867                 fromX = fromY = -1;
6868                 return;
6869             }
6870         }
6871         DrawPosition(FALSE, boards[currentMove]);
6872         return;
6873     }
6874
6875     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6876     if(clickType == Press
6877             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6878               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6879               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6880         return;
6881
6882     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6883         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6884
6885     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6886         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6887                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6888         defaultPromoChoice = DefaultPromoChoice(side);
6889     }
6890
6891     autoQueen = appData.alwaysPromoteToQueen;
6892
6893     if (fromX == -1) {
6894       int originalY = y;
6895       gatingPiece = EmptySquare;
6896       if (clickType != Press) {
6897         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6898             DragPieceEnd(xPix, yPix); dragging = 0;
6899             DrawPosition(FALSE, NULL);
6900         }
6901         return;
6902       }
6903       fromX = x; fromY = y; toX = toY = -1;
6904       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6905          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6906          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6907             /* First square */
6908             if (OKToStartUserMove(fromX, fromY)) {
6909                 second = 0;
6910                 MarkTargetSquares(0);
6911                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6912                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6913                     promoSweep = defaultPromoChoice;
6914                     selectFlag = 0; lastX = xPix; lastY = yPix;
6915                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6916                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6917                 }
6918                 if (appData.highlightDragging) {
6919                     SetHighlights(fromX, fromY, -1, -1);
6920                 }
6921             } else fromX = fromY = -1;
6922             return;
6923         }
6924     }
6925
6926     /* fromX != -1 */
6927     if (clickType == Press && gameMode != EditPosition) {
6928         ChessSquare fromP;
6929         ChessSquare toP;
6930         int frc;
6931
6932         // ignore off-board to clicks
6933         if(y < 0 || x < 0) return;
6934
6935         /* Check if clicking again on the same color piece */
6936         fromP = boards[currentMove][fromY][fromX];
6937         toP = boards[currentMove][y][x];
6938         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6939         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6940              WhitePawn <= toP && toP <= WhiteKing &&
6941              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6942              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6943             (BlackPawn <= fromP && fromP <= BlackKing &&
6944              BlackPawn <= toP && toP <= BlackKing &&
6945              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6946              !(fromP == BlackKing && toP == BlackRook && frc))) {
6947             /* Clicked again on same color piece -- changed his mind */
6948             second = (x == fromX && y == fromY);
6949             promoDefaultAltered = FALSE;
6950             MarkTargetSquares(1);
6951            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6952             if (appData.highlightDragging) {
6953                 SetHighlights(x, y, -1, -1);
6954             } else {
6955                 ClearHighlights();
6956             }
6957             if (OKToStartUserMove(x, y)) {
6958                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6959                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6960                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6961                  gatingPiece = boards[currentMove][fromY][fromX];
6962                 else gatingPiece = EmptySquare;
6963                 fromX = x;
6964                 fromY = y; dragging = 1;
6965                 MarkTargetSquares(0);
6966                 DragPieceBegin(xPix, yPix, FALSE);
6967                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6968                     promoSweep = defaultPromoChoice;
6969                     selectFlag = 0; lastX = xPix; lastY = yPix;
6970                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6971                 }
6972             }
6973            }
6974            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6975            second = FALSE; 
6976         }
6977         // ignore clicks on holdings
6978         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6979     }
6980
6981     if (clickType == Release && x == fromX && y == fromY) {
6982         DragPieceEnd(xPix, yPix); dragging = 0;
6983         if(clearFlag) {
6984             // a deferred attempt to click-click move an empty square on top of a piece
6985             boards[currentMove][y][x] = EmptySquare;
6986             ClearHighlights();
6987             DrawPosition(FALSE, boards[currentMove]);
6988             fromX = fromY = -1; clearFlag = 0;
6989             return;
6990         }
6991         if (appData.animateDragging) {
6992             /* Undo animation damage if any */
6993             DrawPosition(FALSE, NULL);
6994         }
6995         if (second) {
6996             /* Second up/down in same square; just abort move */
6997             second = 0;
6998             fromX = fromY = -1;
6999             gatingPiece = EmptySquare;
7000             ClearHighlights();
7001             gotPremove = 0;
7002             ClearPremoveHighlights();
7003         } else {
7004             /* First upclick in same square; start click-click mode */
7005             SetHighlights(x, y, -1, -1);
7006         }
7007         return;
7008     }
7009
7010     clearFlag = 0;
7011
7012     /* we now have a different from- and (possibly off-board) to-square */
7013     /* Completed move */
7014     toX = x;
7015     toY = y;
7016     saveAnimate = appData.animate;
7017     MarkTargetSquares(1);
7018     if (clickType == Press) {
7019         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7020             // must be Edit Position mode with empty-square selected
7021             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7022             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7023             return;
7024         }
7025         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7026             ChessSquare piece = boards[currentMove][fromY][fromX];
7027             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7028             promoSweep = defaultPromoChoice;
7029             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7030             selectFlag = 0; lastX = xPix; lastY = yPix;
7031             Sweep(0); // Pawn that is going to promote: preview promotion piece
7032             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7033             DrawPosition(FALSE, boards[currentMove]);
7034             return;
7035         }
7036         /* Finish clickclick move */
7037         if (appData.animate || appData.highlightLastMove) {
7038             SetHighlights(fromX, fromY, toX, toY);
7039         } else {
7040             ClearHighlights();
7041         }
7042     } else {
7043         /* Finish drag move */
7044         if (appData.highlightLastMove) {
7045             SetHighlights(fromX, fromY, toX, toY);
7046         } else {
7047             ClearHighlights();
7048         }
7049         DragPieceEnd(xPix, yPix); dragging = 0;
7050         /* Don't animate move and drag both */
7051         appData.animate = FALSE;
7052     }
7053
7054     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7055     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7056         ChessSquare piece = boards[currentMove][fromY][fromX];
7057         if(gameMode == EditPosition && piece != EmptySquare &&
7058            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7059             int n;
7060
7061             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7062                 n = PieceToNumber(piece - (int)BlackPawn);
7063                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7065                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7066             } else
7067             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7068                 n = PieceToNumber(piece);
7069                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7070                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7071                 boards[currentMove][n][BOARD_WIDTH-2]++;
7072             }
7073             boards[currentMove][fromY][fromX] = EmptySquare;
7074         }
7075         ClearHighlights();
7076         fromX = fromY = -1;
7077         DrawPosition(TRUE, boards[currentMove]);
7078         return;
7079     }
7080
7081     // off-board moves should not be highlighted
7082     if(x < 0 || y < 0) ClearHighlights();
7083
7084     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7085
7086     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7087         SetHighlights(fromX, fromY, toX, toY);
7088         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7089             // [HGM] super: promotion to captured piece selected from holdings
7090             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7091             promotionChoice = TRUE;
7092             // kludge follows to temporarily execute move on display, without promoting yet
7093             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7094             boards[currentMove][toY][toX] = p;
7095             DrawPosition(FALSE, boards[currentMove]);
7096             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7097             boards[currentMove][toY][toX] = q;
7098             DisplayMessage("Click in holdings to choose piece", "");
7099             return;
7100         }
7101         PromotionPopUp();
7102     } else {
7103         int oldMove = currentMove;
7104         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7105         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7106         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7107         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7108            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7109             DrawPosition(TRUE, boards[currentMove]);
7110         fromX = fromY = -1;
7111     }
7112     appData.animate = saveAnimate;
7113     if (appData.animate || appData.animateDragging) {
7114         /* Undo animation damage if needed */
7115         DrawPosition(FALSE, NULL);
7116     }
7117 }
7118
7119 int
7120 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7121 {   // front-end-free part taken out of PieceMenuPopup
7122     int whichMenu; int xSqr, ySqr;
7123
7124     if(seekGraphUp) { // [HGM] seekgraph
7125         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7126         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7127         return -2;
7128     }
7129
7130     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7131          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7132         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7133         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7134         if(action == Press)   {
7135             originalFlip = flipView;
7136             flipView = !flipView; // temporarily flip board to see game from partners perspective
7137             DrawPosition(TRUE, partnerBoard);
7138             DisplayMessage(partnerStatus, "");
7139             partnerUp = TRUE;
7140         } else if(action == Release) {
7141             flipView = originalFlip;
7142             DrawPosition(TRUE, boards[currentMove]);
7143             partnerUp = FALSE;
7144         }
7145         return -2;
7146     }
7147
7148     xSqr = EventToSquare(x, BOARD_WIDTH);
7149     ySqr = EventToSquare(y, BOARD_HEIGHT);
7150     if (action == Release) {
7151         if(pieceSweep != EmptySquare) {
7152             EditPositionMenuEvent(pieceSweep, toX, toY);
7153             pieceSweep = EmptySquare;
7154         } else UnLoadPV(); // [HGM] pv
7155     }
7156     if (action != Press) return -2; // return code to be ignored
7157     switch (gameMode) {
7158       case IcsExamining:
7159         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7160       case EditPosition:
7161         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7162         if (xSqr < 0 || ySqr < 0) return -1;
7163         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7164         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7165         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7166         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7167         NextPiece(0);
7168         return 2; // grab
7169       case IcsObserving:
7170         if(!appData.icsEngineAnalyze) return -1;
7171       case IcsPlayingWhite:
7172       case IcsPlayingBlack:
7173         if(!appData.zippyPlay) goto noZip;
7174       case AnalyzeMode:
7175       case AnalyzeFile:
7176       case MachinePlaysWhite:
7177       case MachinePlaysBlack:
7178       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7179         if (!appData.dropMenu) {
7180           LoadPV(x, y);
7181           return 2; // flag front-end to grab mouse events
7182         }
7183         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7184            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7185       case EditGame:
7186       noZip:
7187         if (xSqr < 0 || ySqr < 0) return -1;
7188         if (!appData.dropMenu || appData.testLegality &&
7189             gameInfo.variant != VariantBughouse &&
7190             gameInfo.variant != VariantCrazyhouse) return -1;
7191         whichMenu = 1; // drop menu
7192         break;
7193       default:
7194         return -1;
7195     }
7196
7197     if (((*fromX = xSqr) < 0) ||
7198         ((*fromY = ySqr) < 0)) {
7199         *fromX = *fromY = -1;
7200         return -1;
7201     }
7202     if (flipView)
7203       *fromX = BOARD_WIDTH - 1 - *fromX;
7204     else
7205       *fromY = BOARD_HEIGHT - 1 - *fromY;
7206
7207     return whichMenu;
7208 }
7209
7210 void
7211 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7212 {
7213 //    char * hint = lastHint;
7214     FrontEndProgramStats stats;
7215
7216     stats.which = cps == &first ? 0 : 1;
7217     stats.depth = cpstats->depth;
7218     stats.nodes = cpstats->nodes;
7219     stats.score = cpstats->score;
7220     stats.time = cpstats->time;
7221     stats.pv = cpstats->movelist;
7222     stats.hint = lastHint;
7223     stats.an_move_index = 0;
7224     stats.an_move_count = 0;
7225
7226     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7227         stats.hint = cpstats->move_name;
7228         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7229         stats.an_move_count = cpstats->nr_moves;
7230     }
7231
7232     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
7233
7234     SetProgramStats( &stats );
7235 }
7236
7237 void
7238 ClearEngineOutputPane (int which)
7239 {
7240     static FrontEndProgramStats dummyStats;
7241     dummyStats.which = which;
7242     dummyStats.pv = "#";
7243     SetProgramStats( &dummyStats );
7244 }
7245
7246 #define MAXPLAYERS 500
7247
7248 char *
7249 TourneyStandings (int display)
7250 {
7251     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7252     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7253     char result, *p, *names[MAXPLAYERS];
7254
7255     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7256         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7257     names[0] = p = strdup(appData.participants);
7258     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7259
7260     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7261
7262     while(result = appData.results[nr]) {
7263         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7264         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7265         wScore = bScore = 0;
7266         switch(result) {
7267           case '+': wScore = 2; break;
7268           case '-': bScore = 2; break;
7269           case '=': wScore = bScore = 1; break;
7270           case ' ':
7271           case '*': return strdup("busy"); // tourney not finished
7272         }
7273         score[w] += wScore;
7274         score[b] += bScore;
7275         games[w]++;
7276         games[b]++;
7277         nr++;
7278     }
7279     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7280     for(w=0; w<nPlayers; w++) {
7281         bScore = -1;
7282         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7283         ranking[w] = b; points[w] = bScore; score[b] = -2;
7284     }
7285     p = malloc(nPlayers*34+1);
7286     for(w=0; w<nPlayers && w<display; w++)
7287         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7288     free(names[0]);
7289     return p;
7290 }
7291
7292 void
7293 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7294 {       // count all piece types
7295         int p, f, r;
7296         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7297         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7298         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7299                 p = board[r][f];
7300                 pCnt[p]++;
7301                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7302                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7303                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7304                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7305                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7306                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7307         }
7308 }
7309
7310 int
7311 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7312 {
7313         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7314         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7315
7316         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7317         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7318         if(myPawns == 2 && nMine == 3) // KPP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7320         if(myPawns == 1 && nMine == 2) // KP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7322         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7323             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7324         if(myPawns) return FALSE;
7325         if(pCnt[WhiteRook+side])
7326             return pCnt[BlackRook-side] ||
7327                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7328                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7329                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7330         if(pCnt[WhiteCannon+side]) {
7331             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7332             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7333         }
7334         if(pCnt[WhiteKnight+side])
7335             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7336         return FALSE;
7337 }
7338
7339 int
7340 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7341 {
7342         VariantClass v = gameInfo.variant;
7343
7344         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7345         if(v == VariantShatranj) return TRUE; // always winnable through baring
7346         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7347         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7348
7349         if(v == VariantXiangqi) {
7350                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7351
7352                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7353                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7354                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7355                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7356                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7357                 if(stale) // we have at least one last-rank P plus perhaps C
7358                     return majors // KPKX
7359                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7360                 else // KCA*E*
7361                     return pCnt[WhiteFerz+side] // KCAK
7362                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7363                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7364                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7365
7366         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7367                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7368
7369                 if(nMine == 1) return FALSE; // bare King
7370                 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
7371                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7372                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7373                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7374                 if(pCnt[WhiteKnight+side])
7375                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7376                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7377                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7378                 if(nBishops)
7379                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7380                 if(pCnt[WhiteAlfil+side])
7381                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7382                 if(pCnt[WhiteWazir+side])
7383                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7384         }
7385
7386         return TRUE;
7387 }
7388
7389 int
7390 CompareWithRights (Board b1, Board b2)
7391 {
7392     int rights = 0;
7393     if(!CompareBoards(b1, b2)) return FALSE;
7394     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7395     /* compare castling rights */
7396     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7397            rights++; /* King lost rights, while rook still had them */
7398     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7399         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7400            rights++; /* but at least one rook lost them */
7401     }
7402     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7403            rights++;
7404     if( b1[CASTLING][5] != NoRights ) {
7405         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7406            rights++;
7407     }
7408     return rights == 0;
7409 }
7410
7411 int
7412 Adjudicate (ChessProgramState *cps)
7413 {       // [HGM] some adjudications useful with buggy engines
7414         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7415         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7416         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7417         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7418         int k, count = 0; static int bare = 1;
7419         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7420         Boolean canAdjudicate = !appData.icsActive;
7421
7422         // most tests only when we understand the game, i.e. legality-checking on
7423             if( appData.testLegality )
7424             {   /* [HGM] Some more adjudications for obstinate engines */
7425                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7426                 static int moveCount = 6;
7427                 ChessMove result;
7428                 char *reason = NULL;
7429
7430                 /* Count what is on board. */
7431                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7432
7433                 /* Some material-based adjudications that have to be made before stalemate test */
7434                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7435                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7436                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7437                      if(canAdjudicate && appData.checkMates) {
7438                          if(engineOpponent)
7439                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7440                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7441                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7442                          return 1;
7443                      }
7444                 }
7445
7446                 /* Bare King in Shatranj (loses) or Losers (wins) */
7447                 if( nrW == 1 || nrB == 1) {
7448                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7449                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7450                      if(canAdjudicate && appData.checkMates) {
7451                          if(engineOpponent)
7452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7453                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7454                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7455                          return 1;
7456                      }
7457                   } else
7458                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7459                   {    /* bare King */
7460                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7461                         if(canAdjudicate && appData.checkMates) {
7462                             /* but only adjudicate if adjudication enabled */
7463                             if(engineOpponent)
7464                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7465                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7466                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7467                             return 1;
7468                         }
7469                   }
7470                 } else bare = 1;
7471
7472
7473             // don't wait for engine to announce game end if we can judge ourselves
7474             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7475               case MT_CHECK:
7476                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7477                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7478                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7479                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7480                             checkCnt++;
7481                         if(checkCnt >= 2) {
7482                             reason = "Xboard adjudication: 3rd check";
7483                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7484                             break;
7485                         }
7486                     }
7487                 }
7488               case MT_NONE:
7489               default:
7490                 break;
7491               case MT_STALEMATE:
7492               case MT_STAINMATE:
7493                 reason = "Xboard adjudication: Stalemate";
7494                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7495                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7496                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7497                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7498                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7499                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7500                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7501                                                                         EP_CHECKMATE : EP_WINS);
7502                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7503                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7504                 }
7505                 break;
7506               case MT_CHECKMATE:
7507                 reason = "Xboard adjudication: Checkmate";
7508                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7509                 break;
7510             }
7511
7512                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7513                     case EP_STALEMATE:
7514                         result = GameIsDrawn; break;
7515                     case EP_CHECKMATE:
7516                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7517                     case EP_WINS:
7518                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7519                     default:
7520                         result = EndOfFile;
7521                 }
7522                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7523                     if(engineOpponent)
7524                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7525                     GameEnds( result, reason, GE_XBOARD );
7526                     return 1;
7527                 }
7528
7529                 /* Next absolutely insufficient mating material. */
7530                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7531                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7532                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7533
7534                      /* always flag draws, for judging claims */
7535                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7536
7537                      if(canAdjudicate && appData.materialDraws) {
7538                          /* but only adjudicate them if adjudication enabled */
7539                          if(engineOpponent) {
7540                            SendToProgram("force\n", engineOpponent); // suppress reply
7541                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7542                          }
7543                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7544                          return 1;
7545                      }
7546                 }
7547
7548                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7549                 if(gameInfo.variant == VariantXiangqi ?
7550                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7551                  : nrW + nrB == 4 &&
7552                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7553                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7554                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7555                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7556                    ) ) {
7557                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7558                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7559                           if(engineOpponent) {
7560                             SendToProgram("force\n", engineOpponent); // suppress reply
7561                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7562                           }
7563                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7564                           return 1;
7565                      }
7566                 } else moveCount = 6;
7567             }
7568
7569         // Repetition draws and 50-move rule can be applied independently of legality testing
7570
7571                 /* Check for rep-draws */
7572                 count = 0;
7573                 for(k = forwardMostMove-2;
7574                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7575                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7576                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7577                     k-=2)
7578                 {   int rights=0;
7579                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7580                         /* compare castling rights */
7581                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7582                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7583                                 rights++; /* King lost rights, while rook still had them */
7584                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7585                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7586                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7587                                    rights++; /* but at least one rook lost them */
7588                         }
7589                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7590                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7591                                 rights++;
7592                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7593                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7594                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7595                                    rights++;
7596                         }
7597                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7598                             && appData.drawRepeats > 1) {
7599                              /* adjudicate after user-specified nr of repeats */
7600                              int result = GameIsDrawn;
7601                              char *details = "XBoard adjudication: repetition draw";
7602                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7603                                 // [HGM] xiangqi: check for forbidden perpetuals
7604                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7605                                 for(m=forwardMostMove; m>k; m-=2) {
7606                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7607                                         ourPerpetual = 0; // the current mover did not always check
7608                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7609                                         hisPerpetual = 0; // the opponent did not always check
7610                                 }
7611                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7612                                                                         ourPerpetual, hisPerpetual);
7613                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7614                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7615                                     details = "Xboard adjudication: perpetual checking";
7616                                 } else
7617                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7618                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7619                                 } else
7620                                 // Now check for perpetual chases
7621                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7622                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7623                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7624                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7625                                         static char resdet[MSG_SIZ];
7626                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7627                                         details = resdet;
7628                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7629                                     } else
7630                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7631                                         break; // Abort repetition-checking loop.
7632                                 }
7633                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7634                              }
7635                              if(engineOpponent) {
7636                                SendToProgram("force\n", engineOpponent); // suppress reply
7637                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7638                              }
7639                              GameEnds( result, details, GE_XBOARD );
7640                              return 1;
7641                         }
7642                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7643                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7644                     }
7645                 }
7646
7647                 /* Now we test for 50-move draws. Determine ply count */
7648                 count = forwardMostMove;
7649                 /* look for last irreversble move */
7650                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7651                     count--;
7652                 /* if we hit starting position, add initial plies */
7653                 if( count == backwardMostMove )
7654                     count -= initialRulePlies;
7655                 count = forwardMostMove - count;
7656                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7657                         // adjust reversible move counter for checks in Xiangqi
7658                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7659                         if(i < backwardMostMove) i = backwardMostMove;
7660                         while(i <= forwardMostMove) {
7661                                 lastCheck = inCheck; // check evasion does not count
7662                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7663                                 if(inCheck || lastCheck) count--; // check does not count
7664                                 i++;
7665                         }
7666                 }
7667                 if( count >= 100)
7668                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7669                          /* this is used to judge if draw claims are legal */
7670                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7671                          if(engineOpponent) {
7672                            SendToProgram("force\n", engineOpponent); // suppress reply
7673                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                          }
7675                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7676                          return 1;
7677                 }
7678
7679                 /* if draw offer is pending, treat it as a draw claim
7680                  * when draw condition present, to allow engines a way to
7681                  * claim draws before making their move to avoid a race
7682                  * condition occurring after their move
7683                  */
7684                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7685                          char *p = NULL;
7686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7687                              p = "Draw claim: 50-move rule";
7688                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7689                              p = "Draw claim: 3-fold repetition";
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7691                              p = "Draw claim: insufficient mating material";
7692                          if( p != NULL && canAdjudicate) {
7693                              if(engineOpponent) {
7694                                SendToProgram("force\n", engineOpponent); // suppress reply
7695                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7696                              }
7697                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7698                              return 1;
7699                          }
7700                 }
7701
7702                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7703                     if(engineOpponent) {
7704                       SendToProgram("force\n", engineOpponent); // suppress reply
7705                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7706                     }
7707                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7708                     return 1;
7709                 }
7710         return 0;
7711 }
7712
7713 char *
7714 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7715 {   // [HGM] book: this routine intercepts moves to simulate book replies
7716     char *bookHit = NULL;
7717
7718     //first determine if the incoming move brings opponent into his book
7719     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7720         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7721     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7722     if(bookHit != NULL && !cps->bookSuspend) {
7723         // make sure opponent is not going to reply after receiving move to book position
7724         SendToProgram("force\n", cps);
7725         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7726     }
7727     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7728     // now arrange restart after book miss
7729     if(bookHit) {
7730         // after a book hit we never send 'go', and the code after the call to this routine
7731         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7732         char buf[MSG_SIZ], *move = bookHit;
7733         if(cps->useSAN) {
7734             int fromX, fromY, toX, toY;
7735             char promoChar;
7736             ChessMove moveType;
7737             move = buf + 30;
7738             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7739                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7740                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7741                                     PosFlags(forwardMostMove),
7742                                     fromY, fromX, toY, toX, promoChar, move);
7743             } else {
7744                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7745                 bookHit = NULL;
7746             }
7747         }
7748         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7749         SendToProgram(buf, cps);
7750         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7751     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7752         SendToProgram("go\n", cps);
7753         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7754     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7755         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7756             SendToProgram("go\n", cps);
7757         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7758     }
7759     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7760 }
7761
7762 char *savedMessage;
7763 ChessProgramState *savedState;
7764 void
7765 DeferredBookMove (void)
7766 {
7767         if(savedState->lastPing != savedState->lastPong)
7768                     ScheduleDelayedEvent(DeferredBookMove, 10);
7769         else
7770         HandleMachineMove(savedMessage, savedState);
7771 }
7772
7773 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7774
7775 void
7776 HandleMachineMove (char *message, ChessProgramState *cps)
7777 {
7778     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7779     char realname[MSG_SIZ];
7780     int fromX, fromY, toX, toY;
7781     ChessMove moveType;
7782     char promoChar;
7783     char *p, *pv=buf1;
7784     int machineWhite;
7785     char *bookHit;
7786
7787     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7788         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7789         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7790             DisplayError(_("Invalid pairing from pairing engine"), 0);
7791             return;
7792         }
7793         pairingReceived = 1;
7794         NextMatchGame();
7795         return; // Skim the pairing messages here.
7796     }
7797
7798     cps->userError = 0;
7799
7800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7801     /*
7802      * Kludge to ignore BEL characters
7803      */
7804     while (*message == '\007') message++;
7805
7806     /*
7807      * [HGM] engine debug message: ignore lines starting with '#' character
7808      */
7809     if(cps->debug && *message == '#') return;
7810
7811     /*
7812      * Look for book output
7813      */
7814     if (cps == &first && bookRequested) {
7815         if (message[0] == '\t' || message[0] == ' ') {
7816             /* Part of the book output is here; append it */
7817             strcat(bookOutput, message);
7818             strcat(bookOutput, "  \n");
7819             return;
7820         } else if (bookOutput[0] != NULLCHAR) {
7821             /* All of book output has arrived; display it */
7822             char *p = bookOutput;
7823             while (*p != NULLCHAR) {
7824                 if (*p == '\t') *p = ' ';
7825                 p++;
7826             }
7827             DisplayInformation(bookOutput);
7828             bookRequested = FALSE;
7829             /* Fall through to parse the current output */
7830         }
7831     }
7832
7833     /*
7834      * Look for machine move.
7835      */
7836     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7837         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7838     {
7839         /* This method is only useful on engines that support ping */
7840         if (cps->lastPing != cps->lastPong) {
7841           if (gameMode == BeginningOfGame) {
7842             /* Extra move from before last new; ignore */
7843             if (appData.debugMode) {
7844                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7845             }
7846           } else {
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7849                         cps->which, gameMode);
7850             }
7851
7852             SendToProgram("undo\n", cps);
7853           }
7854           return;
7855         }
7856
7857         switch (gameMode) {
7858           case BeginningOfGame:
7859             /* Extra move from before last reset; ignore */
7860             if (appData.debugMode) {
7861                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7862             }
7863             return;
7864
7865           case EndOfGame:
7866           case IcsIdle:
7867           default:
7868             /* Extra move after we tried to stop.  The mode test is
7869                not a reliable way of detecting this problem, but it's
7870                the best we can do on engines that don't support ping.
7871             */
7872             if (appData.debugMode) {
7873                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7874                         cps->which, gameMode);
7875             }
7876             SendToProgram("undo\n", cps);
7877             return;
7878
7879           case MachinePlaysWhite:
7880           case IcsPlayingWhite:
7881             machineWhite = TRUE;
7882             break;
7883
7884           case MachinePlaysBlack:
7885           case IcsPlayingBlack:
7886             machineWhite = FALSE;
7887             break;
7888
7889           case TwoMachinesPlay:
7890             machineWhite = (cps->twoMachinesColor[0] == 'w');
7891             break;
7892         }
7893         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7894             if (appData.debugMode) {
7895                 fprintf(debugFP,
7896                         "Ignoring move out of turn by %s, gameMode %d"
7897                         ", forwardMost %d\n",
7898                         cps->which, gameMode, forwardMostMove);
7899             }
7900             return;
7901         }
7902
7903         if(cps->alphaRank) AlphaRank(machineMove, 4);
7904         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7905                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7906             /* Machine move could not be parsed; ignore it. */
7907           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7908                     machineMove, _(cps->which));
7909             DisplayError(buf1, 0);
7910             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7911                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7912             if (gameMode == TwoMachinesPlay) {
7913               GameEnds(machineWhite ? BlackWins : WhiteWins,
7914                        buf1, GE_XBOARD);
7915             }
7916             return;
7917         }
7918
7919         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7920         /* So we have to redo legality test with true e.p. status here,  */
7921         /* to make sure an illegal e.p. capture does not slip through,   */
7922         /* to cause a forfeit on a justified illegal-move complaint      */
7923         /* of the opponent.                                              */
7924         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7925            ChessMove moveType;
7926            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7927                              fromY, fromX, toY, toX, promoChar);
7928             if(moveType == IllegalMove) {
7929               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7930                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7931                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7932                            buf1, GE_XBOARD);
7933                 return;
7934            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7935            /* [HGM] Kludge to handle engines that send FRC-style castling
7936               when they shouldn't (like TSCP-Gothic) */
7937            switch(moveType) {
7938              case WhiteASideCastleFR:
7939              case BlackASideCastleFR:
7940                toX+=2;
7941                currentMoveString[2]++;
7942                break;
7943              case WhiteHSideCastleFR:
7944              case BlackHSideCastleFR:
7945                toX--;
7946                currentMoveString[2]--;
7947                break;
7948              default: ; // nothing to do, but suppresses warning of pedantic compilers
7949            }
7950         }
7951         hintRequested = FALSE;
7952         lastHint[0] = NULLCHAR;
7953         bookRequested = FALSE;
7954         /* Program may be pondering now */
7955         cps->maybeThinking = TRUE;
7956         if (cps->sendTime == 2) cps->sendTime = 1;
7957         if (cps->offeredDraw) cps->offeredDraw--;
7958
7959         /* [AS] Save move info*/
7960         pvInfoList[ forwardMostMove ].score = programStats.score;
7961         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7962         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7963
7964         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7965
7966         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7967         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7968             int count = 0;
7969
7970             while( count < adjudicateLossPlies ) {
7971                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7972
7973                 if( count & 1 ) {
7974                     score = -score; /* Flip score for winning side */
7975                 }
7976
7977                 if( score > adjudicateLossThreshold ) {
7978                     break;
7979                 }
7980
7981                 count++;
7982             }
7983
7984             if( count >= adjudicateLossPlies ) {
7985                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7986
7987                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7988                     "Xboard adjudication",
7989                     GE_XBOARD );
7990
7991                 return;
7992             }
7993         }
7994
7995         if(Adjudicate(cps)) {
7996             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7997             return; // [HGM] adjudicate: for all automatic game ends
7998         }
7999
8000 #if ZIPPY
8001         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8002             first.initDone) {
8003           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8004                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8005                 SendToICS("draw ");
8006                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8007           }
8008           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8009           ics_user_moved = 1;
8010           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8011                 char buf[3*MSG_SIZ];
8012
8013                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8014                         programStats.score / 100.,
8015                         programStats.depth,
8016                         programStats.time / 100.,
8017                         (unsigned int)programStats.nodes,
8018                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8019                         programStats.movelist);
8020                 SendToICS(buf);
8021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8022           }
8023         }
8024 #endif
8025
8026         /* [AS] Clear stats for next move */
8027         ClearProgramStats();
8028         thinkOutput[0] = NULLCHAR;
8029         hiddenThinkOutputState = 0;
8030
8031         bookHit = NULL;
8032         if (gameMode == TwoMachinesPlay) {
8033             /* [HGM] relaying draw offers moved to after reception of move */
8034             /* and interpreting offer as claim if it brings draw condition */
8035             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8036                 SendToProgram("draw\n", cps->other);
8037             }
8038             if (cps->other->sendTime) {
8039                 SendTimeRemaining(cps->other,
8040                                   cps->other->twoMachinesColor[0] == 'w');
8041             }
8042             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8043             if (firstMove && !bookHit) {
8044                 firstMove = FALSE;
8045                 if (cps->other->useColors) {
8046                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8047                 }
8048                 SendToProgram("go\n", cps->other);
8049             }
8050             cps->other->maybeThinking = TRUE;
8051         }
8052
8053         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8054
8055         if (!pausing && appData.ringBellAfterMoves) {
8056             RingBell();
8057         }
8058
8059         /*
8060          * Reenable menu items that were disabled while
8061          * machine was thinking
8062          */
8063         if (gameMode != TwoMachinesPlay)
8064             SetUserThinkingEnables();
8065
8066         // [HGM] book: after book hit opponent has received move and is now in force mode
8067         // force the book reply into it, and then fake that it outputted this move by jumping
8068         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8069         if(bookHit) {
8070                 static char bookMove[MSG_SIZ]; // a bit generous?
8071
8072                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8073                 strcat(bookMove, bookHit);
8074                 message = bookMove;
8075                 cps = cps->other;
8076                 programStats.nodes = programStats.depth = programStats.time =
8077                 programStats.score = programStats.got_only_move = 0;
8078                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8079
8080                 if(cps->lastPing != cps->lastPong) {
8081                     savedMessage = message; // args for deferred call
8082                     savedState = cps;
8083                     ScheduleDelayedEvent(DeferredBookMove, 10);
8084                     return;
8085                 }
8086                 goto FakeBookMove;
8087         }
8088
8089         return;
8090     }
8091
8092     /* Set special modes for chess engines.  Later something general
8093      *  could be added here; for now there is just one kludge feature,
8094      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8095      *  when "xboard" is given as an interactive command.
8096      */
8097     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8098         cps->useSigint = FALSE;
8099         cps->useSigterm = FALSE;
8100     }
8101     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8102       ParseFeatures(message+8, cps);
8103       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8104     }
8105
8106     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8107                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8108       int dummy, s=6; char buf[MSG_SIZ];
8109       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8110       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8111       if(startedFromSetupPosition) return;
8112       ParseFEN(boards[0], &dummy, message+s);
8113       DrawPosition(TRUE, boards[0]);
8114       startedFromSetupPosition = TRUE;
8115       return;
8116     }
8117     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8118      * want this, I was asked to put it in, and obliged.
8119      */
8120     if (!strncmp(message, "setboard ", 9)) {
8121         Board initial_position;
8122
8123         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8124
8125         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8126             DisplayError(_("Bad FEN received from engine"), 0);
8127             return ;
8128         } else {
8129            Reset(TRUE, FALSE);
8130            CopyBoard(boards[0], initial_position);
8131            initialRulePlies = FENrulePlies;
8132            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8133            else gameMode = MachinePlaysBlack;
8134            DrawPosition(FALSE, boards[currentMove]);
8135         }
8136         return;
8137     }
8138
8139     /*
8140      * Look for communication commands
8141      */
8142     if (!strncmp(message, "telluser ", 9)) {
8143         if(message[9] == '\\' && message[10] == '\\')
8144             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8145         PlayTellSound();
8146         DisplayNote(message + 9);
8147         return;
8148     }
8149     if (!strncmp(message, "tellusererror ", 14)) {
8150         cps->userError = 1;
8151         if(message[14] == '\\' && message[15] == '\\')
8152             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8153         PlayTellSound();
8154         DisplayError(message + 14, 0);
8155         return;
8156     }
8157     if (!strncmp(message, "tellopponent ", 13)) {
8158       if (appData.icsActive) {
8159         if (loggedOn) {
8160           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8161           SendToICS(buf1);
8162         }
8163       } else {
8164         DisplayNote(message + 13);
8165       }
8166       return;
8167     }
8168     if (!strncmp(message, "tellothers ", 11)) {
8169       if (appData.icsActive) {
8170         if (loggedOn) {
8171           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8172           SendToICS(buf1);
8173         }
8174       }
8175       return;
8176     }
8177     if (!strncmp(message, "tellall ", 8)) {
8178       if (appData.icsActive) {
8179         if (loggedOn) {
8180           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8181           SendToICS(buf1);
8182         }
8183       } else {
8184         DisplayNote(message + 8);
8185       }
8186       return;
8187     }
8188     if (strncmp(message, "warning", 7) == 0) {
8189         /* Undocumented feature, use tellusererror in new code */
8190         DisplayError(message, 0);
8191         return;
8192     }
8193     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8194         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8195         strcat(realname, " query");
8196         AskQuestion(realname, buf2, buf1, cps->pr);
8197         return;
8198     }
8199     /* Commands from the engine directly to ICS.  We don't allow these to be
8200      *  sent until we are logged on. Crafty kibitzes have been known to
8201      *  interfere with the login process.
8202      */
8203     if (loggedOn) {
8204         if (!strncmp(message, "tellics ", 8)) {
8205             SendToICS(message + 8);
8206             SendToICS("\n");
8207             return;
8208         }
8209         if (!strncmp(message, "tellicsnoalias ", 15)) {
8210             SendToICS(ics_prefix);
8211             SendToICS(message + 15);
8212             SendToICS("\n");
8213             return;
8214         }
8215         /* The following are for backward compatibility only */
8216         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8217             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8218             SendToICS(ics_prefix);
8219             SendToICS(message);
8220             SendToICS("\n");
8221             return;
8222         }
8223     }
8224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8225         return;
8226     }
8227     /*
8228      * If the move is illegal, cancel it and redraw the board.
8229      * Also deal with other error cases.  Matching is rather loose
8230      * here to accommodate engines written before the spec.
8231      */
8232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8233         strncmp(message, "Error", 5) == 0) {
8234         if (StrStr(message, "name") ||
8235             StrStr(message, "rating") || StrStr(message, "?") ||
8236             StrStr(message, "result") || StrStr(message, "board") ||
8237             StrStr(message, "bk") || StrStr(message, "computer") ||
8238             StrStr(message, "variant") || StrStr(message, "hint") ||
8239             StrStr(message, "random") || StrStr(message, "depth") ||
8240             StrStr(message, "accepted")) {
8241             return;
8242         }
8243         if (StrStr(message, "protover")) {
8244           /* Program is responding to input, so it's apparently done
8245              initializing, and this error message indicates it is
8246              protocol version 1.  So we don't need to wait any longer
8247              for it to initialize and send feature commands. */
8248           FeatureDone(cps, 1);
8249           cps->protocolVersion = 1;
8250           return;
8251         }
8252         cps->maybeThinking = FALSE;
8253
8254         if (StrStr(message, "draw")) {
8255             /* Program doesn't have "draw" command */
8256             cps->sendDrawOffers = 0;
8257             return;
8258         }
8259         if (cps->sendTime != 1 &&
8260             (StrStr(message, "time") || StrStr(message, "otim"))) {
8261           /* Program apparently doesn't have "time" or "otim" command */
8262           cps->sendTime = 0;
8263           return;
8264         }
8265         if (StrStr(message, "analyze")) {
8266             cps->analysisSupport = FALSE;
8267             cps->analyzing = FALSE;
8268 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8269             EditGameEvent(); // [HGM] try to preserve loaded game
8270             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8271             DisplayError(buf2, 0);
8272             return;
8273         }
8274         if (StrStr(message, "(no matching move)st")) {
8275           /* Special kludge for GNU Chess 4 only */
8276           cps->stKludge = TRUE;
8277           SendTimeControl(cps, movesPerSession, timeControl,
8278                           timeIncrement, appData.searchDepth,
8279                           searchTime);
8280           return;
8281         }
8282         if (StrStr(message, "(no matching move)sd")) {
8283           /* Special kludge for GNU Chess 4 only */
8284           cps->sdKludge = TRUE;
8285           SendTimeControl(cps, movesPerSession, timeControl,
8286                           timeIncrement, appData.searchDepth,
8287                           searchTime);
8288           return;
8289         }
8290         if (!StrStr(message, "llegal")) {
8291             return;
8292         }
8293         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8294             gameMode == IcsIdle) return;
8295         if (forwardMostMove <= backwardMostMove) return;
8296         if (pausing) PauseEvent();
8297       if(appData.forceIllegal) {
8298             // [HGM] illegal: machine refused move; force position after move into it
8299           SendToProgram("force\n", cps);
8300           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8301                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8302                 // when black is to move, while there might be nothing on a2 or black
8303                 // might already have the move. So send the board as if white has the move.
8304                 // But first we must change the stm of the engine, as it refused the last move
8305                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8306                 if(WhiteOnMove(forwardMostMove)) {
8307                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8308                     SendBoard(cps, forwardMostMove); // kludgeless board
8309                 } else {
8310                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8311                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8312                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8313                 }
8314           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8315             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8316                  gameMode == TwoMachinesPlay)
8317               SendToProgram("go\n", cps);
8318             return;
8319       } else
8320         if (gameMode == PlayFromGameFile) {
8321             /* Stop reading this game file */
8322             gameMode = EditGame;
8323             ModeHighlight();
8324         }
8325         /* [HGM] illegal-move claim should forfeit game when Xboard */
8326         /* only passes fully legal moves                            */
8327         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8328             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8329                                 "False illegal-move claim", GE_XBOARD );
8330             return; // do not take back move we tested as valid
8331         }
8332         currentMove = forwardMostMove-1;
8333         DisplayMove(currentMove-1); /* before DisplayMoveError */
8334         SwitchClocks(forwardMostMove-1); // [HGM] race
8335         DisplayBothClocks();
8336         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8337                 parseList[currentMove], _(cps->which));
8338         DisplayMoveError(buf1);
8339         DrawPosition(FALSE, boards[currentMove]);
8340
8341         SetUserThinkingEnables();
8342         return;
8343     }
8344     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8345         /* Program has a broken "time" command that
8346            outputs a string not ending in newline.
8347            Don't use it. */
8348         cps->sendTime = 0;
8349     }
8350
8351     /*
8352      * If chess program startup fails, exit with an error message.
8353      * Attempts to recover here are futile.
8354      */
8355     if ((StrStr(message, "unknown host") != NULL)
8356         || (StrStr(message, "No remote directory") != NULL)
8357         || (StrStr(message, "not found") != NULL)
8358         || (StrStr(message, "No such file") != NULL)
8359         || (StrStr(message, "can't alloc") != NULL)
8360         || (StrStr(message, "Permission denied") != NULL)) {
8361
8362         cps->maybeThinking = FALSE;
8363         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8364                 _(cps->which), cps->program, cps->host, message);
8365         RemoveInputSource(cps->isr);
8366         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8367             if(cps == &first) appData.noChessProgram = TRUE;
8368             DisplayError(buf1, 0);
8369         }
8370         return;
8371     }
8372
8373     /*
8374      * Look for hint output
8375      */
8376     if (sscanf(message, "Hint: %s", buf1) == 1) {
8377         if (cps == &first && hintRequested) {
8378             hintRequested = FALSE;
8379             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8380                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8381                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8382                                     PosFlags(forwardMostMove),
8383                                     fromY, fromX, toY, toX, promoChar, buf1);
8384                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8385                 DisplayInformation(buf2);
8386             } else {
8387                 /* Hint move could not be parsed!? */
8388               snprintf(buf2, sizeof(buf2),
8389                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8390                         buf1, _(cps->which));
8391                 DisplayError(buf2, 0);
8392             }
8393         } else {
8394           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8395         }
8396         return;
8397     }
8398
8399     /*
8400      * Ignore other messages if game is not in progress
8401      */
8402     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8403         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8404
8405     /*
8406      * look for win, lose, draw, or draw offer
8407      */
8408     if (strncmp(message, "1-0", 3) == 0) {
8409         char *p, *q, *r = "";
8410         p = strchr(message, '{');
8411         if (p) {
8412             q = strchr(p, '}');
8413             if (q) {
8414                 *q = NULLCHAR;
8415                 r = p + 1;
8416             }
8417         }
8418         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8419         return;
8420     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8431         if (strcmp(r, "Black resigns") == 0) {
8432             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8433             return;
8434         }
8435         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8436         return;
8437     } else if (strncmp(message, "1/2", 3) == 0) {
8438         char *p, *q, *r = "";
8439         p = strchr(message, '{');
8440         if (p) {
8441             q = strchr(p, '}');
8442             if (q) {
8443                 *q = NULLCHAR;
8444                 r = p + 1;
8445             }
8446         }
8447
8448         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8449         return;
8450
8451     } else if (strncmp(message, "White resign", 12) == 0) {
8452         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "Black resign", 12) == 0) {
8455         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "White matches", 13) == 0 ||
8458                strncmp(message, "Black matches", 13) == 0   ) {
8459         /* [HGM] ignore GNUShogi noises */
8460         return;
8461     } else if (strncmp(message, "White", 5) == 0 &&
8462                message[5] != '(' &&
8463                StrStr(message, "Black") == NULL) {
8464         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8465         return;
8466     } else if (strncmp(message, "Black", 5) == 0 &&
8467                message[5] != '(') {
8468         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8469         return;
8470     } else if (strcmp(message, "resign") == 0 ||
8471                strcmp(message, "computer resigns") == 0) {
8472         switch (gameMode) {
8473           case MachinePlaysBlack:
8474           case IcsPlayingBlack:
8475             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8476             break;
8477           case MachinePlaysWhite:
8478           case IcsPlayingWhite:
8479             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8480             break;
8481           case TwoMachinesPlay:
8482             if (cps->twoMachinesColor[0] == 'w')
8483               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8484             else
8485               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8486             break;
8487           default:
8488             /* can't happen */
8489             break;
8490         }
8491         return;
8492     } else if (strncmp(message, "opponent mates", 14) == 0) {
8493         switch (gameMode) {
8494           case MachinePlaysBlack:
8495           case IcsPlayingBlack:
8496             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8497             break;
8498           case MachinePlaysWhite:
8499           case IcsPlayingWhite:
8500             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8501             break;
8502           case TwoMachinesPlay:
8503             if (cps->twoMachinesColor[0] == 'w')
8504               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8505             else
8506               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8507             break;
8508           default:
8509             /* can't happen */
8510             break;
8511         }
8512         return;
8513     } else if (strncmp(message, "computer mates", 14) == 0) {
8514         switch (gameMode) {
8515           case MachinePlaysBlack:
8516           case IcsPlayingBlack:
8517             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8518             break;
8519           case MachinePlaysWhite:
8520           case IcsPlayingWhite:
8521             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8522             break;
8523           case TwoMachinesPlay:
8524             if (cps->twoMachinesColor[0] == 'w')
8525               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8526             else
8527               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8528             break;
8529           default:
8530             /* can't happen */
8531             break;
8532         }
8533         return;
8534     } else if (strncmp(message, "checkmate", 9) == 0) {
8535         if (WhiteOnMove(forwardMostMove)) {
8536             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8537         } else {
8538             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539         }
8540         return;
8541     } else if (strstr(message, "Draw") != NULL ||
8542                strstr(message, "game is a draw") != NULL) {
8543         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8544         return;
8545     } else if (strstr(message, "offer") != NULL &&
8546                strstr(message, "draw") != NULL) {
8547 #if ZIPPY
8548         if (appData.zippyPlay && first.initDone) {
8549             /* Relay offer to ICS */
8550             SendToICS(ics_prefix);
8551             SendToICS("draw\n");
8552         }
8553 #endif
8554         cps->offeredDraw = 2; /* valid until this engine moves twice */
8555         if (gameMode == TwoMachinesPlay) {
8556             if (cps->other->offeredDraw) {
8557                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8558             /* [HGM] in two-machine mode we delay relaying draw offer      */
8559             /* until after we also have move, to see if it is really claim */
8560             }
8561         } else if (gameMode == MachinePlaysWhite ||
8562                    gameMode == MachinePlaysBlack) {
8563           if (userOfferedDraw) {
8564             DisplayInformation(_("Machine accepts your draw offer"));
8565             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8566           } else {
8567             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8568           }
8569         }
8570     }
8571
8572
8573     /*
8574      * Look for thinking output
8575      */
8576     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8577           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8578                                 ) {
8579         int plylev, mvleft, mvtot, curscore, time;
8580         char mvname[MOVE_LEN];
8581         u64 nodes; // [DM]
8582         char plyext;
8583         int ignore = FALSE;
8584         int prefixHint = FALSE;
8585         mvname[0] = NULLCHAR;
8586
8587         switch (gameMode) {
8588           case MachinePlaysBlack:
8589           case IcsPlayingBlack:
8590             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8591             break;
8592           case MachinePlaysWhite:
8593           case IcsPlayingWhite:
8594             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8595             break;
8596           case AnalyzeMode:
8597           case AnalyzeFile:
8598             break;
8599           case IcsObserving: /* [DM] icsEngineAnalyze */
8600             if (!appData.icsEngineAnalyze) ignore = TRUE;
8601             break;
8602           case TwoMachinesPlay:
8603             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8604                 ignore = TRUE;
8605             }
8606             break;
8607           default:
8608             ignore = TRUE;
8609             break;
8610         }
8611
8612         if (!ignore) {
8613             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8614             buf1[0] = NULLCHAR;
8615             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8616                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8617
8618                 if (plyext != ' ' && plyext != '\t') {
8619                     time *= 100;
8620                 }
8621
8622                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8623                 if( cps->scoreIsAbsolute &&
8624                     ( gameMode == MachinePlaysBlack ||
8625                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8626                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8627                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8628                      !WhiteOnMove(currentMove)
8629                     ) )
8630                 {
8631                     curscore = -curscore;
8632                 }
8633
8634                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8635
8636                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8637                         char buf[MSG_SIZ];
8638                         FILE *f;
8639                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8640                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8641                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8642                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8643                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8644                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8645                                 fclose(f);
8646                         } else DisplayError(_("failed writing PV"), 0);
8647                 }
8648
8649                 tempStats.depth = plylev;
8650                 tempStats.nodes = nodes;
8651                 tempStats.time = time;
8652                 tempStats.score = curscore;
8653                 tempStats.got_only_move = 0;
8654
8655                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8656                         int ticklen;
8657
8658                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8659                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8660                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8661                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8662                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8663                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8664                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8665                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8666                 }
8667
8668                 /* Buffer overflow protection */
8669                 if (pv[0] != NULLCHAR) {
8670                     if (strlen(pv) >= sizeof(tempStats.movelist)
8671                         && appData.debugMode) {
8672                         fprintf(debugFP,
8673                                 "PV is too long; using the first %u bytes.\n",
8674                                 (unsigned) sizeof(tempStats.movelist) - 1);
8675                     }
8676
8677                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8678                 } else {
8679                     sprintf(tempStats.movelist, " no PV\n");
8680                 }
8681
8682                 if (tempStats.seen_stat) {
8683                     tempStats.ok_to_send = 1;
8684                 }
8685
8686                 if (strchr(tempStats.movelist, '(') != NULL) {
8687                     tempStats.line_is_book = 1;
8688                     tempStats.nr_moves = 0;
8689                     tempStats.moves_left = 0;
8690                 } else {
8691                     tempStats.line_is_book = 0;
8692                 }
8693
8694                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8695                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8696
8697                 SendProgramStatsToFrontend( cps, &tempStats );
8698
8699                 /*
8700                     [AS] Protect the thinkOutput buffer from overflow... this
8701                     is only useful if buf1 hasn't overflowed first!
8702                 */
8703                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8704                          plylev,
8705                          (gameMode == TwoMachinesPlay ?
8706                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8707                          ((double) curscore) / 100.0,
8708                          prefixHint ? lastHint : "",
8709                          prefixHint ? " " : "" );
8710
8711                 if( buf1[0] != NULLCHAR ) {
8712                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8713
8714                     if( strlen(pv) > max_len ) {
8715                         if( appData.debugMode) {
8716                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8717                         }
8718                         pv[max_len+1] = '\0';
8719                     }
8720
8721                     strcat( thinkOutput, pv);
8722                 }
8723
8724                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8725                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8726                     DisplayMove(currentMove - 1);
8727                 }
8728                 return;
8729
8730             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8731                 /* crafty (9.25+) says "(only move) <move>"
8732                  * if there is only 1 legal move
8733                  */
8734                 sscanf(p, "(only move) %s", buf1);
8735                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8736                 sprintf(programStats.movelist, "%s (only move)", buf1);
8737                 programStats.depth = 1;
8738                 programStats.nr_moves = 1;
8739                 programStats.moves_left = 1;
8740                 programStats.nodes = 1;
8741                 programStats.time = 1;
8742                 programStats.got_only_move = 1;
8743
8744                 /* Not really, but we also use this member to
8745                    mean "line isn't going to change" (Crafty
8746                    isn't searching, so stats won't change) */
8747                 programStats.line_is_book = 1;
8748
8749                 SendProgramStatsToFrontend( cps, &programStats );
8750
8751                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8752                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8753                     DisplayMove(currentMove - 1);
8754                 }
8755                 return;
8756             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8757                               &time, &nodes, &plylev, &mvleft,
8758                               &mvtot, mvname) >= 5) {
8759                 /* The stat01: line is from Crafty (9.29+) in response
8760                    to the "." command */
8761                 programStats.seen_stat = 1;
8762                 cps->maybeThinking = TRUE;
8763
8764                 if (programStats.got_only_move || !appData.periodicUpdates)
8765                   return;
8766
8767                 programStats.depth = plylev;
8768                 programStats.time = time;
8769                 programStats.nodes = nodes;
8770                 programStats.moves_left = mvleft;
8771                 programStats.nr_moves = mvtot;
8772                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8773                 programStats.ok_to_send = 1;
8774                 programStats.movelist[0] = '\0';
8775
8776                 SendProgramStatsToFrontend( cps, &programStats );
8777
8778                 return;
8779
8780             } else if (strncmp(message,"++",2) == 0) {
8781                 /* Crafty 9.29+ outputs this */
8782                 programStats.got_fail = 2;
8783                 return;
8784
8785             } else if (strncmp(message,"--",2) == 0) {
8786                 /* Crafty 9.29+ outputs this */
8787                 programStats.got_fail = 1;
8788                 return;
8789
8790             } else if (thinkOutput[0] != NULLCHAR &&
8791                        strncmp(message, "    ", 4) == 0) {
8792                 unsigned message_len;
8793
8794                 p = message;
8795                 while (*p && *p == ' ') p++;
8796
8797                 message_len = strlen( p );
8798
8799                 /* [AS] Avoid buffer overflow */
8800                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8801                     strcat(thinkOutput, " ");
8802                     strcat(thinkOutput, p);
8803                 }
8804
8805                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8806                     strcat(programStats.movelist, " ");
8807                     strcat(programStats.movelist, p);
8808                 }
8809
8810                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8811                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8812                     DisplayMove(currentMove - 1);
8813                 }
8814                 return;
8815             }
8816         }
8817         else {
8818             buf1[0] = NULLCHAR;
8819
8820             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8821                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8822             {
8823                 ChessProgramStats cpstats;
8824
8825                 if (plyext != ' ' && plyext != '\t') {
8826                     time *= 100;
8827                 }
8828
8829                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8830                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8831                     curscore = -curscore;
8832                 }
8833
8834                 cpstats.depth = plylev;
8835                 cpstats.nodes = nodes;
8836                 cpstats.time = time;
8837                 cpstats.score = curscore;
8838                 cpstats.got_only_move = 0;
8839                 cpstats.movelist[0] = '\0';
8840
8841                 if (buf1[0] != NULLCHAR) {
8842                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8843                 }
8844
8845                 cpstats.ok_to_send = 0;
8846                 cpstats.line_is_book = 0;
8847                 cpstats.nr_moves = 0;
8848                 cpstats.moves_left = 0;
8849
8850                 SendProgramStatsToFrontend( cps, &cpstats );
8851             }
8852         }
8853     }
8854 }
8855
8856
8857 /* Parse a game score from the character string "game", and
8858    record it as the history of the current game.  The game
8859    score is NOT assumed to start from the standard position.
8860    The display is not updated in any way.
8861    */
8862 void
8863 ParseGameHistory (char *game)
8864 {
8865     ChessMove moveType;
8866     int fromX, fromY, toX, toY, boardIndex;
8867     char promoChar;
8868     char *p, *q;
8869     char buf[MSG_SIZ];
8870
8871     if (appData.debugMode)
8872       fprintf(debugFP, "Parsing game history: %s\n", game);
8873
8874     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8875     gameInfo.site = StrSave(appData.icsHost);
8876     gameInfo.date = PGNDate();
8877     gameInfo.round = StrSave("-");
8878
8879     /* Parse out names of players */
8880     while (*game == ' ') game++;
8881     p = buf;
8882     while (*game != ' ') *p++ = *game++;
8883     *p = NULLCHAR;
8884     gameInfo.white = StrSave(buf);
8885     while (*game == ' ') game++;
8886     p = buf;
8887     while (*game != ' ' && *game != '\n') *p++ = *game++;
8888     *p = NULLCHAR;
8889     gameInfo.black = StrSave(buf);
8890
8891     /* Parse moves */
8892     boardIndex = blackPlaysFirst ? 1 : 0;
8893     yynewstr(game);
8894     for (;;) {
8895         yyboardindex = boardIndex;
8896         moveType = (ChessMove) Myylex();
8897         switch (moveType) {
8898           case IllegalMove:             /* maybe suicide chess, etc. */
8899   if (appData.debugMode) {
8900     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8901     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8902     setbuf(debugFP, NULL);
8903   }
8904           case WhitePromotion:
8905           case BlackPromotion:
8906           case WhiteNonPromotion:
8907           case BlackNonPromotion:
8908           case NormalMove:
8909           case WhiteCapturesEnPassant:
8910           case BlackCapturesEnPassant:
8911           case WhiteKingSideCastle:
8912           case WhiteQueenSideCastle:
8913           case BlackKingSideCastle:
8914           case BlackQueenSideCastle:
8915           case WhiteKingSideCastleWild:
8916           case WhiteQueenSideCastleWild:
8917           case BlackKingSideCastleWild:
8918           case BlackQueenSideCastleWild:
8919           /* PUSH Fabien */
8920           case WhiteHSideCastleFR:
8921           case WhiteASideCastleFR:
8922           case BlackHSideCastleFR:
8923           case BlackASideCastleFR:
8924           /* POP Fabien */
8925             fromX = currentMoveString[0] - AAA;
8926             fromY = currentMoveString[1] - ONE;
8927             toX = currentMoveString[2] - AAA;
8928             toY = currentMoveString[3] - ONE;
8929             promoChar = currentMoveString[4];
8930             break;
8931           case WhiteDrop:
8932           case BlackDrop:
8933             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8934             fromX = moveType == WhiteDrop ?
8935               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8936             (int) CharToPiece(ToLower(currentMoveString[0]));
8937             fromY = DROP_RANK;
8938             toX = currentMoveString[2] - AAA;
8939             toY = currentMoveString[3] - ONE;
8940             promoChar = NULLCHAR;
8941             break;
8942           case AmbiguousMove:
8943             /* bug? */
8944             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8945   if (appData.debugMode) {
8946     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8947     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8948     setbuf(debugFP, NULL);
8949   }
8950             DisplayError(buf, 0);
8951             return;
8952           case ImpossibleMove:
8953             /* bug? */
8954             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8955   if (appData.debugMode) {
8956     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8957     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8958     setbuf(debugFP, NULL);
8959   }
8960             DisplayError(buf, 0);
8961             return;
8962           case EndOfFile:
8963             if (boardIndex < backwardMostMove) {
8964                 /* Oops, gap.  How did that happen? */
8965                 DisplayError(_("Gap in move list"), 0);
8966                 return;
8967             }
8968             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8969             if (boardIndex > forwardMostMove) {
8970                 forwardMostMove = boardIndex;
8971             }
8972             return;
8973           case ElapsedTime:
8974             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8975                 strcat(parseList[boardIndex-1], " ");
8976                 strcat(parseList[boardIndex-1], yy_text);
8977             }
8978             continue;
8979           case Comment:
8980           case PGNTag:
8981           case NAG:
8982           default:
8983             /* ignore */
8984             continue;
8985           case WhiteWins:
8986           case BlackWins:
8987           case GameIsDrawn:
8988           case GameUnfinished:
8989             if (gameMode == IcsExamining) {
8990                 if (boardIndex < backwardMostMove) {
8991                     /* Oops, gap.  How did that happen? */
8992                     return;
8993                 }
8994                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8995                 return;
8996             }
8997             gameInfo.result = moveType;
8998             p = strchr(yy_text, '{');
8999             if (p == NULL) p = strchr(yy_text, '(');
9000             if (p == NULL) {
9001                 p = yy_text;
9002                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9003             } else {
9004                 q = strchr(p, *p == '{' ? '}' : ')');
9005                 if (q != NULL) *q = NULLCHAR;
9006                 p++;
9007             }
9008             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9009             gameInfo.resultDetails = StrSave(p);
9010             continue;
9011         }
9012         if (boardIndex >= forwardMostMove &&
9013             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9014             backwardMostMove = blackPlaysFirst ? 1 : 0;
9015             return;
9016         }
9017         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9018                                  fromY, fromX, toY, toX, promoChar,
9019                                  parseList[boardIndex]);
9020         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9021         /* currentMoveString is set as a side-effect of yylex */
9022         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9023         strcat(moveList[boardIndex], "\n");
9024         boardIndex++;
9025         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9026         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9027           case MT_NONE:
9028           case MT_STALEMATE:
9029           default:
9030             break;
9031           case MT_CHECK:
9032             if(gameInfo.variant != VariantShogi)
9033                 strcat(parseList[boardIndex - 1], "+");
9034             break;
9035           case MT_CHECKMATE:
9036           case MT_STAINMATE:
9037             strcat(parseList[boardIndex - 1], "#");
9038             break;
9039         }
9040     }
9041 }
9042
9043
9044 /* Apply a move to the given board  */
9045 void
9046 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9047 {
9048   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9049   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9050
9051     /* [HGM] compute & store e.p. status and castling rights for new position */
9052     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9053
9054       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9055       oldEP = (signed char)board[EP_STATUS];
9056       board[EP_STATUS] = EP_NONE;
9057
9058   if (fromY == DROP_RANK) {
9059         /* must be first */
9060         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9061             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9062             return;
9063         }
9064         piece = board[toY][toX] = (ChessSquare) fromX;
9065   } else {
9066       int i;
9067
9068       if( board[toY][toX] != EmptySquare )
9069            board[EP_STATUS] = EP_CAPTURE;
9070
9071       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9072            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9073                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9074       } else
9075       if( board[fromY][fromX] == WhitePawn ) {
9076            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9077                board[EP_STATUS] = EP_PAWN_MOVE;
9078            if( toY-fromY==2) {
9079                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9080                         gameInfo.variant != VariantBerolina || toX < fromX)
9081                       board[EP_STATUS] = toX | berolina;
9082                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9083                         gameInfo.variant != VariantBerolina || toX > fromX)
9084                       board[EP_STATUS] = toX;
9085            }
9086       } else
9087       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
9092                         gameInfo.variant != VariantBerolina || toX < fromX)
9093                       board[EP_STATUS] = toX | berolina;
9094                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9095                         gameInfo.variant != VariantBerolina || toX > fromX)
9096                       board[EP_STATUS] = toX;
9097            }
9098        }
9099
9100        for(i=0; i<nrCastlingRights; i++) {
9101            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9102               board[CASTLING][i] == toX   && castlingRank[i] == toY
9103              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9104        }
9105
9106      if (fromX == toX && fromY == toY) return;
9107
9108      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9109      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9110      if(gameInfo.variant == VariantKnightmate)
9111          king += (int) WhiteUnicorn - (int) WhiteKing;
9112
9113     /* Code added by Tord: */
9114     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9115     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9116         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9117       board[fromY][fromX] = EmptySquare;
9118       board[toY][toX] = EmptySquare;
9119       if((toX > fromX) != (piece == WhiteRook)) {
9120         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9121       } else {
9122         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9123       }
9124     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9125                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9126       board[fromY][fromX] = EmptySquare;
9127       board[toY][toX] = EmptySquare;
9128       if((toX > fromX) != (piece == BlackRook)) {
9129         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9130       } else {
9131         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9132       }
9133     /* End of code added by Tord */
9134
9135     } else if (board[fromY][fromX] == king
9136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137         && toY == fromY && toX > fromX+1) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = king;
9140         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9141         board[fromY][BOARD_RGHT-1] = EmptySquare;
9142     } else if (board[fromY][fromX] == king
9143         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9144                && toY == fromY && toX < fromX-1) {
9145         board[fromY][fromX] = EmptySquare;
9146         board[toY][toX] = king;
9147         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9148         board[fromY][BOARD_LEFT] = EmptySquare;
9149     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9150                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9151                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9152                ) {
9153         /* white pawn promotion */
9154         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9155         if(gameInfo.variant==VariantBughouse ||
9156            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9157             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9158         board[fromY][fromX] = EmptySquare;
9159     } else if ((fromY >= BOARD_HEIGHT>>1)
9160                && (toX != fromX)
9161                && gameInfo.variant != VariantXiangqi
9162                && gameInfo.variant != VariantBerolina
9163                && (board[fromY][fromX] == WhitePawn)
9164                && (board[toY][toX] == EmptySquare)) {
9165         board[fromY][fromX] = EmptySquare;
9166         board[toY][toX] = WhitePawn;
9167         captured = board[toY - 1][toX];
9168         board[toY - 1][toX] = EmptySquare;
9169     } else if ((fromY == BOARD_HEIGHT-4)
9170                && (toX == fromX)
9171                && gameInfo.variant == VariantBerolina
9172                && (board[fromY][fromX] == WhitePawn)
9173                && (board[toY][toX] == EmptySquare)) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = WhitePawn;
9176         if(oldEP & EP_BEROLIN_A) {
9177                 captured = board[fromY][fromX-1];
9178                 board[fromY][fromX-1] = EmptySquare;
9179         }else{  captured = board[fromY][fromX+1];
9180                 board[fromY][fromX+1] = EmptySquare;
9181         }
9182     } else if (board[fromY][fromX] == king
9183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9184                && toY == fromY && toX > fromX+1) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = king;
9187         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9188         board[fromY][BOARD_RGHT-1] = EmptySquare;
9189     } else if (board[fromY][fromX] == king
9190         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9191                && toY == fromY && toX < fromX-1) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = king;
9194         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9195         board[fromY][BOARD_LEFT] = EmptySquare;
9196     } else if (fromY == 7 && fromX == 3
9197                && board[fromY][fromX] == BlackKing
9198                && toY == 7 && toX == 5) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackKing;
9201         board[fromY][7] = EmptySquare;
9202         board[toY][4] = BlackRook;
9203     } else if (fromY == 7 && fromX == 3
9204                && board[fromY][fromX] == BlackKing
9205                && toY == 7 && toX == 1) {
9206         board[fromY][fromX] = EmptySquare;
9207         board[toY][toX] = BlackKing;
9208         board[fromY][0] = EmptySquare;
9209         board[toY][2] = BlackRook;
9210     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9211                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9212                && toY < promoRank && promoChar
9213                ) {
9214         /* black pawn promotion */
9215         board[toY][toX] = CharToPiece(ToLower(promoChar));
9216         if(gameInfo.variant==VariantBughouse ||
9217            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9218             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9219         board[fromY][fromX] = EmptySquare;
9220     } else if ((fromY < BOARD_HEIGHT>>1)
9221                && (toX != fromX)
9222                && gameInfo.variant != VariantXiangqi
9223                && gameInfo.variant != VariantBerolina
9224                && (board[fromY][fromX] == BlackPawn)
9225                && (board[toY][toX] == EmptySquare)) {
9226         board[fromY][fromX] = EmptySquare;
9227         board[toY][toX] = BlackPawn;
9228         captured = board[toY + 1][toX];
9229         board[toY + 1][toX] = EmptySquare;
9230     } else if ((fromY == 3)
9231                && (toX == fromX)
9232                && gameInfo.variant == VariantBerolina
9233                && (board[fromY][fromX] == BlackPawn)
9234                && (board[toY][toX] == EmptySquare)) {
9235         board[fromY][fromX] = EmptySquare;
9236         board[toY][toX] = BlackPawn;
9237         if(oldEP & EP_BEROLIN_A) {
9238                 captured = board[fromY][fromX-1];
9239                 board[fromY][fromX-1] = EmptySquare;
9240         }else{  captured = board[fromY][fromX+1];
9241                 board[fromY][fromX+1] = EmptySquare;
9242         }
9243     } else {
9244         board[toY][toX] = board[fromY][fromX];
9245         board[fromY][fromX] = EmptySquare;
9246     }
9247   }
9248
9249     if (gameInfo.holdingsWidth != 0) {
9250
9251       /* !!A lot more code needs to be written to support holdings  */
9252       /* [HGM] OK, so I have written it. Holdings are stored in the */
9253       /* penultimate board files, so they are automaticlly stored   */
9254       /* in the game history.                                       */
9255       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9256                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9257         /* Delete from holdings, by decreasing count */
9258         /* and erasing image if necessary            */
9259         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9260         if(p < (int) BlackPawn) { /* white drop */
9261              p -= (int)WhitePawn;
9262                  p = PieceToNumber((ChessSquare)p);
9263              if(p >= gameInfo.holdingsSize) p = 0;
9264              if(--board[p][BOARD_WIDTH-2] <= 0)
9265                   board[p][BOARD_WIDTH-1] = EmptySquare;
9266              if((int)board[p][BOARD_WIDTH-2] < 0)
9267                         board[p][BOARD_WIDTH-2] = 0;
9268         } else {                  /* black drop */
9269              p -= (int)BlackPawn;
9270                  p = PieceToNumber((ChessSquare)p);
9271              if(p >= gameInfo.holdingsSize) p = 0;
9272              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9273                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9274              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9275                         board[BOARD_HEIGHT-1-p][1] = 0;
9276         }
9277       }
9278       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9279           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9280         /* [HGM] holdings: Add to holdings, if holdings exist */
9281         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9282                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9283                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9284         }
9285         p = (int) captured;
9286         if (p >= (int) BlackPawn) {
9287           p -= (int)BlackPawn;
9288           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289                   /* in Shogi restore piece to its original  first */
9290                   captured = (ChessSquare) (DEMOTED captured);
9291                   p = DEMOTED p;
9292           }
9293           p = PieceToNumber((ChessSquare)p);
9294           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9295           board[p][BOARD_WIDTH-2]++;
9296           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9297         } else {
9298           p -= (int)WhitePawn;
9299           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9300                   captured = (ChessSquare) (DEMOTED captured);
9301                   p = DEMOTED p;
9302           }
9303           p = PieceToNumber((ChessSquare)p);
9304           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9305           board[BOARD_HEIGHT-1-p][1]++;
9306           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9307         }
9308       }
9309     } else if (gameInfo.variant == VariantAtomic) {
9310       if (captured != EmptySquare) {
9311         int y, x;
9312         for (y = toY-1; y <= toY+1; y++) {
9313           for (x = toX-1; x <= toX+1; x++) {
9314             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9315                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9316               board[y][x] = EmptySquare;
9317             }
9318           }
9319         }
9320         board[toY][toX] = EmptySquare;
9321       }
9322     }
9323     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9324         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9325     } else
9326     if(promoChar == '+') {
9327         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9328         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9329     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9330         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9331     }
9332     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9333                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9334         // [HGM] superchess: take promotion piece out of holdings
9335         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9336         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9337             if(!--board[k][BOARD_WIDTH-2])
9338                 board[k][BOARD_WIDTH-1] = EmptySquare;
9339         } else {
9340             if(!--board[BOARD_HEIGHT-1-k][1])
9341                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9342         }
9343     }
9344
9345 }
9346
9347 /* Updates forwardMostMove */
9348 void
9349 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9350 {
9351 //    forwardMostMove++; // [HGM] bare: moved downstream
9352
9353     (void) CoordsToAlgebraic(boards[forwardMostMove],
9354                              PosFlags(forwardMostMove),
9355                              fromY, fromX, toY, toX, promoChar,
9356                              parseList[forwardMostMove]);
9357
9358     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9359         int timeLeft; static int lastLoadFlag=0; int king, piece;
9360         piece = boards[forwardMostMove][fromY][fromX];
9361         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9362         if(gameInfo.variant == VariantKnightmate)
9363             king += (int) WhiteUnicorn - (int) WhiteKing;
9364         if(forwardMostMove == 0) {
9365             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9366                 fprintf(serverMoves, "%s;", UserName());
9367             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9368                 fprintf(serverMoves, "%s;", second.tidy);
9369             fprintf(serverMoves, "%s;", first.tidy);
9370             if(gameMode == MachinePlaysWhite)
9371                 fprintf(serverMoves, "%s;", UserName());
9372             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9373                 fprintf(serverMoves, "%s;", second.tidy);
9374         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9375         lastLoadFlag = loadFlag;
9376         // print base move
9377         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9378         // print castling suffix
9379         if( toY == fromY && piece == king ) {
9380             if(toX-fromX > 1)
9381                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9382             if(fromX-toX >1)
9383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9384         }
9385         // e.p. suffix
9386         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9387              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9388              boards[forwardMostMove][toY][toX] == EmptySquare
9389              && fromX != toX && fromY != toY)
9390                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9391         // promotion suffix
9392         if(promoChar != NULLCHAR)
9393                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9394         if(!loadFlag) {
9395                 char buf[MOVE_LEN*2], *p; int len;
9396             fprintf(serverMoves, "/%d/%d",
9397                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9398             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9399             else                      timeLeft = blackTimeRemaining/1000;
9400             fprintf(serverMoves, "/%d", timeLeft);
9401                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9402                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9403                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9404             fprintf(serverMoves, "/%s", buf);
9405         }
9406         fflush(serverMoves);
9407     }
9408
9409     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9410         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9411       return;
9412     }
9413     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9414     if (commentList[forwardMostMove+1] != NULL) {
9415         free(commentList[forwardMostMove+1]);
9416         commentList[forwardMostMove+1] = NULL;
9417     }
9418     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9419     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9420     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9421     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9422     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9423     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9424     adjustedClock = FALSE;
9425     gameInfo.result = GameUnfinished;
9426     if (gameInfo.resultDetails != NULL) {
9427         free(gameInfo.resultDetails);
9428         gameInfo.resultDetails = NULL;
9429     }
9430     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9431                               moveList[forwardMostMove - 1]);
9432     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9433       case MT_NONE:
9434       case MT_STALEMATE:
9435       default:
9436         break;
9437       case MT_CHECK:
9438         if(gameInfo.variant != VariantShogi)
9439             strcat(parseList[forwardMostMove - 1], "+");
9440         break;
9441       case MT_CHECKMATE:
9442       case MT_STAINMATE:
9443         strcat(parseList[forwardMostMove - 1], "#");
9444         break;
9445     }
9446
9447 }
9448
9449 /* Updates currentMove if not pausing */
9450 void
9451 ShowMove (int fromX, int fromY, int toX, int toY)
9452 {
9453     int instant = (gameMode == PlayFromGameFile) ?
9454         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9455     if(appData.noGUI) return;
9456     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9457         if (!instant) {
9458             if (forwardMostMove == currentMove + 1) {
9459                 AnimateMove(boards[forwardMostMove - 1],
9460                             fromX, fromY, toX, toY);
9461             }
9462             if (appData.highlightLastMove) {
9463                 SetHighlights(fromX, fromY, toX, toY);
9464             }
9465         }
9466         currentMove = forwardMostMove;
9467     }
9468
9469     if (instant) return;
9470
9471     DisplayMove(currentMove - 1);
9472     DrawPosition(FALSE, boards[currentMove]);
9473     DisplayBothClocks();
9474     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9475 }
9476
9477 void
9478 SendEgtPath (ChessProgramState *cps)
9479 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9480         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9481
9482         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9483
9484         while(*p) {
9485             char c, *q = name+1, *r, *s;
9486
9487             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9488             while(*p && *p != ',') *q++ = *p++;
9489             *q++ = ':'; *q = 0;
9490             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9491                 strcmp(name, ",nalimov:") == 0 ) {
9492                 // take nalimov path from the menu-changeable option first, if it is defined
9493               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9494                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9495             } else
9496             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9497                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9498                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9499                 s = r = StrStr(s, ":") + 1; // beginning of path info
9500                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9501                 c = *r; *r = 0;             // temporarily null-terminate path info
9502                     *--q = 0;               // strip of trailig ':' from name
9503                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9504                 *r = c;
9505                 SendToProgram(buf,cps);     // send egtbpath command for this format
9506             }
9507             if(*p == ',') p++; // read away comma to position for next format name
9508         }
9509 }
9510
9511 void
9512 InitChessProgram (ChessProgramState *cps, int setup)
9513 /* setup needed to setup FRC opening position */
9514 {
9515     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9516     if (appData.noChessProgram) return;
9517     hintRequested = FALSE;
9518     bookRequested = FALSE;
9519
9520     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9521     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9522     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9523     if(cps->memSize) { /* [HGM] memory */
9524       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9525         SendToProgram(buf, cps);
9526     }
9527     SendEgtPath(cps); /* [HGM] EGT */
9528     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9529       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9530         SendToProgram(buf, cps);
9531     }
9532
9533     SendToProgram(cps->initString, cps);
9534     if (gameInfo.variant != VariantNormal &&
9535         gameInfo.variant != VariantLoadable
9536         /* [HGM] also send variant if board size non-standard */
9537         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9538                                             ) {
9539       char *v = VariantName(gameInfo.variant);
9540       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9541         /* [HGM] in protocol 1 we have to assume all variants valid */
9542         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9543         DisplayFatalError(buf, 0, 1);
9544         return;
9545       }
9546
9547       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9548       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9549       if( gameInfo.variant == VariantXiangqi )
9550            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9551       if( gameInfo.variant == VariantShogi )
9552            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9553       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9554            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9555       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9556           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9557            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9558       if( gameInfo.variant == VariantCourier )
9559            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9560       if( gameInfo.variant == VariantSuper )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9562       if( gameInfo.variant == VariantGreat )
9563            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9564       if( gameInfo.variant == VariantSChess )
9565            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9566       if( gameInfo.variant == VariantGrand )
9567            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9568
9569       if(overruled) {
9570         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9571                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9572            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9573            if(StrStr(cps->variants, b) == NULL) {
9574                // specific sized variant not known, check if general sizing allowed
9575                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9576                    if(StrStr(cps->variants, "boardsize") == NULL) {
9577                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9578                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9579                        DisplayFatalError(buf, 0, 1);
9580                        return;
9581                    }
9582                    /* [HGM] here we really should compare with the maximum supported board size */
9583                }
9584            }
9585       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9586       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9587       SendToProgram(buf, cps);
9588     }
9589     currentlyInitializedVariant = gameInfo.variant;
9590
9591     /* [HGM] send opening position in FRC to first engine */
9592     if(setup) {
9593           SendToProgram("force\n", cps);
9594           SendBoard(cps, 0);
9595           /* engine is now in force mode! Set flag to wake it up after first move. */
9596           setboardSpoiledMachineBlack = 1;
9597     }
9598
9599     if (cps->sendICS) {
9600       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9601       SendToProgram(buf, cps);
9602     }
9603     cps->maybeThinking = FALSE;
9604     cps->offeredDraw = 0;
9605     if (!appData.icsActive) {
9606         SendTimeControl(cps, movesPerSession, timeControl,
9607                         timeIncrement, appData.searchDepth,
9608                         searchTime);
9609     }
9610     if (appData.showThinking
9611         // [HGM] thinking: four options require thinking output to be sent
9612         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9613                                 ) {
9614         SendToProgram("post\n", cps);
9615     }
9616     SendToProgram("hard\n", cps);
9617     if (!appData.ponderNextMove) {
9618         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9619            it without being sure what state we are in first.  "hard"
9620            is not a toggle, so that one is OK.
9621          */
9622         SendToProgram("easy\n", cps);
9623     }
9624     if (cps->usePing) {
9625       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9626       SendToProgram(buf, cps);
9627     }
9628     cps->initDone = TRUE;
9629     ClearEngineOutputPane(cps == &second);
9630 }
9631
9632
9633 void
9634 StartChessProgram (ChessProgramState *cps)
9635 {
9636     char buf[MSG_SIZ];
9637     int err;
9638
9639     if (appData.noChessProgram) return;
9640     cps->initDone = FALSE;
9641
9642     if (strcmp(cps->host, "localhost") == 0) {
9643         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9644     } else if (*appData.remoteShell == NULLCHAR) {
9645         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9646     } else {
9647         if (*appData.remoteUser == NULLCHAR) {
9648           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9649                     cps->program);
9650         } else {
9651           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9652                     cps->host, appData.remoteUser, cps->program);
9653         }
9654         err = StartChildProcess(buf, "", &cps->pr);
9655     }
9656
9657     if (err != 0) {
9658       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9659         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9660         if(cps != &first) return;
9661         appData.noChessProgram = TRUE;
9662         ThawUI();
9663         SetNCPMode();
9664 //      DisplayFatalError(buf, err, 1);
9665 //      cps->pr = NoProc;
9666 //      cps->isr = NULL;
9667         return;
9668     }
9669
9670     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9671     if (cps->protocolVersion > 1) {
9672       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9673       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9674       cps->comboCnt = 0;  //                and values of combo boxes
9675       SendToProgram(buf, cps);
9676     } else {
9677       SendToProgram("xboard\n", cps);
9678     }
9679 }
9680
9681 void
9682 TwoMachinesEventIfReady P((void))
9683 {
9684   static int curMess = 0;
9685   if (first.lastPing != first.lastPong) {
9686     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9687     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9688     return;
9689   }
9690   if (second.lastPing != second.lastPong) {
9691     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9692     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9693     return;
9694   }
9695   DisplayMessage("", ""); curMess = 0;
9696   ThawUI();
9697   TwoMachinesEvent();
9698 }
9699
9700 char *
9701 MakeName (char *template)
9702 {
9703     time_t clock;
9704     struct tm *tm;
9705     static char buf[MSG_SIZ];
9706     char *p = buf;
9707     int i;
9708
9709     clock = time((time_t *)NULL);
9710     tm = localtime(&clock);
9711
9712     while(*p++ = *template++) if(p[-1] == '%') {
9713         switch(*template++) {
9714           case 0:   *p = 0; return buf;
9715           case 'Y': i = tm->tm_year+1900; break;
9716           case 'y': i = tm->tm_year-100; break;
9717           case 'M': i = tm->tm_mon+1; break;
9718           case 'd': i = tm->tm_mday; break;
9719           case 'h': i = tm->tm_hour; break;
9720           case 'm': i = tm->tm_min; break;
9721           case 's': i = tm->tm_sec; break;
9722           default:  i = 0;
9723         }
9724         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9725     }
9726     return buf;
9727 }
9728
9729 int
9730 CountPlayers (char *p)
9731 {
9732     int n = 0;
9733     while(p = strchr(p, '\n')) p++, n++; // count participants
9734     return n;
9735 }
9736
9737 FILE *
9738 WriteTourneyFile (char *results, FILE *f)
9739 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9740     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9741     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9742         // create a file with tournament description
9743         fprintf(f, "-participants {%s}\n", appData.participants);
9744         fprintf(f, "-seedBase %d\n", appData.seedBase);
9745         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9746         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9747         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9748         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9749         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9750         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9751         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9752         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9753         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9754         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9755         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9756         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9757         if(searchTime > 0)
9758                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9759         else {
9760                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9761                 fprintf(f, "-tc %s\n", appData.timeControl);
9762                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9763         }
9764         fprintf(f, "-results \"%s\"\n", results);
9765     }
9766     return f;
9767 }
9768
9769 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9770
9771 void
9772 Substitute (char *participants, int expunge)
9773 {
9774     int i, changed, changes=0, nPlayers=0;
9775     char *p, *q, *r, buf[MSG_SIZ];
9776     if(participants == NULL) return;
9777     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9778     r = p = participants; q = appData.participants;
9779     while(*p && *p == *q) {
9780         if(*p == '\n') r = p+1, nPlayers++;
9781         p++; q++;
9782     }
9783     if(*p) { // difference
9784         while(*p && *p++ != '\n');
9785         while(*q && *q++ != '\n');
9786       changed = nPlayers;
9787         changes = 1 + (strcmp(p, q) != 0);
9788     }
9789     if(changes == 1) { // a single engine mnemonic was changed
9790         q = r; while(*q) nPlayers += (*q++ == '\n');
9791         p = buf; while(*r && (*p = *r++) != '\n') p++;
9792         *p = NULLCHAR;
9793         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9794         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9795         if(mnemonic[i]) { // The substitute is valid
9796             FILE *f;
9797             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9798                 flock(fileno(f), LOCK_EX);
9799                 ParseArgsFromFile(f);
9800                 fseek(f, 0, SEEK_SET);
9801                 FREE(appData.participants); appData.participants = participants;
9802                 if(expunge) { // erase results of replaced engine
9803                     int len = strlen(appData.results), w, b, dummy;
9804                     for(i=0; i<len; i++) {
9805                         Pairing(i, nPlayers, &w, &b, &dummy);
9806                         if((w == changed || b == changed) && appData.results[i] == '*') {
9807                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9808                             fclose(f);
9809                             return;
9810                         }
9811                     }
9812                     for(i=0; i<len; i++) {
9813                         Pairing(i, nPlayers, &w, &b, &dummy);
9814                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9815                     }
9816                 }
9817                 WriteTourneyFile(appData.results, f);
9818                 fclose(f); // release lock
9819                 return;
9820             }
9821         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9822     }
9823     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9824     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9825     free(participants);
9826     return;
9827 }
9828
9829 int
9830 CreateTourney (char *name)
9831 {
9832         FILE *f;
9833         if(matchMode && strcmp(name, appData.tourneyFile)) {
9834              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9835         }
9836         if(name[0] == NULLCHAR) {
9837             if(appData.participants[0])
9838                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9839             return 0;
9840         }
9841         f = fopen(name, "r");
9842         if(f) { // file exists
9843             ASSIGN(appData.tourneyFile, name);
9844             ParseArgsFromFile(f); // parse it
9845         } else {
9846             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9847             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9848                 DisplayError(_("Not enough participants"), 0);
9849                 return 0;
9850             }
9851             ASSIGN(appData.tourneyFile, name);
9852             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9853             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9854         }
9855         fclose(f);
9856         appData.noChessProgram = FALSE;
9857         appData.clockMode = TRUE;
9858         SetGNUMode();
9859         return 1;
9860 }
9861
9862 int
9863 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9864 {
9865     char buf[MSG_SIZ], *p, *q;
9866     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9867     skip = !all && group[0]; // if group requested, we start in skip mode
9868     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9869         p = names; q = buf; header = 0;
9870         while(*p && *p != '\n') *q++ = *p++;
9871         *q = 0;
9872         if(*p == '\n') p++;
9873         if(buf[0] == '#') {
9874             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9875             depth++; // we must be entering a new group
9876             if(all) continue; // suppress printing group headers when complete list requested
9877             header = 1;
9878             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9879         }
9880         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9881         if(engineList[i]) free(engineList[i]);
9882         engineList[i] = strdup(buf);
9883         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9884         if(engineMnemonic[i]) free(engineMnemonic[i]);
9885         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9886             strcat(buf, " (");
9887             sscanf(q + 8, "%s", buf + strlen(buf));
9888             strcat(buf, ")");
9889         }
9890         engineMnemonic[i] = strdup(buf);
9891         i++;
9892     }
9893     engineList[i] = engineMnemonic[i] = NULL;
9894     return i;
9895 }
9896
9897 // following implemented as macro to avoid type limitations
9898 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9899
9900 void
9901 SwapEngines (int n)
9902 {   // swap settings for first engine and other engine (so far only some selected options)
9903     int h;
9904     char *p;
9905     if(n == 0) return;
9906     SWAP(directory, p)
9907     SWAP(chessProgram, p)
9908     SWAP(isUCI, h)
9909     SWAP(hasOwnBookUCI, h)
9910     SWAP(protocolVersion, h)
9911     SWAP(reuse, h)
9912     SWAP(scoreIsAbsolute, h)
9913     SWAP(timeOdds, h)
9914     SWAP(logo, p)
9915     SWAP(pgnName, p)
9916     SWAP(pvSAN, h)
9917     SWAP(engOptions, p)
9918 }
9919
9920 int
9921 SetPlayer (int player, char *p)
9922 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9923     int i;
9924     char buf[MSG_SIZ], *engineName;
9925     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9926     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9927     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9928     if(mnemonic[i]) {
9929         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9930         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9931         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9932         ParseArgsFromString(buf);
9933     }
9934     free(engineName);
9935     return i;
9936 }
9937
9938 char *recentEngines;
9939
9940 void
9941 RecentEngineEvent (int nr)
9942 {
9943     int n;
9944 //    SwapEngines(1); // bump first to second
9945 //    ReplaceEngine(&second, 1); // and load it there
9946     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9947     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9948     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9949         ReplaceEngine(&first, 0);
9950         FloatToFront(&appData.recentEngineList, command[n]);
9951     }
9952 }
9953
9954 int
9955 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9956 {   // determine players from game number
9957     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9958
9959     if(appData.tourneyType == 0) {
9960         roundsPerCycle = (nPlayers - 1) | 1;
9961         pairingsPerRound = nPlayers / 2;
9962     } else if(appData.tourneyType > 0) {
9963         roundsPerCycle = nPlayers - appData.tourneyType;
9964         pairingsPerRound = appData.tourneyType;
9965     }
9966     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9967     gamesPerCycle = gamesPerRound * roundsPerCycle;
9968     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9969     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9970     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9971     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9972     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9973     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9974
9975     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9976     if(appData.roundSync) *syncInterval = gamesPerRound;
9977
9978     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9979
9980     if(appData.tourneyType == 0) {
9981         if(curPairing == (nPlayers-1)/2 ) {
9982             *whitePlayer = curRound;
9983             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9984         } else {
9985             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9986             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9987             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9988             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9989         }
9990     } else if(appData.tourneyType > 1) {
9991         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9992         *whitePlayer = curRound + appData.tourneyType;
9993     } else if(appData.tourneyType > 0) {
9994         *whitePlayer = curPairing;
9995         *blackPlayer = curRound + appData.tourneyType;
9996     }
9997
9998     // take care of white/black alternation per round. 
9999     // For cycles and games this is already taken care of by default, derived from matchGame!
10000     return curRound & 1;
10001 }
10002
10003 int
10004 NextTourneyGame (int nr, int *swapColors)
10005 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10006     char *p, *q;
10007     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10008     FILE *tf;
10009     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10010     tf = fopen(appData.tourneyFile, "r");
10011     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10012     ParseArgsFromFile(tf); fclose(tf);
10013     InitTimeControls(); // TC might be altered from tourney file
10014
10015     nPlayers = CountPlayers(appData.participants); // count participants
10016     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10017     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10018
10019     if(syncInterval) {
10020         p = q = appData.results;
10021         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10022         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10023             DisplayMessage(_("Waiting for other game(s)"),"");
10024             waitingForGame = TRUE;
10025             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10026             return 0;
10027         }
10028         waitingForGame = FALSE;
10029     }
10030
10031     if(appData.tourneyType < 0) {
10032         if(nr>=0 && !pairingReceived) {
10033             char buf[1<<16];
10034             if(pairing.pr == NoProc) {
10035                 if(!appData.pairingEngine[0]) {
10036                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10037                     return 0;
10038                 }
10039                 StartChessProgram(&pairing); // starts the pairing engine
10040             }
10041             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10042             SendToProgram(buf, &pairing);
10043             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10044             SendToProgram(buf, &pairing);
10045             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10046         }
10047         pairingReceived = 0;                              // ... so we continue here 
10048         *swapColors = 0;
10049         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10050         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10051         matchGame = 1; roundNr = nr / syncInterval + 1;
10052     }
10053
10054     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10055
10056     // redefine engines, engine dir, etc.
10057     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10058     if(first.pr == NoProc) {
10059       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10060       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10061     }
10062     if(second.pr == NoProc) {
10063       SwapEngines(1);
10064       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10065       SwapEngines(1);         // and make that valid for second engine by swapping
10066       InitEngine(&second, 1);
10067     }
10068     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10069     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10070     return 1;
10071 }
10072
10073 void
10074 NextMatchGame ()
10075 {   // performs game initialization that does not invoke engines, and then tries to start the game
10076     int res, firstWhite, swapColors = 0;
10077     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10078     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
10079         char buf[MSG_SIZ];
10080         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10081         if(strcmp(buf, currentDebugFile)) { // name has changed
10082             FILE *f = fopen(buf, "w");
10083             if(f) { // if opening the new file failed, just keep using the old one
10084                 ASSIGN(currentDebugFile, buf);
10085                 fclose(debugFP);
10086                 debugFP = f;
10087             }
10088             if(appData.serverFileName) {
10089                 if(serverFP) fclose(serverFP);
10090                 serverFP = fopen(appData.serverFileName, "w");
10091                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10092                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10093             }
10094         }
10095     }
10096     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10097     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10098     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10099     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10100     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10101     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10102     Reset(FALSE, first.pr != NoProc);
10103     res = LoadGameOrPosition(matchGame); // setup game
10104     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10105     if(!res) return; // abort when bad game/pos file
10106     TwoMachinesEvent();
10107 }
10108
10109 void
10110 UserAdjudicationEvent (int result)
10111 {
10112     ChessMove gameResult = GameIsDrawn;
10113
10114     if( result > 0 ) {
10115         gameResult = WhiteWins;
10116     }
10117     else if( result < 0 ) {
10118         gameResult = BlackWins;
10119     }
10120
10121     if( gameMode == TwoMachinesPlay ) {
10122         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10123     }
10124 }
10125
10126
10127 // [HGM] save: calculate checksum of game to make games easily identifiable
10128 int
10129 StringCheckSum (char *s)
10130 {
10131         int i = 0;
10132         if(s==NULL) return 0;
10133         while(*s) i = i*259 + *s++;
10134         return i;
10135 }
10136
10137 int
10138 GameCheckSum ()
10139 {
10140         int i, sum=0;
10141         for(i=backwardMostMove; i<forwardMostMove; i++) {
10142                 sum += pvInfoList[i].depth;
10143                 sum += StringCheckSum(parseList[i]);
10144                 sum += StringCheckSum(commentList[i]);
10145                 sum *= 261;
10146         }
10147         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10148         return sum + StringCheckSum(commentList[i]);
10149 } // end of save patch
10150
10151 void
10152 GameEnds (ChessMove result, char *resultDetails, int whosays)
10153 {
10154     GameMode nextGameMode;
10155     int isIcsGame;
10156     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10157
10158     if(endingGame) return; /* [HGM] crash: forbid recursion */
10159     endingGame = 1;
10160     if(twoBoards) { // [HGM] dual: switch back to one board
10161         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10162         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10163     }
10164     if (appData.debugMode) {
10165       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10166               result, resultDetails ? resultDetails : "(null)", whosays);
10167     }
10168
10169     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10170
10171     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10172         /* If we are playing on ICS, the server decides when the
10173            game is over, but the engine can offer to draw, claim
10174            a draw, or resign.
10175          */
10176 #if ZIPPY
10177         if (appData.zippyPlay && first.initDone) {
10178             if (result == GameIsDrawn) {
10179                 /* In case draw still needs to be claimed */
10180                 SendToICS(ics_prefix);
10181                 SendToICS("draw\n");
10182             } else if (StrCaseStr(resultDetails, "resign")) {
10183                 SendToICS(ics_prefix);
10184                 SendToICS("resign\n");
10185             }
10186         }
10187 #endif
10188         endingGame = 0; /* [HGM] crash */
10189         return;
10190     }
10191
10192     /* If we're loading the game from a file, stop */
10193     if (whosays == GE_FILE) {
10194       (void) StopLoadGameTimer();
10195       gameFileFP = NULL;
10196     }
10197
10198     /* Cancel draw offers */
10199     first.offeredDraw = second.offeredDraw = 0;
10200
10201     /* If this is an ICS game, only ICS can really say it's done;
10202        if not, anyone can. */
10203     isIcsGame = (gameMode == IcsPlayingWhite ||
10204                  gameMode == IcsPlayingBlack ||
10205                  gameMode == IcsObserving    ||
10206                  gameMode == IcsExamining);
10207
10208     if (!isIcsGame || whosays == GE_ICS) {
10209         /* OK -- not an ICS game, or ICS said it was done */
10210         StopClocks();
10211         if (!isIcsGame && !appData.noChessProgram)
10212           SetUserThinkingEnables();
10213
10214         /* [HGM] if a machine claims the game end we verify this claim */
10215         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10216             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10217                 char claimer;
10218                 ChessMove trueResult = (ChessMove) -1;
10219
10220                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10221                                             first.twoMachinesColor[0] :
10222                                             second.twoMachinesColor[0] ;
10223
10224                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10225                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10226                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10227                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10228                 } else
10229                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10230                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10231                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10232                 } else
10233                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10234                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10235                 }
10236
10237                 // now verify win claims, but not in drop games, as we don't understand those yet
10238                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10239                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10240                     (result == WhiteWins && claimer == 'w' ||
10241                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10242                       if (appData.debugMode) {
10243                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10244                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10245                       }
10246                       if(result != trueResult) {
10247                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10248                               result = claimer == 'w' ? BlackWins : WhiteWins;
10249                               resultDetails = buf;
10250                       }
10251                 } else
10252                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10253                     && (forwardMostMove <= backwardMostMove ||
10254                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10255                         (claimer=='b')==(forwardMostMove&1))
10256                                                                                   ) {
10257                       /* [HGM] verify: draws that were not flagged are false claims */
10258                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10259                       result = claimer == 'w' ? BlackWins : WhiteWins;
10260                       resultDetails = buf;
10261                 }
10262                 /* (Claiming a loss is accepted no questions asked!) */
10263             }
10264             /* [HGM] bare: don't allow bare King to win */
10265             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10266                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10267                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10268                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10269                && result != GameIsDrawn)
10270             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10271                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10272                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10273                         if(p >= 0 && p <= (int)WhiteKing) k++;
10274                 }
10275                 if (appData.debugMode) {
10276                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10277                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10278                 }
10279                 if(k <= 1) {
10280                         result = GameIsDrawn;
10281                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10282                         resultDetails = buf;
10283                 }
10284             }
10285         }
10286
10287
10288         if(serverMoves != NULL && !loadFlag) { char c = '=';
10289             if(result==WhiteWins) c = '+';
10290             if(result==BlackWins) c = '-';
10291             if(resultDetails != NULL)
10292                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10293         }
10294         if (resultDetails != NULL) {
10295             gameInfo.result = result;
10296             gameInfo.resultDetails = StrSave(resultDetails);
10297
10298             /* display last move only if game was not loaded from file */
10299             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10300                 DisplayMove(currentMove - 1);
10301
10302             if (forwardMostMove != 0) {
10303                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10304                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10305                                                                 ) {
10306                     if (*appData.saveGameFile != NULLCHAR) {
10307                         SaveGameToFile(appData.saveGameFile, TRUE);
10308                     } else if (appData.autoSaveGames) {
10309                         AutoSaveGame();
10310                     }
10311                     if (*appData.savePositionFile != NULLCHAR) {
10312                         SavePositionToFile(appData.savePositionFile);
10313                     }
10314                 }
10315             }
10316
10317             /* Tell program how game ended in case it is learning */
10318             /* [HGM] Moved this to after saving the PGN, just in case */
10319             /* engine died and we got here through time loss. In that */
10320             /* case we will get a fatal error writing the pipe, which */
10321             /* would otherwise lose us the PGN.                       */
10322             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10323             /* output during GameEnds should never be fatal anymore   */
10324             if (gameMode == MachinePlaysWhite ||
10325                 gameMode == MachinePlaysBlack ||
10326                 gameMode == TwoMachinesPlay ||
10327                 gameMode == IcsPlayingWhite ||
10328                 gameMode == IcsPlayingBlack ||
10329                 gameMode == BeginningOfGame) {
10330                 char buf[MSG_SIZ];
10331                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10332                         resultDetails);
10333                 if (first.pr != NoProc) {
10334                     SendToProgram(buf, &first);
10335                 }
10336                 if (second.pr != NoProc &&
10337                     gameMode == TwoMachinesPlay) {
10338                     SendToProgram(buf, &second);
10339                 }
10340             }
10341         }
10342
10343         if (appData.icsActive) {
10344             if (appData.quietPlay &&
10345                 (gameMode == IcsPlayingWhite ||
10346                  gameMode == IcsPlayingBlack)) {
10347                 SendToICS(ics_prefix);
10348                 SendToICS("set shout 1\n");
10349             }
10350             nextGameMode = IcsIdle;
10351             ics_user_moved = FALSE;
10352             /* clean up premove.  It's ugly when the game has ended and the
10353              * premove highlights are still on the board.
10354              */
10355             if (gotPremove) {
10356               gotPremove = FALSE;
10357               ClearPremoveHighlights();
10358               DrawPosition(FALSE, boards[currentMove]);
10359             }
10360             if (whosays == GE_ICS) {
10361                 switch (result) {
10362                 case WhiteWins:
10363                     if (gameMode == IcsPlayingWhite)
10364                         PlayIcsWinSound();
10365                     else if(gameMode == IcsPlayingBlack)
10366                         PlayIcsLossSound();
10367                     break;
10368                 case BlackWins:
10369                     if (gameMode == IcsPlayingBlack)
10370                         PlayIcsWinSound();
10371                     else if(gameMode == IcsPlayingWhite)
10372                         PlayIcsLossSound();
10373                     break;
10374                 case GameIsDrawn:
10375                     PlayIcsDrawSound();
10376                     break;
10377                 default:
10378                     PlayIcsUnfinishedSound();
10379                 }
10380             }
10381         } else if (gameMode == EditGame ||
10382                    gameMode == PlayFromGameFile ||
10383                    gameMode == AnalyzeMode ||
10384                    gameMode == AnalyzeFile) {
10385             nextGameMode = gameMode;
10386         } else {
10387             nextGameMode = EndOfGame;
10388         }
10389         pausing = FALSE;
10390         ModeHighlight();
10391     } else {
10392         nextGameMode = gameMode;
10393     }
10394
10395     if (appData.noChessProgram) {
10396         gameMode = nextGameMode;
10397         ModeHighlight();
10398         endingGame = 0; /* [HGM] crash */
10399         return;
10400     }
10401
10402     if (first.reuse) {
10403         /* Put first chess program into idle state */
10404         if (first.pr != NoProc &&
10405             (gameMode == MachinePlaysWhite ||
10406              gameMode == MachinePlaysBlack ||
10407              gameMode == TwoMachinesPlay ||
10408              gameMode == IcsPlayingWhite ||
10409              gameMode == IcsPlayingBlack ||
10410              gameMode == BeginningOfGame)) {
10411             SendToProgram("force\n", &first);
10412             if (first.usePing) {
10413               char buf[MSG_SIZ];
10414               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10415               SendToProgram(buf, &first);
10416             }
10417         }
10418     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10419         /* Kill off first chess program */
10420         if (first.isr != NULL)
10421           RemoveInputSource(first.isr);
10422         first.isr = NULL;
10423
10424         if (first.pr != NoProc) {
10425             ExitAnalyzeMode();
10426             DoSleep( appData.delayBeforeQuit );
10427             SendToProgram("quit\n", &first);
10428             DoSleep( appData.delayAfterQuit );
10429             DestroyChildProcess(first.pr, first.useSigterm);
10430         }
10431         first.pr = NoProc;
10432     }
10433     if (second.reuse) {
10434         /* Put second chess program into idle state */
10435         if (second.pr != NoProc &&
10436             gameMode == TwoMachinesPlay) {
10437             SendToProgram("force\n", &second);
10438             if (second.usePing) {
10439               char buf[MSG_SIZ];
10440               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10441               SendToProgram(buf, &second);
10442             }
10443         }
10444     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10445         /* Kill off second chess program */
10446         if (second.isr != NULL)
10447           RemoveInputSource(second.isr);
10448         second.isr = NULL;
10449
10450         if (second.pr != NoProc) {
10451             DoSleep( appData.delayBeforeQuit );
10452             SendToProgram("quit\n", &second);
10453             DoSleep( appData.delayAfterQuit );
10454             DestroyChildProcess(second.pr, second.useSigterm);
10455         }
10456         second.pr = NoProc;
10457     }
10458
10459     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10460         char resChar = '=';
10461         switch (result) {
10462         case WhiteWins:
10463           resChar = '+';
10464           if (first.twoMachinesColor[0] == 'w') {
10465             first.matchWins++;
10466           } else {
10467             second.matchWins++;
10468           }
10469           break;
10470         case BlackWins:
10471           resChar = '-';
10472           if (first.twoMachinesColor[0] == 'b') {
10473             first.matchWins++;
10474           } else {
10475             second.matchWins++;
10476           }
10477           break;
10478         case GameUnfinished:
10479           resChar = ' ';
10480         default:
10481           break;
10482         }
10483
10484         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10485         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10486             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10487             ReserveGame(nextGame, resChar); // sets nextGame
10488             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10489             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10490         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10491
10492         if (nextGame <= appData.matchGames && !abortMatch) {
10493             gameMode = nextGameMode;
10494             matchGame = nextGame; // this will be overruled in tourney mode!
10495             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10496             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10497             endingGame = 0; /* [HGM] crash */
10498             return;
10499         } else {
10500             gameMode = nextGameMode;
10501             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10502                      first.tidy, second.tidy,
10503                      first.matchWins, second.matchWins,
10504                      appData.matchGames - (first.matchWins + second.matchWins));
10505             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10506             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10507             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10508             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10509                 first.twoMachinesColor = "black\n";
10510                 second.twoMachinesColor = "white\n";
10511             } else {
10512                 first.twoMachinesColor = "white\n";
10513                 second.twoMachinesColor = "black\n";
10514             }
10515         }
10516     }
10517     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10518         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10519       ExitAnalyzeMode();
10520     gameMode = nextGameMode;
10521     ModeHighlight();
10522     endingGame = 0;  /* [HGM] crash */
10523     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10524         if(matchMode == TRUE) { // match through command line: exit with or without popup
10525             if(ranking) {
10526                 ToNrEvent(forwardMostMove);
10527                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10528                 else ExitEvent(0);
10529             } else DisplayFatalError(buf, 0, 0);
10530         } else { // match through menu; just stop, with or without popup
10531             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10532             ModeHighlight();
10533             if(ranking){
10534                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10535             } else DisplayNote(buf);
10536       }
10537       if(ranking) free(ranking);
10538     }
10539 }
10540
10541 /* Assumes program was just initialized (initString sent).
10542    Leaves program in force mode. */
10543 void
10544 FeedMovesToProgram (ChessProgramState *cps, int upto)
10545 {
10546     int i;
10547
10548     if (appData.debugMode)
10549       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10550               startedFromSetupPosition ? "position and " : "",
10551               backwardMostMove, upto, cps->which);
10552     if(currentlyInitializedVariant != gameInfo.variant) {
10553       char buf[MSG_SIZ];
10554         // [HGM] variantswitch: make engine aware of new variant
10555         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10556                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10557         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10558         SendToProgram(buf, cps);
10559         currentlyInitializedVariant = gameInfo.variant;
10560     }
10561     SendToProgram("force\n", cps);
10562     if (startedFromSetupPosition) {
10563         SendBoard(cps, backwardMostMove);
10564     if (appData.debugMode) {
10565         fprintf(debugFP, "feedMoves\n");
10566     }
10567     }
10568     for (i = backwardMostMove; i < upto; i++) {
10569         SendMoveToProgram(i, cps);
10570     }
10571 }
10572
10573
10574 int
10575 ResurrectChessProgram ()
10576 {
10577      /* The chess program may have exited.
10578         If so, restart it and feed it all the moves made so far. */
10579     static int doInit = 0;
10580
10581     if (appData.noChessProgram) return 1;
10582
10583     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10584         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10585         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10586         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10587     } else {
10588         if (first.pr != NoProc) return 1;
10589         StartChessProgram(&first);
10590     }
10591     InitChessProgram(&first, FALSE);
10592     FeedMovesToProgram(&first, currentMove);
10593
10594     if (!first.sendTime) {
10595         /* can't tell gnuchess what its clock should read,
10596            so we bow to its notion. */
10597         ResetClocks();
10598         timeRemaining[0][currentMove] = whiteTimeRemaining;
10599         timeRemaining[1][currentMove] = blackTimeRemaining;
10600     }
10601
10602     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10603                 appData.icsEngineAnalyze) && first.analysisSupport) {
10604       SendToProgram("analyze\n", &first);
10605       first.analyzing = TRUE;
10606     }
10607     return 1;
10608 }
10609
10610 /*
10611  * Button procedures
10612  */
10613 void
10614 Reset (int redraw, int init)
10615 {
10616     int i;
10617
10618     if (appData.debugMode) {
10619         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10620                 redraw, init, gameMode);
10621     }
10622     CleanupTail(); // [HGM] vari: delete any stored variations
10623     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10624     pausing = pauseExamInvalid = FALSE;
10625     startedFromSetupPosition = blackPlaysFirst = FALSE;
10626     firstMove = TRUE;
10627     whiteFlag = blackFlag = FALSE;
10628     userOfferedDraw = FALSE;
10629     hintRequested = bookRequested = FALSE;
10630     first.maybeThinking = FALSE;
10631     second.maybeThinking = FALSE;
10632     first.bookSuspend = FALSE; // [HGM] book
10633     second.bookSuspend = FALSE;
10634     thinkOutput[0] = NULLCHAR;
10635     lastHint[0] = NULLCHAR;
10636     ClearGameInfo(&gameInfo);
10637     gameInfo.variant = StringToVariant(appData.variant);
10638     ics_user_moved = ics_clock_paused = FALSE;
10639     ics_getting_history = H_FALSE;
10640     ics_gamenum = -1;
10641     white_holding[0] = black_holding[0] = NULLCHAR;
10642     ClearProgramStats();
10643     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10644
10645     ResetFrontEnd();
10646     ClearHighlights();
10647     flipView = appData.flipView;
10648     ClearPremoveHighlights();
10649     gotPremove = FALSE;
10650     alarmSounded = FALSE;
10651
10652     GameEnds(EndOfFile, NULL, GE_PLAYER);
10653     if(appData.serverMovesName != NULL) {
10654         /* [HGM] prepare to make moves file for broadcasting */
10655         clock_t t = clock();
10656         if(serverMoves != NULL) fclose(serverMoves);
10657         serverMoves = fopen(appData.serverMovesName, "r");
10658         if(serverMoves != NULL) {
10659             fclose(serverMoves);
10660             /* delay 15 sec before overwriting, so all clients can see end */
10661             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10662         }
10663         serverMoves = fopen(appData.serverMovesName, "w");
10664     }
10665
10666     ExitAnalyzeMode();
10667     gameMode = BeginningOfGame;
10668     ModeHighlight();
10669     if(appData.icsActive) gameInfo.variant = VariantNormal;
10670     currentMove = forwardMostMove = backwardMostMove = 0;
10671     MarkTargetSquares(1);
10672     InitPosition(redraw);
10673     for (i = 0; i < MAX_MOVES; i++) {
10674         if (commentList[i] != NULL) {
10675             free(commentList[i]);
10676             commentList[i] = NULL;
10677         }
10678     }
10679     ResetClocks();
10680     timeRemaining[0][0] = whiteTimeRemaining;
10681     timeRemaining[1][0] = blackTimeRemaining;
10682
10683     if (first.pr == NoProc) {
10684         StartChessProgram(&first);
10685     }
10686     if (init) {
10687             InitChessProgram(&first, startedFromSetupPosition);
10688     }
10689     DisplayTitle("");
10690     DisplayMessage("", "");
10691     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10692     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10693 }
10694
10695 void
10696 AutoPlayGameLoop ()
10697 {
10698     for (;;) {
10699         if (!AutoPlayOneMove())
10700           return;
10701         if (matchMode || appData.timeDelay == 0)
10702           continue;
10703         if (appData.timeDelay < 0)
10704           return;
10705         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10706         break;
10707     }
10708 }
10709
10710
10711 int
10712 AutoPlayOneMove ()
10713 {
10714     int fromX, fromY, toX, toY;
10715
10716     if (appData.debugMode) {
10717       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10718     }
10719
10720     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10721       return FALSE;
10722
10723     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10724       pvInfoList[currentMove].depth = programStats.depth;
10725       pvInfoList[currentMove].score = programStats.score;
10726       pvInfoList[currentMove].time  = 0;
10727       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10728     }
10729
10730     if (currentMove >= forwardMostMove) {
10731       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10732 //      gameMode = EndOfGame;
10733 //      ModeHighlight();
10734
10735       /* [AS] Clear current move marker at the end of a game */
10736       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10737
10738       return FALSE;
10739     }
10740
10741     toX = moveList[currentMove][2] - AAA;
10742     toY = moveList[currentMove][3] - ONE;
10743
10744     if (moveList[currentMove][1] == '@') {
10745         if (appData.highlightLastMove) {
10746             SetHighlights(-1, -1, toX, toY);
10747         }
10748     } else {
10749         fromX = moveList[currentMove][0] - AAA;
10750         fromY = moveList[currentMove][1] - ONE;
10751
10752         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10753
10754         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10755
10756         if (appData.highlightLastMove) {
10757             SetHighlights(fromX, fromY, toX, toY);
10758         }
10759     }
10760     DisplayMove(currentMove);
10761     SendMoveToProgram(currentMove++, &first);
10762     DisplayBothClocks();
10763     DrawPosition(FALSE, boards[currentMove]);
10764     // [HGM] PV info: always display, routine tests if empty
10765     DisplayComment(currentMove - 1, commentList[currentMove]);
10766     return TRUE;
10767 }
10768
10769
10770 int
10771 LoadGameOneMove (ChessMove readAhead)
10772 {
10773     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10774     char promoChar = NULLCHAR;
10775     ChessMove moveType;
10776     char move[MSG_SIZ];
10777     char *p, *q;
10778
10779     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10780         gameMode != AnalyzeMode && gameMode != Training) {
10781         gameFileFP = NULL;
10782         return FALSE;
10783     }
10784
10785     yyboardindex = forwardMostMove;
10786     if (readAhead != EndOfFile) {
10787       moveType = readAhead;
10788     } else {
10789       if (gameFileFP == NULL)
10790           return FALSE;
10791       moveType = (ChessMove) Myylex();
10792     }
10793
10794     done = FALSE;
10795     switch (moveType) {
10796       case Comment:
10797         if (appData.debugMode)
10798           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10799         p = yy_text;
10800
10801         /* append the comment but don't display it */
10802         AppendComment(currentMove, p, FALSE);
10803         return TRUE;
10804
10805       case WhiteCapturesEnPassant:
10806       case BlackCapturesEnPassant:
10807       case WhitePromotion:
10808       case BlackPromotion:
10809       case WhiteNonPromotion:
10810       case BlackNonPromotion:
10811       case NormalMove:
10812       case WhiteKingSideCastle:
10813       case WhiteQueenSideCastle:
10814       case BlackKingSideCastle:
10815       case BlackQueenSideCastle:
10816       case WhiteKingSideCastleWild:
10817       case WhiteQueenSideCastleWild:
10818       case BlackKingSideCastleWild:
10819       case BlackQueenSideCastleWild:
10820       /* PUSH Fabien */
10821       case WhiteHSideCastleFR:
10822       case WhiteASideCastleFR:
10823       case BlackHSideCastleFR:
10824       case BlackASideCastleFR:
10825       /* POP Fabien */
10826         if (appData.debugMode)
10827           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10828         fromX = currentMoveString[0] - AAA;
10829         fromY = currentMoveString[1] - ONE;
10830         toX = currentMoveString[2] - AAA;
10831         toY = currentMoveString[3] - ONE;
10832         promoChar = currentMoveString[4];
10833         break;
10834
10835       case WhiteDrop:
10836       case BlackDrop:
10837         if (appData.debugMode)
10838           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10839         fromX = moveType == WhiteDrop ?
10840           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10841         (int) CharToPiece(ToLower(currentMoveString[0]));
10842         fromY = DROP_RANK;
10843         toX = currentMoveString[2] - AAA;
10844         toY = currentMoveString[3] - ONE;
10845         break;
10846
10847       case WhiteWins:
10848       case BlackWins:
10849       case GameIsDrawn:
10850       case GameUnfinished:
10851         if (appData.debugMode)
10852           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10853         p = strchr(yy_text, '{');
10854         if (p == NULL) p = strchr(yy_text, '(');
10855         if (p == NULL) {
10856             p = yy_text;
10857             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10858         } else {
10859             q = strchr(p, *p == '{' ? '}' : ')');
10860             if (q != NULL) *q = NULLCHAR;
10861             p++;
10862         }
10863         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10864         GameEnds(moveType, p, GE_FILE);
10865         done = TRUE;
10866         if (cmailMsgLoaded) {
10867             ClearHighlights();
10868             flipView = WhiteOnMove(currentMove);
10869             if (moveType == GameUnfinished) flipView = !flipView;
10870             if (appData.debugMode)
10871               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10872         }
10873         break;
10874
10875       case EndOfFile:
10876         if (appData.debugMode)
10877           fprintf(debugFP, "Parser hit end of file\n");
10878         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10879           case MT_NONE:
10880           case MT_CHECK:
10881             break;
10882           case MT_CHECKMATE:
10883           case MT_STAINMATE:
10884             if (WhiteOnMove(currentMove)) {
10885                 GameEnds(BlackWins, "Black mates", GE_FILE);
10886             } else {
10887                 GameEnds(WhiteWins, "White mates", GE_FILE);
10888             }
10889             break;
10890           case MT_STALEMATE:
10891             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10892             break;
10893         }
10894         done = TRUE;
10895         break;
10896
10897       case MoveNumberOne:
10898         if (lastLoadGameStart == GNUChessGame) {
10899             /* GNUChessGames have numbers, but they aren't move numbers */
10900             if (appData.debugMode)
10901               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10902                       yy_text, (int) moveType);
10903             return LoadGameOneMove(EndOfFile); /* tail recursion */
10904         }
10905         /* else fall thru */
10906
10907       case XBoardGame:
10908       case GNUChessGame:
10909       case PGNTag:
10910         /* Reached start of next game in file */
10911         if (appData.debugMode)
10912           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10913         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10914           case MT_NONE:
10915           case MT_CHECK:
10916             break;
10917           case MT_CHECKMATE:
10918           case MT_STAINMATE:
10919             if (WhiteOnMove(currentMove)) {
10920                 GameEnds(BlackWins, "Black mates", GE_FILE);
10921             } else {
10922                 GameEnds(WhiteWins, "White mates", GE_FILE);
10923             }
10924             break;
10925           case MT_STALEMATE:
10926             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10927             break;
10928         }
10929         done = TRUE;
10930         break;
10931
10932       case PositionDiagram:     /* should not happen; ignore */
10933       case ElapsedTime:         /* ignore */
10934       case NAG:                 /* ignore */
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10937                   yy_text, (int) moveType);
10938         return LoadGameOneMove(EndOfFile); /* tail recursion */
10939
10940       case IllegalMove:
10941         if (appData.testLegality) {
10942             if (appData.debugMode)
10943               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10944             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10945                     (forwardMostMove / 2) + 1,
10946                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947             DisplayError(move, 0);
10948             done = TRUE;
10949         } else {
10950             if (appData.debugMode)
10951               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10952                       yy_text, currentMoveString);
10953             fromX = currentMoveString[0] - AAA;
10954             fromY = currentMoveString[1] - ONE;
10955             toX = currentMoveString[2] - AAA;
10956             toY = currentMoveString[3] - ONE;
10957             promoChar = currentMoveString[4];
10958         }
10959         break;
10960
10961       case AmbiguousMove:
10962         if (appData.debugMode)
10963           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10964         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10965                 (forwardMostMove / 2) + 1,
10966                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10967         DisplayError(move, 0);
10968         done = TRUE;
10969         break;
10970
10971       default:
10972       case ImpossibleMove:
10973         if (appData.debugMode)
10974           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10975         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10976                 (forwardMostMove / 2) + 1,
10977                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10978         DisplayError(move, 0);
10979         done = TRUE;
10980         break;
10981     }
10982
10983     if (done) {
10984         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10985             DrawPosition(FALSE, boards[currentMove]);
10986             DisplayBothClocks();
10987             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10988               DisplayComment(currentMove - 1, commentList[currentMove]);
10989         }
10990         (void) StopLoadGameTimer();
10991         gameFileFP = NULL;
10992         cmailOldMove = forwardMostMove;
10993         return FALSE;
10994     } else {
10995         /* currentMoveString is set as a side-effect of yylex */
10996
10997         thinkOutput[0] = NULLCHAR;
10998         MakeMove(fromX, fromY, toX, toY, promoChar);
10999         currentMove = forwardMostMove;
11000         return TRUE;
11001     }
11002 }
11003
11004 /* Load the nth game from the given file */
11005 int
11006 LoadGameFromFile (char *filename, int n, char *title, int useList)
11007 {
11008     FILE *f;
11009     char buf[MSG_SIZ];
11010
11011     if (strcmp(filename, "-") == 0) {
11012         f = stdin;
11013         title = "stdin";
11014     } else {
11015         f = fopen(filename, "rb");
11016         if (f == NULL) {
11017           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11018             DisplayError(buf, errno);
11019             return FALSE;
11020         }
11021     }
11022     if (fseek(f, 0, 0) == -1) {
11023         /* f is not seekable; probably a pipe */
11024         useList = FALSE;
11025     }
11026     if (useList && n == 0) {
11027         int error = GameListBuild(f);
11028         if (error) {
11029             DisplayError(_("Cannot build game list"), error);
11030         } else if (!ListEmpty(&gameList) &&
11031                    ((ListGame *) gameList.tailPred)->number > 1) {
11032             GameListPopUp(f, title);
11033             return TRUE;
11034         }
11035         GameListDestroy();
11036         n = 1;
11037     }
11038     if (n == 0) n = 1;
11039     return LoadGame(f, n, title, FALSE);
11040 }
11041
11042
11043 void
11044 MakeRegisteredMove ()
11045 {
11046     int fromX, fromY, toX, toY;
11047     char promoChar;
11048     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11049         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11050           case CMAIL_MOVE:
11051           case CMAIL_DRAW:
11052             if (appData.debugMode)
11053               fprintf(debugFP, "Restoring %s for game %d\n",
11054                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11055
11056             thinkOutput[0] = NULLCHAR;
11057             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11058             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11059             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11060             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11061             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11062             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11063             MakeMove(fromX, fromY, toX, toY, promoChar);
11064             ShowMove(fromX, fromY, toX, toY);
11065
11066             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11067               case MT_NONE:
11068               case MT_CHECK:
11069                 break;
11070
11071               case MT_CHECKMATE:
11072               case MT_STAINMATE:
11073                 if (WhiteOnMove(currentMove)) {
11074                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11075                 } else {
11076                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11077                 }
11078                 break;
11079
11080               case MT_STALEMATE:
11081                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11082                 break;
11083             }
11084
11085             break;
11086
11087           case CMAIL_RESIGN:
11088             if (WhiteOnMove(currentMove)) {
11089                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11090             } else {
11091                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11092             }
11093             break;
11094
11095           case CMAIL_ACCEPT:
11096             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11097             break;
11098
11099           default:
11100             break;
11101         }
11102     }
11103
11104     return;
11105 }
11106
11107 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11108 int
11109 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11110 {
11111     int retVal;
11112
11113     if (gameNumber > nCmailGames) {
11114         DisplayError(_("No more games in this message"), 0);
11115         return FALSE;
11116     }
11117     if (f == lastLoadGameFP) {
11118         int offset = gameNumber - lastLoadGameNumber;
11119         if (offset == 0) {
11120             cmailMsg[0] = NULLCHAR;
11121             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11122                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11123                 nCmailMovesRegistered--;
11124             }
11125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11126             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11127                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11128             }
11129         } else {
11130             if (! RegisterMove()) return FALSE;
11131         }
11132     }
11133
11134     retVal = LoadGame(f, gameNumber, title, useList);
11135
11136     /* Make move registered during previous look at this game, if any */
11137     MakeRegisteredMove();
11138
11139     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11140         commentList[currentMove]
11141           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11142         DisplayComment(currentMove - 1, commentList[currentMove]);
11143     }
11144
11145     return retVal;
11146 }
11147
11148 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11149 int
11150 ReloadGame (int offset)
11151 {
11152     int gameNumber = lastLoadGameNumber + offset;
11153     if (lastLoadGameFP == NULL) {
11154         DisplayError(_("No game has been loaded yet"), 0);
11155         return FALSE;
11156     }
11157     if (gameNumber <= 0) {
11158         DisplayError(_("Can't back up any further"), 0);
11159         return FALSE;
11160     }
11161     if (cmailMsgLoaded) {
11162         return CmailLoadGame(lastLoadGameFP, gameNumber,
11163                              lastLoadGameTitle, lastLoadGameUseList);
11164     } else {
11165         return LoadGame(lastLoadGameFP, gameNumber,
11166                         lastLoadGameTitle, lastLoadGameUseList);
11167     }
11168 }
11169
11170 int keys[EmptySquare+1];
11171
11172 int
11173 PositionMatches (Board b1, Board b2)
11174 {
11175     int r, f, sum=0;
11176     switch(appData.searchMode) {
11177         case 1: return CompareWithRights(b1, b2);
11178         case 2:
11179             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11180                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11181             }
11182             return TRUE;
11183         case 3:
11184             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11185               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11186                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11187             }
11188             return sum==0;
11189         case 4:
11190             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11191                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11192             }
11193             return sum==0;
11194     }
11195     return TRUE;
11196 }
11197
11198 #define Q_PROMO  4
11199 #define Q_EP     3
11200 #define Q_BCASTL 2
11201 #define Q_WCASTL 1
11202
11203 int pieceList[256], quickBoard[256];
11204 ChessSquare pieceType[256] = { EmptySquare };
11205 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11206 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11207 int soughtTotal, turn;
11208 Boolean epOK, flipSearch;
11209
11210 typedef struct {
11211     unsigned char piece, to;
11212 } Move;
11213
11214 #define DSIZE (250000)
11215
11216 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11217 Move *moveDatabase = initialSpace;
11218 unsigned int movePtr, dataSize = DSIZE;
11219
11220 int
11221 MakePieceList (Board board, int *counts)
11222 {
11223     int r, f, n=Q_PROMO, total=0;
11224     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11225     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11226         int sq = f + (r<<4);
11227         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11228             quickBoard[sq] = ++n;
11229             pieceList[n] = sq;
11230             pieceType[n] = board[r][f];
11231             counts[board[r][f]]++;
11232             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11233             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11234             total++;
11235         }
11236     }
11237     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11238     return total;
11239 }
11240
11241 void
11242 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11243 {
11244     int sq = fromX + (fromY<<4);
11245     int piece = quickBoard[sq];
11246     quickBoard[sq] = 0;
11247     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11248     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11249         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11250         moveDatabase[movePtr++].piece = Q_WCASTL;
11251         quickBoard[sq] = piece;
11252         piece = quickBoard[from]; quickBoard[from] = 0;
11253         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11254     } else
11255     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11256         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11257         moveDatabase[movePtr++].piece = Q_BCASTL;
11258         quickBoard[sq] = piece;
11259         piece = quickBoard[from]; quickBoard[from] = 0;
11260         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11261     } else
11262     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11263         quickBoard[(fromY<<4)+toX] = 0;
11264         moveDatabase[movePtr].piece = Q_EP;
11265         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11266         moveDatabase[movePtr].to = sq;
11267     } else
11268     if(promoPiece != pieceType[piece]) {
11269         moveDatabase[movePtr++].piece = Q_PROMO;
11270         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11271     }
11272     moveDatabase[movePtr].piece = piece;
11273     quickBoard[sq] = piece;
11274     movePtr++;
11275 }
11276
11277 int
11278 PackGame (Board board)
11279 {
11280     Move *newSpace = NULL;
11281     moveDatabase[movePtr].piece = 0; // terminate previous game
11282     if(movePtr > dataSize) {
11283         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11284         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11285         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11286         if(newSpace) {
11287             int i;
11288             Move *p = moveDatabase, *q = newSpace;
11289             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11290             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11291             moveDatabase = newSpace;
11292         } else { // calloc failed, we must be out of memory. Too bad...
11293             dataSize = 0; // prevent calloc events for all subsequent games
11294             return 0;     // and signal this one isn't cached
11295         }
11296     }
11297     movePtr++;
11298     MakePieceList(board, counts);
11299     return movePtr;
11300 }
11301
11302 int
11303 QuickCompare (Board board, int *minCounts, int *maxCounts)
11304 {   // compare according to search mode
11305     int r, f;
11306     switch(appData.searchMode)
11307     {
11308       case 1: // exact position match
11309         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11311             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11312         }
11313         break;
11314       case 2: // can have extra material on empty squares
11315         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11316             if(board[r][f] == EmptySquare) continue;
11317             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11318         }
11319         break;
11320       case 3: // material with exact Pawn structure
11321         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11322             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11323             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11324         } // fall through to material comparison
11325       case 4: // exact material
11326         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11327         break;
11328       case 6: // material range with given imbalance
11329         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11330         // fall through to range comparison
11331       case 5: // material range
11332         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11333     }
11334     return TRUE;
11335 }
11336
11337 int
11338 QuickScan (Board board, Move *move)
11339 {   // reconstruct game,and compare all positions in it
11340     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11341     do {
11342         int piece = move->piece;
11343         int to = move->to, from = pieceList[piece];
11344         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11345           if(!piece) return -1;
11346           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11347             piece = (++move)->piece;
11348             from = pieceList[piece];
11349             counts[pieceType[piece]]--;
11350             pieceType[piece] = (ChessSquare) move->to;
11351             counts[move->to]++;
11352           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11353             counts[pieceType[quickBoard[to]]]--;
11354             quickBoard[to] = 0; total--;
11355             move++;
11356             continue;
11357           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11358             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11359             from  = pieceList[piece]; // so this must be King
11360             quickBoard[from] = 0;
11361             quickBoard[to] = piece;
11362             pieceList[piece] = to;
11363             move++;
11364             continue;
11365           }
11366         }
11367         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11368         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11369         quickBoard[from] = 0;
11370         quickBoard[to] = piece;
11371         pieceList[piece] = to;
11372         cnt++; turn ^= 3;
11373         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11374            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11375            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11376                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11377           ) {
11378             static int lastCounts[EmptySquare+1];
11379             int i;
11380             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11381             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11382         } else stretch = 0;
11383         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11384         move++;
11385     } while(1);
11386 }
11387
11388 void
11389 InitSearch ()
11390 {
11391     int r, f;
11392     flipSearch = FALSE;
11393     CopyBoard(soughtBoard, boards[currentMove]);
11394     soughtTotal = MakePieceList(soughtBoard, maxSought);
11395     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11396     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11397     CopyBoard(reverseBoard, boards[currentMove]);
11398     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11399         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11400         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11401         reverseBoard[r][f] = piece;
11402     }
11403     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11404     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11405     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11406                  || (boards[currentMove][CASTLING][2] == NoRights || 
11407                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11408                  && (boards[currentMove][CASTLING][5] == NoRights || 
11409                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11410       ) {
11411         flipSearch = TRUE;
11412         CopyBoard(flipBoard, soughtBoard);
11413         CopyBoard(rotateBoard, reverseBoard);
11414         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11415             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11416             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11417         }
11418     }
11419     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11420     if(appData.searchMode >= 5) {
11421         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11422         MakePieceList(soughtBoard, minSought);
11423         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11424     }
11425     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11426         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11427 }
11428
11429 GameInfo dummyInfo;
11430
11431 int
11432 GameContainsPosition (FILE *f, ListGame *lg)
11433 {
11434     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11435     int fromX, fromY, toX, toY;
11436     char promoChar;
11437     static int initDone=FALSE;
11438
11439     // weed out games based on numerical tag comparison
11440     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11441     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11442     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11443     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11444     if(!initDone) {
11445         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11446         initDone = TRUE;
11447     }
11448     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11449     else CopyBoard(boards[scratch], initialPosition); // default start position
11450     if(lg->moves) {
11451         turn = btm + 1;
11452         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11453         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11454     }
11455     if(btm) plyNr++;
11456     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11457     fseek(f, lg->offset, 0);
11458     yynewfile(f);
11459     while(1) {
11460         yyboardindex = scratch;
11461         quickFlag = plyNr+1;
11462         next = Myylex();
11463         quickFlag = 0;
11464         switch(next) {
11465             case PGNTag:
11466                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11467             default:
11468                 continue;
11469
11470             case XBoardGame:
11471             case GNUChessGame:
11472                 if(plyNr) return -1; // after we have seen moves, this is for new game
11473               continue;
11474
11475             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11476             case ImpossibleMove:
11477             case WhiteWins: // game ends here with these four
11478             case BlackWins:
11479             case GameIsDrawn:
11480             case GameUnfinished:
11481                 return -1;
11482
11483             case IllegalMove:
11484                 if(appData.testLegality) return -1;
11485             case WhiteCapturesEnPassant:
11486             case BlackCapturesEnPassant:
11487             case WhitePromotion:
11488             case BlackPromotion:
11489             case WhiteNonPromotion:
11490             case BlackNonPromotion:
11491             case NormalMove:
11492             case WhiteKingSideCastle:
11493             case WhiteQueenSideCastle:
11494             case BlackKingSideCastle:
11495             case BlackQueenSideCastle:
11496             case WhiteKingSideCastleWild:
11497             case WhiteQueenSideCastleWild:
11498             case BlackKingSideCastleWild:
11499             case BlackQueenSideCastleWild:
11500             case WhiteHSideCastleFR:
11501             case WhiteASideCastleFR:
11502             case BlackHSideCastleFR:
11503             case BlackASideCastleFR:
11504                 fromX = currentMoveString[0] - AAA;
11505                 fromY = currentMoveString[1] - ONE;
11506                 toX = currentMoveString[2] - AAA;
11507                 toY = currentMoveString[3] - ONE;
11508                 promoChar = currentMoveString[4];
11509                 break;
11510             case WhiteDrop:
11511             case BlackDrop:
11512                 fromX = next == WhiteDrop ?
11513                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11514                   (int) CharToPiece(ToLower(currentMoveString[0]));
11515                 fromY = DROP_RANK;
11516                 toX = currentMoveString[2] - AAA;
11517                 toY = currentMoveString[3] - ONE;
11518                 promoChar = 0;
11519                 break;
11520         }
11521         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11522         plyNr++;
11523         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11524         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11525         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11526         if(appData.findMirror) {
11527             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11528             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11529         }
11530     }
11531 }
11532
11533 /* Load the nth game from open file f */
11534 int
11535 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11536 {
11537     ChessMove cm;
11538     char buf[MSG_SIZ];
11539     int gn = gameNumber;
11540     ListGame *lg = NULL;
11541     int numPGNTags = 0;
11542     int err, pos = -1;
11543     GameMode oldGameMode;
11544     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11545
11546     if (appData.debugMode)
11547         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11548
11549     if (gameMode == Training )
11550         SetTrainingModeOff();
11551
11552     oldGameMode = gameMode;
11553     if (gameMode != BeginningOfGame) {
11554       Reset(FALSE, TRUE);
11555     }
11556
11557     gameFileFP = f;
11558     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11559         fclose(lastLoadGameFP);
11560     }
11561
11562     if (useList) {
11563         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11564
11565         if (lg) {
11566             fseek(f, lg->offset, 0);
11567             GameListHighlight(gameNumber);
11568             pos = lg->position;
11569             gn = 1;
11570         }
11571         else {
11572             DisplayError(_("Game number out of range"), 0);
11573             return FALSE;
11574         }
11575     } else {
11576         GameListDestroy();
11577         if (fseek(f, 0, 0) == -1) {
11578             if (f == lastLoadGameFP ?
11579                 gameNumber == lastLoadGameNumber + 1 :
11580                 gameNumber == 1) {
11581                 gn = 1;
11582             } else {
11583                 DisplayError(_("Can't seek on game file"), 0);
11584                 return FALSE;
11585             }
11586         }
11587     }
11588     lastLoadGameFP = f;
11589     lastLoadGameNumber = gameNumber;
11590     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11591     lastLoadGameUseList = useList;
11592
11593     yynewfile(f);
11594
11595     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11596       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11597                 lg->gameInfo.black);
11598             DisplayTitle(buf);
11599     } else if (*title != NULLCHAR) {
11600         if (gameNumber > 1) {
11601           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11602             DisplayTitle(buf);
11603         } else {
11604             DisplayTitle(title);
11605         }
11606     }
11607
11608     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11609         gameMode = PlayFromGameFile;
11610         ModeHighlight();
11611     }
11612
11613     currentMove = forwardMostMove = backwardMostMove = 0;
11614     CopyBoard(boards[0], initialPosition);
11615     StopClocks();
11616
11617     /*
11618      * Skip the first gn-1 games in the file.
11619      * Also skip over anything that precedes an identifiable
11620      * start of game marker, to avoid being confused by
11621      * garbage at the start of the file.  Currently
11622      * recognized start of game markers are the move number "1",
11623      * the pattern "gnuchess .* game", the pattern
11624      * "^[#;%] [^ ]* game file", and a PGN tag block.
11625      * A game that starts with one of the latter two patterns
11626      * will also have a move number 1, possibly
11627      * following a position diagram.
11628      * 5-4-02: Let's try being more lenient and allowing a game to
11629      * start with an unnumbered move.  Does that break anything?
11630      */
11631     cm = lastLoadGameStart = EndOfFile;
11632     while (gn > 0) {
11633         yyboardindex = forwardMostMove;
11634         cm = (ChessMove) Myylex();
11635         switch (cm) {
11636           case EndOfFile:
11637             if (cmailMsgLoaded) {
11638                 nCmailGames = CMAIL_MAX_GAMES - gn;
11639             } else {
11640                 Reset(TRUE, TRUE);
11641                 DisplayError(_("Game not found in file"), 0);
11642             }
11643             return FALSE;
11644
11645           case GNUChessGame:
11646           case XBoardGame:
11647             gn--;
11648             lastLoadGameStart = cm;
11649             break;
11650
11651           case MoveNumberOne:
11652             switch (lastLoadGameStart) {
11653               case GNUChessGame:
11654               case XBoardGame:
11655               case PGNTag:
11656                 break;
11657               case MoveNumberOne:
11658               case EndOfFile:
11659                 gn--;           /* count this game */
11660                 lastLoadGameStart = cm;
11661                 break;
11662               default:
11663                 /* impossible */
11664                 break;
11665             }
11666             break;
11667
11668           case PGNTag:
11669             switch (lastLoadGameStart) {
11670               case GNUChessGame:
11671               case PGNTag:
11672               case MoveNumberOne:
11673               case EndOfFile:
11674                 gn--;           /* count this game */
11675                 lastLoadGameStart = cm;
11676                 break;
11677               case XBoardGame:
11678                 lastLoadGameStart = cm; /* game counted already */
11679                 break;
11680               default:
11681                 /* impossible */
11682                 break;
11683             }
11684             if (gn > 0) {
11685                 do {
11686                     yyboardindex = forwardMostMove;
11687                     cm = (ChessMove) Myylex();
11688                 } while (cm == PGNTag || cm == Comment);
11689             }
11690             break;
11691
11692           case WhiteWins:
11693           case BlackWins:
11694           case GameIsDrawn:
11695             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11696                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11697                     != CMAIL_OLD_RESULT) {
11698                     nCmailResults ++ ;
11699                     cmailResult[  CMAIL_MAX_GAMES
11700                                 - gn - 1] = CMAIL_OLD_RESULT;
11701                 }
11702             }
11703             break;
11704
11705           case NormalMove:
11706             /* Only a NormalMove can be at the start of a game
11707              * without a position diagram. */
11708             if (lastLoadGameStart == EndOfFile ) {
11709               gn--;
11710               lastLoadGameStart = MoveNumberOne;
11711             }
11712             break;
11713
11714           default:
11715             break;
11716         }
11717     }
11718
11719     if (appData.debugMode)
11720       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11721
11722     if (cm == XBoardGame) {
11723         /* Skip any header junk before position diagram and/or move 1 */
11724         for (;;) {
11725             yyboardindex = forwardMostMove;
11726             cm = (ChessMove) Myylex();
11727
11728             if (cm == EndOfFile ||
11729                 cm == GNUChessGame || cm == XBoardGame) {
11730                 /* Empty game; pretend end-of-file and handle later */
11731                 cm = EndOfFile;
11732                 break;
11733             }
11734
11735             if (cm == MoveNumberOne || cm == PositionDiagram ||
11736                 cm == PGNTag || cm == Comment)
11737               break;
11738         }
11739     } else if (cm == GNUChessGame) {
11740         if (gameInfo.event != NULL) {
11741             free(gameInfo.event);
11742         }
11743         gameInfo.event = StrSave(yy_text);
11744     }
11745
11746     startedFromSetupPosition = FALSE;
11747     while (cm == PGNTag) {
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11750         err = ParsePGNTag(yy_text, &gameInfo);
11751         if (!err) numPGNTags++;
11752
11753         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11754         if(gameInfo.variant != oldVariant) {
11755             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11756             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11757             InitPosition(TRUE);
11758             oldVariant = gameInfo.variant;
11759             if (appData.debugMode)
11760               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11761         }
11762
11763
11764         if (gameInfo.fen != NULL) {
11765           Board initial_position;
11766           startedFromSetupPosition = TRUE;
11767           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11768             Reset(TRUE, TRUE);
11769             DisplayError(_("Bad FEN position in file"), 0);
11770             return FALSE;
11771           }
11772           CopyBoard(boards[0], initial_position);
11773           if (blackPlaysFirst) {
11774             currentMove = forwardMostMove = backwardMostMove = 1;
11775             CopyBoard(boards[1], initial_position);
11776             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11777             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11778             timeRemaining[0][1] = whiteTimeRemaining;
11779             timeRemaining[1][1] = blackTimeRemaining;
11780             if (commentList[0] != NULL) {
11781               commentList[1] = commentList[0];
11782               commentList[0] = NULL;
11783             }
11784           } else {
11785             currentMove = forwardMostMove = backwardMostMove = 0;
11786           }
11787           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11788           {   int i;
11789               initialRulePlies = FENrulePlies;
11790               for( i=0; i< nrCastlingRights; i++ )
11791                   initialRights[i] = initial_position[CASTLING][i];
11792           }
11793           yyboardindex = forwardMostMove;
11794           free(gameInfo.fen);
11795           gameInfo.fen = NULL;
11796         }
11797
11798         yyboardindex = forwardMostMove;
11799         cm = (ChessMove) Myylex();
11800
11801         /* Handle comments interspersed among the tags */
11802         while (cm == Comment) {
11803             char *p;
11804             if (appData.debugMode)
11805               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11806             p = yy_text;
11807             AppendComment(currentMove, p, FALSE);
11808             yyboardindex = forwardMostMove;
11809             cm = (ChessMove) Myylex();
11810         }
11811     }
11812
11813     /* don't rely on existence of Event tag since if game was
11814      * pasted from clipboard the Event tag may not exist
11815      */
11816     if (numPGNTags > 0){
11817         char *tags;
11818         if (gameInfo.variant == VariantNormal) {
11819           VariantClass v = StringToVariant(gameInfo.event);
11820           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11821           if(v < VariantShogi) gameInfo.variant = v;
11822         }
11823         if (!matchMode) {
11824           if( appData.autoDisplayTags ) {
11825             tags = PGNTags(&gameInfo);
11826             TagsPopUp(tags, CmailMsg());
11827             free(tags);
11828           }
11829         }
11830     } else {
11831         /* Make something up, but don't display it now */
11832         SetGameInfo();
11833         TagsPopDown();
11834     }
11835
11836     if (cm == PositionDiagram) {
11837         int i, j;
11838         char *p;
11839         Board initial_position;
11840
11841         if (appData.debugMode)
11842           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11843
11844         if (!startedFromSetupPosition) {
11845             p = yy_text;
11846             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11847               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11848                 switch (*p) {
11849                   case '{':
11850                   case '[':
11851                   case '-':
11852                   case ' ':
11853                   case '\t':
11854                   case '\n':
11855                   case '\r':
11856                     break;
11857                   default:
11858                     initial_position[i][j++] = CharToPiece(*p);
11859                     break;
11860                 }
11861             while (*p == ' ' || *p == '\t' ||
11862                    *p == '\n' || *p == '\r') p++;
11863
11864             if (strncmp(p, "black", strlen("black"))==0)
11865               blackPlaysFirst = TRUE;
11866             else
11867               blackPlaysFirst = FALSE;
11868             startedFromSetupPosition = TRUE;
11869
11870             CopyBoard(boards[0], initial_position);
11871             if (blackPlaysFirst) {
11872                 currentMove = forwardMostMove = backwardMostMove = 1;
11873                 CopyBoard(boards[1], initial_position);
11874                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11875                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11876                 timeRemaining[0][1] = whiteTimeRemaining;
11877                 timeRemaining[1][1] = blackTimeRemaining;
11878                 if (commentList[0] != NULL) {
11879                     commentList[1] = commentList[0];
11880                     commentList[0] = NULL;
11881                 }
11882             } else {
11883                 currentMove = forwardMostMove = backwardMostMove = 0;
11884             }
11885         }
11886         yyboardindex = forwardMostMove;
11887         cm = (ChessMove) Myylex();
11888     }
11889
11890     if (first.pr == NoProc) {
11891         StartChessProgram(&first);
11892     }
11893     InitChessProgram(&first, FALSE);
11894     SendToProgram("force\n", &first);
11895     if (startedFromSetupPosition) {
11896         SendBoard(&first, forwardMostMove);
11897     if (appData.debugMode) {
11898         fprintf(debugFP, "Load Game\n");
11899     }
11900         DisplayBothClocks();
11901     }
11902
11903     /* [HGM] server: flag to write setup moves in broadcast file as one */
11904     loadFlag = appData.suppressLoadMoves;
11905
11906     while (cm == Comment) {
11907         char *p;
11908         if (appData.debugMode)
11909           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11910         p = yy_text;
11911         AppendComment(currentMove, p, FALSE);
11912         yyboardindex = forwardMostMove;
11913         cm = (ChessMove) Myylex();
11914     }
11915
11916     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11917         cm == WhiteWins || cm == BlackWins ||
11918         cm == GameIsDrawn || cm == GameUnfinished) {
11919         DisplayMessage("", _("No moves in game"));
11920         if (cmailMsgLoaded) {
11921             if (appData.debugMode)
11922               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11923             ClearHighlights();
11924             flipView = FALSE;
11925         }
11926         DrawPosition(FALSE, boards[currentMove]);
11927         DisplayBothClocks();
11928         gameMode = EditGame;
11929         ModeHighlight();
11930         gameFileFP = NULL;
11931         cmailOldMove = 0;
11932         return TRUE;
11933     }
11934
11935     // [HGM] PV info: routine tests if comment empty
11936     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11937         DisplayComment(currentMove - 1, commentList[currentMove]);
11938     }
11939     if (!matchMode && appData.timeDelay != 0)
11940       DrawPosition(FALSE, boards[currentMove]);
11941
11942     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11943       programStats.ok_to_send = 1;
11944     }
11945
11946     /* if the first token after the PGN tags is a move
11947      * and not move number 1, retrieve it from the parser
11948      */
11949     if (cm != MoveNumberOne)
11950         LoadGameOneMove(cm);
11951
11952     /* load the remaining moves from the file */
11953     while (LoadGameOneMove(EndOfFile)) {
11954       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11955       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11956     }
11957
11958     /* rewind to the start of the game */
11959     currentMove = backwardMostMove;
11960
11961     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11962
11963     if (oldGameMode == AnalyzeFile ||
11964         oldGameMode == AnalyzeMode) {
11965       AnalyzeFileEvent();
11966     }
11967
11968     if (!matchMode && pos >= 0) {
11969         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11970     } else
11971     if (matchMode || appData.timeDelay == 0) {
11972       ToEndEvent();
11973     } else if (appData.timeDelay > 0) {
11974       AutoPlayGameLoop();
11975     }
11976
11977     if (appData.debugMode)
11978         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11979
11980     loadFlag = 0; /* [HGM] true game starts */
11981     return TRUE;
11982 }
11983
11984 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11985 int
11986 ReloadPosition (int offset)
11987 {
11988     int positionNumber = lastLoadPositionNumber + offset;
11989     if (lastLoadPositionFP == NULL) {
11990         DisplayError(_("No position has been loaded yet"), 0);
11991         return FALSE;
11992     }
11993     if (positionNumber <= 0) {
11994         DisplayError(_("Can't back up any further"), 0);
11995         return FALSE;
11996     }
11997     return LoadPosition(lastLoadPositionFP, positionNumber,
11998                         lastLoadPositionTitle);
11999 }
12000
12001 /* Load the nth position from the given file */
12002 int
12003 LoadPositionFromFile (char *filename, int n, char *title)
12004 {
12005     FILE *f;
12006     char buf[MSG_SIZ];
12007
12008     if (strcmp(filename, "-") == 0) {
12009         return LoadPosition(stdin, n, "stdin");
12010     } else {
12011         f = fopen(filename, "rb");
12012         if (f == NULL) {
12013             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12014             DisplayError(buf, errno);
12015             return FALSE;
12016         } else {
12017             return LoadPosition(f, n, title);
12018         }
12019     }
12020 }
12021
12022 /* Load the nth position from the given open file, and close it */
12023 int
12024 LoadPosition (FILE *f, int positionNumber, char *title)
12025 {
12026     char *p, line[MSG_SIZ];
12027     Board initial_position;
12028     int i, j, fenMode, pn;
12029
12030     if (gameMode == Training )
12031         SetTrainingModeOff();
12032
12033     if (gameMode != BeginningOfGame) {
12034         Reset(FALSE, TRUE);
12035     }
12036     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12037         fclose(lastLoadPositionFP);
12038     }
12039     if (positionNumber == 0) positionNumber = 1;
12040     lastLoadPositionFP = f;
12041     lastLoadPositionNumber = positionNumber;
12042     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12043     if (first.pr == NoProc && !appData.noChessProgram) {
12044       StartChessProgram(&first);
12045       InitChessProgram(&first, FALSE);
12046     }
12047     pn = positionNumber;
12048     if (positionNumber < 0) {
12049         /* Negative position number means to seek to that byte offset */
12050         if (fseek(f, -positionNumber, 0) == -1) {
12051             DisplayError(_("Can't seek on position file"), 0);
12052             return FALSE;
12053         };
12054         pn = 1;
12055     } else {
12056         if (fseek(f, 0, 0) == -1) {
12057             if (f == lastLoadPositionFP ?
12058                 positionNumber == lastLoadPositionNumber + 1 :
12059                 positionNumber == 1) {
12060                 pn = 1;
12061             } else {
12062                 DisplayError(_("Can't seek on position file"), 0);
12063                 return FALSE;
12064             }
12065         }
12066     }
12067     /* See if this file is FEN or old-style xboard */
12068     if (fgets(line, MSG_SIZ, f) == NULL) {
12069         DisplayError(_("Position not found in file"), 0);
12070         return FALSE;
12071     }
12072     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12073     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12074
12075     if (pn >= 2) {
12076         if (fenMode || line[0] == '#') pn--;
12077         while (pn > 0) {
12078             /* skip positions before number pn */
12079             if (fgets(line, MSG_SIZ, f) == NULL) {
12080                 Reset(TRUE, TRUE);
12081                 DisplayError(_("Position not found in file"), 0);
12082                 return FALSE;
12083             }
12084             if (fenMode || line[0] == '#') pn--;
12085         }
12086     }
12087
12088     if (fenMode) {
12089         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12090             DisplayError(_("Bad FEN position in file"), 0);
12091             return FALSE;
12092         }
12093     } else {
12094         (void) fgets(line, MSG_SIZ, f);
12095         (void) fgets(line, MSG_SIZ, f);
12096
12097         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12098             (void) fgets(line, MSG_SIZ, f);
12099             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12100                 if (*p == ' ')
12101                   continue;
12102                 initial_position[i][j++] = CharToPiece(*p);
12103             }
12104         }
12105
12106         blackPlaysFirst = FALSE;
12107         if (!feof(f)) {
12108             (void) fgets(line, MSG_SIZ, f);
12109             if (strncmp(line, "black", strlen("black"))==0)
12110               blackPlaysFirst = TRUE;
12111         }
12112     }
12113     startedFromSetupPosition = TRUE;
12114
12115     CopyBoard(boards[0], initial_position);
12116     if (blackPlaysFirst) {
12117         currentMove = forwardMostMove = backwardMostMove = 1;
12118         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12119         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12120         CopyBoard(boards[1], initial_position);
12121         DisplayMessage("", _("Black to play"));
12122     } else {
12123         currentMove = forwardMostMove = backwardMostMove = 0;
12124         DisplayMessage("", _("White to play"));
12125     }
12126     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12127     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12128         SendToProgram("force\n", &first);
12129         SendBoard(&first, forwardMostMove);
12130     }
12131     if (appData.debugMode) {
12132 int i, j;
12133   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12134   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12135         fprintf(debugFP, "Load Position\n");
12136     }
12137
12138     if (positionNumber > 1) {
12139       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12140         DisplayTitle(line);
12141     } else {
12142         DisplayTitle(title);
12143     }
12144     gameMode = EditGame;
12145     ModeHighlight();
12146     ResetClocks();
12147     timeRemaining[0][1] = whiteTimeRemaining;
12148     timeRemaining[1][1] = blackTimeRemaining;
12149     DrawPosition(FALSE, boards[currentMove]);
12150
12151     return TRUE;
12152 }
12153
12154
12155 void
12156 CopyPlayerNameIntoFileName (char **dest, char *src)
12157 {
12158     while (*src != NULLCHAR && *src != ',') {
12159         if (*src == ' ') {
12160             *(*dest)++ = '_';
12161             src++;
12162         } else {
12163             *(*dest)++ = *src++;
12164         }
12165     }
12166 }
12167
12168 char *
12169 DefaultFileName (char *ext)
12170 {
12171     static char def[MSG_SIZ];
12172     char *p;
12173
12174     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12175         p = def;
12176         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12177         *p++ = '-';
12178         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12179         *p++ = '.';
12180         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12181     } else {
12182         def[0] = NULLCHAR;
12183     }
12184     return def;
12185 }
12186
12187 /* Save the current game to the given file */
12188 int
12189 SaveGameToFile (char *filename, int append)
12190 {
12191     FILE *f;
12192     char buf[MSG_SIZ];
12193     int result, i, t,tot=0;
12194
12195     if (strcmp(filename, "-") == 0) {
12196         return SaveGame(stdout, 0, NULL);
12197     } else {
12198         for(i=0; i<10; i++) { // upto 10 tries
12199              f = fopen(filename, append ? "a" : "w");
12200              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12201              if(f || errno != 13) break;
12202              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12203              tot += t;
12204         }
12205         if (f == NULL) {
12206             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12207             DisplayError(buf, errno);
12208             return FALSE;
12209         } else {
12210             safeStrCpy(buf, lastMsg, MSG_SIZ);
12211             DisplayMessage(_("Waiting for access to save file"), "");
12212             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12213             DisplayMessage(_("Saving game"), "");
12214             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12215             result = SaveGame(f, 0, NULL);
12216             DisplayMessage(buf, "");
12217             return result;
12218         }
12219     }
12220 }
12221
12222 char *
12223 SavePart (char *str)
12224 {
12225     static char buf[MSG_SIZ];
12226     char *p;
12227
12228     p = strchr(str, ' ');
12229     if (p == NULL) return str;
12230     strncpy(buf, str, p - str);
12231     buf[p - str] = NULLCHAR;
12232     return buf;
12233 }
12234
12235 #define PGN_MAX_LINE 75
12236
12237 #define PGN_SIDE_WHITE  0
12238 #define PGN_SIDE_BLACK  1
12239
12240 static int
12241 FindFirstMoveOutOfBook (int side)
12242 {
12243     int result = -1;
12244
12245     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12246         int index = backwardMostMove;
12247         int has_book_hit = 0;
12248
12249         if( (index % 2) != side ) {
12250             index++;
12251         }
12252
12253         while( index < forwardMostMove ) {
12254             /* Check to see if engine is in book */
12255             int depth = pvInfoList[index].depth;
12256             int score = pvInfoList[index].score;
12257             int in_book = 0;
12258
12259             if( depth <= 2 ) {
12260                 in_book = 1;
12261             }
12262             else if( score == 0 && depth == 63 ) {
12263                 in_book = 1; /* Zappa */
12264             }
12265             else if( score == 2 && depth == 99 ) {
12266                 in_book = 1; /* Abrok */
12267             }
12268
12269             has_book_hit += in_book;
12270
12271             if( ! in_book ) {
12272                 result = index;
12273
12274                 break;
12275             }
12276
12277             index += 2;
12278         }
12279     }
12280
12281     return result;
12282 }
12283
12284 void
12285 GetOutOfBookInfo (char * buf)
12286 {
12287     int oob[2];
12288     int i;
12289     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12290
12291     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12292     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12293
12294     *buf = '\0';
12295
12296     if( oob[0] >= 0 || oob[1] >= 0 ) {
12297         for( i=0; i<2; i++ ) {
12298             int idx = oob[i];
12299
12300             if( idx >= 0 ) {
12301                 if( i > 0 && oob[0] >= 0 ) {
12302                     strcat( buf, "   " );
12303                 }
12304
12305                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12306                 sprintf( buf+strlen(buf), "%s%.2f",
12307                     pvInfoList[idx].score >= 0 ? "+" : "",
12308                     pvInfoList[idx].score / 100.0 );
12309             }
12310         }
12311     }
12312 }
12313
12314 /* Save game in PGN style and close the file */
12315 int
12316 SaveGamePGN (FILE *f)
12317 {
12318     int i, offset, linelen, newblock;
12319     time_t tm;
12320 //    char *movetext;
12321     char numtext[32];
12322     int movelen, numlen, blank;
12323     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12324
12325     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12326
12327     tm = time((time_t *) NULL);
12328
12329     PrintPGNTags(f, &gameInfo);
12330
12331     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12332
12333     if (backwardMostMove > 0 || startedFromSetupPosition) {
12334         char *fen = PositionToFEN(backwardMostMove, NULL);
12335         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12336         fprintf(f, "\n{--------------\n");
12337         PrintPosition(f, backwardMostMove);
12338         fprintf(f, "--------------}\n");
12339         free(fen);
12340     }
12341     else {
12342         /* [AS] Out of book annotation */
12343         if( appData.saveOutOfBookInfo ) {
12344             char buf[64];
12345
12346             GetOutOfBookInfo( buf );
12347
12348             if( buf[0] != '\0' ) {
12349                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12350             }
12351         }
12352
12353         fprintf(f, "\n");
12354     }
12355
12356     i = backwardMostMove;
12357     linelen = 0;
12358     newblock = TRUE;
12359
12360     while (i < forwardMostMove) {
12361         /* Print comments preceding this move */
12362         if (commentList[i] != NULL) {
12363             if (linelen > 0) fprintf(f, "\n");
12364             fprintf(f, "%s", commentList[i]);
12365             linelen = 0;
12366             newblock = TRUE;
12367         }
12368
12369         /* Format move number */
12370         if ((i % 2) == 0)
12371           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12372         else
12373           if (newblock)
12374             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12375           else
12376             numtext[0] = NULLCHAR;
12377
12378         numlen = strlen(numtext);
12379         newblock = FALSE;
12380
12381         /* Print move number */
12382         blank = linelen > 0 && numlen > 0;
12383         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12384             fprintf(f, "\n");
12385             linelen = 0;
12386             blank = 0;
12387         }
12388         if (blank) {
12389             fprintf(f, " ");
12390             linelen++;
12391         }
12392         fprintf(f, "%s", numtext);
12393         linelen += numlen;
12394
12395         /* Get move */
12396         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12397         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12398
12399         /* Print move */
12400         blank = linelen > 0 && movelen > 0;
12401         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12402             fprintf(f, "\n");
12403             linelen = 0;
12404             blank = 0;
12405         }
12406         if (blank) {
12407             fprintf(f, " ");
12408             linelen++;
12409         }
12410         fprintf(f, "%s", move_buffer);
12411         linelen += movelen;
12412
12413         /* [AS] Add PV info if present */
12414         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12415             /* [HGM] add time */
12416             char buf[MSG_SIZ]; int seconds;
12417
12418             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12419
12420             if( seconds <= 0)
12421               buf[0] = 0;
12422             else
12423               if( seconds < 30 )
12424                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12425               else
12426                 {
12427                   seconds = (seconds + 4)/10; // round to full seconds
12428                   if( seconds < 60 )
12429                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12430                   else
12431                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12432                 }
12433
12434             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12435                       pvInfoList[i].score >= 0 ? "+" : "",
12436                       pvInfoList[i].score / 100.0,
12437                       pvInfoList[i].depth,
12438                       buf );
12439
12440             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12441
12442             /* Print score/depth */
12443             blank = linelen > 0 && movelen > 0;
12444             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12445                 fprintf(f, "\n");
12446                 linelen = 0;
12447                 blank = 0;
12448             }
12449             if (blank) {
12450                 fprintf(f, " ");
12451                 linelen++;
12452             }
12453             fprintf(f, "%s", move_buffer);
12454             linelen += movelen;
12455         }
12456
12457         i++;
12458     }
12459
12460     /* Start a new line */
12461     if (linelen > 0) fprintf(f, "\n");
12462
12463     /* Print comments after last move */
12464     if (commentList[i] != NULL) {
12465         fprintf(f, "%s\n", commentList[i]);
12466     }
12467
12468     /* Print result */
12469     if (gameInfo.resultDetails != NULL &&
12470         gameInfo.resultDetails[0] != NULLCHAR) {
12471         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12472                 PGNResult(gameInfo.result));
12473     } else {
12474         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12475     }
12476
12477     fclose(f);
12478     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12479     return TRUE;
12480 }
12481
12482 /* Save game in old style and close the file */
12483 int
12484 SaveGameOldStyle (FILE *f)
12485 {
12486     int i, offset;
12487     time_t tm;
12488
12489     tm = time((time_t *) NULL);
12490
12491     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12492     PrintOpponents(f);
12493
12494     if (backwardMostMove > 0 || startedFromSetupPosition) {
12495         fprintf(f, "\n[--------------\n");
12496         PrintPosition(f, backwardMostMove);
12497         fprintf(f, "--------------]\n");
12498     } else {
12499         fprintf(f, "\n");
12500     }
12501
12502     i = backwardMostMove;
12503     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12504
12505     while (i < forwardMostMove) {
12506         if (commentList[i] != NULL) {
12507             fprintf(f, "[%s]\n", commentList[i]);
12508         }
12509
12510         if ((i % 2) == 1) {
12511             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12512             i++;
12513         } else {
12514             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12515             i++;
12516             if (commentList[i] != NULL) {
12517                 fprintf(f, "\n");
12518                 continue;
12519             }
12520             if (i >= forwardMostMove) {
12521                 fprintf(f, "\n");
12522                 break;
12523             }
12524             fprintf(f, "%s\n", parseList[i]);
12525             i++;
12526         }
12527     }
12528
12529     if (commentList[i] != NULL) {
12530         fprintf(f, "[%s]\n", commentList[i]);
12531     }
12532
12533     /* This isn't really the old style, but it's close enough */
12534     if (gameInfo.resultDetails != NULL &&
12535         gameInfo.resultDetails[0] != NULLCHAR) {
12536         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12537                 gameInfo.resultDetails);
12538     } else {
12539         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12540     }
12541
12542     fclose(f);
12543     return TRUE;
12544 }
12545
12546 /* Save the current game to open file f and close the file */
12547 int
12548 SaveGame (FILE *f, int dummy, char *dummy2)
12549 {
12550     if (gameMode == EditPosition) EditPositionDone(TRUE);
12551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12552     if (appData.oldSaveStyle)
12553       return SaveGameOldStyle(f);
12554     else
12555       return SaveGamePGN(f);
12556 }
12557
12558 /* Save the current position to the given file */
12559 int
12560 SavePositionToFile (char *filename)
12561 {
12562     FILE *f;
12563     char buf[MSG_SIZ];
12564
12565     if (strcmp(filename, "-") == 0) {
12566         return SavePosition(stdout, 0, NULL);
12567     } else {
12568         f = fopen(filename, "a");
12569         if (f == NULL) {
12570             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12571             DisplayError(buf, errno);
12572             return FALSE;
12573         } else {
12574             safeStrCpy(buf, lastMsg, MSG_SIZ);
12575             DisplayMessage(_("Waiting for access to save file"), "");
12576             flock(fileno(f), LOCK_EX); // [HGM] lock
12577             DisplayMessage(_("Saving position"), "");
12578             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12579             SavePosition(f, 0, NULL);
12580             DisplayMessage(buf, "");
12581             return TRUE;
12582         }
12583     }
12584 }
12585
12586 /* Save the current position to the given open file and close the file */
12587 int
12588 SavePosition (FILE *f, int dummy, char *dummy2)
12589 {
12590     time_t tm;
12591     char *fen;
12592
12593     if (gameMode == EditPosition) EditPositionDone(TRUE);
12594     if (appData.oldSaveStyle) {
12595         tm = time((time_t *) NULL);
12596
12597         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12598         PrintOpponents(f);
12599         fprintf(f, "[--------------\n");
12600         PrintPosition(f, currentMove);
12601         fprintf(f, "--------------]\n");
12602     } else {
12603         fen = PositionToFEN(currentMove, NULL);
12604         fprintf(f, "%s\n", fen);
12605         free(fen);
12606     }
12607     fclose(f);
12608     return TRUE;
12609 }
12610
12611 void
12612 ReloadCmailMsgEvent (int unregister)
12613 {
12614 #if !WIN32
12615     static char *inFilename = NULL;
12616     static char *outFilename;
12617     int i;
12618     struct stat inbuf, outbuf;
12619     int status;
12620
12621     /* Any registered moves are unregistered if unregister is set, */
12622     /* i.e. invoked by the signal handler */
12623     if (unregister) {
12624         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12625             cmailMoveRegistered[i] = FALSE;
12626             if (cmailCommentList[i] != NULL) {
12627                 free(cmailCommentList[i]);
12628                 cmailCommentList[i] = NULL;
12629             }
12630         }
12631         nCmailMovesRegistered = 0;
12632     }
12633
12634     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12635         cmailResult[i] = CMAIL_NOT_RESULT;
12636     }
12637     nCmailResults = 0;
12638
12639     if (inFilename == NULL) {
12640         /* Because the filenames are static they only get malloced once  */
12641         /* and they never get freed                                      */
12642         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12643         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12644
12645         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12646         sprintf(outFilename, "%s.out", appData.cmailGameName);
12647     }
12648
12649     status = stat(outFilename, &outbuf);
12650     if (status < 0) {
12651         cmailMailedMove = FALSE;
12652     } else {
12653         status = stat(inFilename, &inbuf);
12654         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12655     }
12656
12657     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12658        counts the games, notes how each one terminated, etc.
12659
12660        It would be nice to remove this kludge and instead gather all
12661        the information while building the game list.  (And to keep it
12662        in the game list nodes instead of having a bunch of fixed-size
12663        parallel arrays.)  Note this will require getting each game's
12664        termination from the PGN tags, as the game list builder does
12665        not process the game moves.  --mann
12666        */
12667     cmailMsgLoaded = TRUE;
12668     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12669
12670     /* Load first game in the file or popup game menu */
12671     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12672
12673 #endif /* !WIN32 */
12674     return;
12675 }
12676
12677 int
12678 RegisterMove ()
12679 {
12680     FILE *f;
12681     char string[MSG_SIZ];
12682
12683     if (   cmailMailedMove
12684         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12685         return TRUE;            /* Allow free viewing  */
12686     }
12687
12688     /* Unregister move to ensure that we don't leave RegisterMove        */
12689     /* with the move registered when the conditions for registering no   */
12690     /* longer hold                                                       */
12691     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12692         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12693         nCmailMovesRegistered --;
12694
12695         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12696           {
12697               free(cmailCommentList[lastLoadGameNumber - 1]);
12698               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12699           }
12700     }
12701
12702     if (cmailOldMove == -1) {
12703         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12704         return FALSE;
12705     }
12706
12707     if (currentMove > cmailOldMove + 1) {
12708         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12709         return FALSE;
12710     }
12711
12712     if (currentMove < cmailOldMove) {
12713         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12714         return FALSE;
12715     }
12716
12717     if (forwardMostMove > currentMove) {
12718         /* Silently truncate extra moves */
12719         TruncateGame();
12720     }
12721
12722     if (   (currentMove == cmailOldMove + 1)
12723         || (   (currentMove == cmailOldMove)
12724             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12725                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12726         if (gameInfo.result != GameUnfinished) {
12727             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12728         }
12729
12730         if (commentList[currentMove] != NULL) {
12731             cmailCommentList[lastLoadGameNumber - 1]
12732               = StrSave(commentList[currentMove]);
12733         }
12734         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12735
12736         if (appData.debugMode)
12737           fprintf(debugFP, "Saving %s for game %d\n",
12738                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12739
12740         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12741
12742         f = fopen(string, "w");
12743         if (appData.oldSaveStyle) {
12744             SaveGameOldStyle(f); /* also closes the file */
12745
12746             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12747             f = fopen(string, "w");
12748             SavePosition(f, 0, NULL); /* also closes the file */
12749         } else {
12750             fprintf(f, "{--------------\n");
12751             PrintPosition(f, currentMove);
12752             fprintf(f, "--------------}\n\n");
12753
12754             SaveGame(f, 0, NULL); /* also closes the file*/
12755         }
12756
12757         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12758         nCmailMovesRegistered ++;
12759     } else if (nCmailGames == 1) {
12760         DisplayError(_("You have not made a move yet"), 0);
12761         return FALSE;
12762     }
12763
12764     return TRUE;
12765 }
12766
12767 void
12768 MailMoveEvent ()
12769 {
12770 #if !WIN32
12771     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12772     FILE *commandOutput;
12773     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12774     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12775     int nBuffers;
12776     int i;
12777     int archived;
12778     char *arcDir;
12779
12780     if (! cmailMsgLoaded) {
12781         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12782         return;
12783     }
12784
12785     if (nCmailGames == nCmailResults) {
12786         DisplayError(_("No unfinished games"), 0);
12787         return;
12788     }
12789
12790 #if CMAIL_PROHIBIT_REMAIL
12791     if (cmailMailedMove) {
12792       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);
12793         DisplayError(msg, 0);
12794         return;
12795     }
12796 #endif
12797
12798     if (! (cmailMailedMove || RegisterMove())) return;
12799
12800     if (   cmailMailedMove
12801         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12802       snprintf(string, MSG_SIZ, partCommandString,
12803                appData.debugMode ? " -v" : "", appData.cmailGameName);
12804         commandOutput = popen(string, "r");
12805
12806         if (commandOutput == NULL) {
12807             DisplayError(_("Failed to invoke cmail"), 0);
12808         } else {
12809             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12810                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12811             }
12812             if (nBuffers > 1) {
12813                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12814                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12815                 nBytes = MSG_SIZ - 1;
12816             } else {
12817                 (void) memcpy(msg, buffer, nBytes);
12818             }
12819             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12820
12821             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12822                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12823
12824                 archived = TRUE;
12825                 for (i = 0; i < nCmailGames; i ++) {
12826                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12827                         archived = FALSE;
12828                     }
12829                 }
12830                 if (   archived
12831                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12832                         != NULL)) {
12833                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12834                            arcDir,
12835                            appData.cmailGameName,
12836                            gameInfo.date);
12837                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12838                     cmailMsgLoaded = FALSE;
12839                 }
12840             }
12841
12842             DisplayInformation(msg);
12843             pclose(commandOutput);
12844         }
12845     } else {
12846         if ((*cmailMsg) != '\0') {
12847             DisplayInformation(cmailMsg);
12848         }
12849     }
12850
12851     return;
12852 #endif /* !WIN32 */
12853 }
12854
12855 char *
12856 CmailMsg ()
12857 {
12858 #if WIN32
12859     return NULL;
12860 #else
12861     int  prependComma = 0;
12862     char number[5];
12863     char string[MSG_SIZ];       /* Space for game-list */
12864     int  i;
12865
12866     if (!cmailMsgLoaded) return "";
12867
12868     if (cmailMailedMove) {
12869       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12870     } else {
12871         /* Create a list of games left */
12872       snprintf(string, MSG_SIZ, "[");
12873         for (i = 0; i < nCmailGames; i ++) {
12874             if (! (   cmailMoveRegistered[i]
12875                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12876                 if (prependComma) {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12878                 } else {
12879                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12880                     prependComma = 1;
12881                 }
12882
12883                 strcat(string, number);
12884             }
12885         }
12886         strcat(string, "]");
12887
12888         if (nCmailMovesRegistered + nCmailResults == 0) {
12889             switch (nCmailGames) {
12890               case 1:
12891                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12892                 break;
12893
12894               case 2:
12895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12896                 break;
12897
12898               default:
12899                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12900                          nCmailGames);
12901                 break;
12902             }
12903         } else {
12904             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12905               case 1:
12906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12907                          string);
12908                 break;
12909
12910               case 0:
12911                 if (nCmailResults == nCmailGames) {
12912                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12913                 } else {
12914                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12915                 }
12916                 break;
12917
12918               default:
12919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12920                          string);
12921             }
12922         }
12923     }
12924     return cmailMsg;
12925 #endif /* WIN32 */
12926 }
12927
12928 void
12929 ResetGameEvent ()
12930 {
12931     if (gameMode == Training)
12932       SetTrainingModeOff();
12933
12934     Reset(TRUE, TRUE);
12935     cmailMsgLoaded = FALSE;
12936     if (appData.icsActive) {
12937       SendToICS(ics_prefix);
12938       SendToICS("refresh\n");
12939     }
12940 }
12941
12942 void
12943 ExitEvent (int status)
12944 {
12945     exiting++;
12946     if (exiting > 2) {
12947       /* Give up on clean exit */
12948       exit(status);
12949     }
12950     if (exiting > 1) {
12951       /* Keep trying for clean exit */
12952       return;
12953     }
12954
12955     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12956
12957     if (telnetISR != NULL) {
12958       RemoveInputSource(telnetISR);
12959     }
12960     if (icsPR != NoProc) {
12961       DestroyChildProcess(icsPR, TRUE);
12962     }
12963
12964     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12965     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12966
12967     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12968     /* make sure this other one finishes before killing it!                  */
12969     if(endingGame) { int count = 0;
12970         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12971         while(endingGame && count++ < 10) DoSleep(1);
12972         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12973     }
12974
12975     /* Kill off chess programs */
12976     if (first.pr != NoProc) {
12977         ExitAnalyzeMode();
12978
12979         DoSleep( appData.delayBeforeQuit );
12980         SendToProgram("quit\n", &first);
12981         DoSleep( appData.delayAfterQuit );
12982         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12983     }
12984     if (second.pr != NoProc) {
12985         DoSleep( appData.delayBeforeQuit );
12986         SendToProgram("quit\n", &second);
12987         DoSleep( appData.delayAfterQuit );
12988         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12989     }
12990     if (first.isr != NULL) {
12991         RemoveInputSource(first.isr);
12992     }
12993     if (second.isr != NULL) {
12994         RemoveInputSource(second.isr);
12995     }
12996
12997     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12998     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12999
13000     ShutDownFrontEnd();
13001     exit(status);
13002 }
13003
13004 void
13005 PauseEvent ()
13006 {
13007     if (appData.debugMode)
13008         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13009     if (pausing) {
13010         pausing = FALSE;
13011         ModeHighlight();
13012         if (gameMode == MachinePlaysWhite ||
13013             gameMode == MachinePlaysBlack) {
13014             StartClocks();
13015         } else {
13016             DisplayBothClocks();
13017         }
13018         if (gameMode == PlayFromGameFile) {
13019             if (appData.timeDelay >= 0)
13020                 AutoPlayGameLoop();
13021         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13022             Reset(FALSE, TRUE);
13023             SendToICS(ics_prefix);
13024             SendToICS("refresh\n");
13025         } else if (currentMove < forwardMostMove) {
13026             ForwardInner(forwardMostMove);
13027         }
13028         pauseExamInvalid = FALSE;
13029     } else {
13030         switch (gameMode) {
13031           default:
13032             return;
13033           case IcsExamining:
13034             pauseExamForwardMostMove = forwardMostMove;
13035             pauseExamInvalid = FALSE;
13036             /* fall through */
13037           case IcsObserving:
13038           case IcsPlayingWhite:
13039           case IcsPlayingBlack:
13040             pausing = TRUE;
13041             ModeHighlight();
13042             return;
13043           case PlayFromGameFile:
13044             (void) StopLoadGameTimer();
13045             pausing = TRUE;
13046             ModeHighlight();
13047             break;
13048           case BeginningOfGame:
13049             if (appData.icsActive) return;
13050             /* else fall through */
13051           case MachinePlaysWhite:
13052           case MachinePlaysBlack:
13053           case TwoMachinesPlay:
13054             if (forwardMostMove == 0)
13055               return;           /* don't pause if no one has moved */
13056             if ((gameMode == MachinePlaysWhite &&
13057                  !WhiteOnMove(forwardMostMove)) ||
13058                 (gameMode == MachinePlaysBlack &&
13059                  WhiteOnMove(forwardMostMove))) {
13060                 StopClocks();
13061             }
13062             pausing = TRUE;
13063             ModeHighlight();
13064             break;
13065         }
13066     }
13067 }
13068
13069 void
13070 EditCommentEvent ()
13071 {
13072     char title[MSG_SIZ];
13073
13074     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13075       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13076     } else {
13077       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13078                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13079                parseList[currentMove - 1]);
13080     }
13081
13082     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13083 }
13084
13085
13086 void
13087 EditTagsEvent ()
13088 {
13089     char *tags = PGNTags(&gameInfo);
13090     bookUp = FALSE;
13091     EditTagsPopUp(tags, NULL);
13092     free(tags);
13093 }
13094
13095 void
13096 AnalyzeModeEvent ()
13097 {
13098     if (appData.noChessProgram || gameMode == AnalyzeMode)
13099       return;
13100
13101     if (gameMode != AnalyzeFile) {
13102         if (!appData.icsEngineAnalyze) {
13103                EditGameEvent();
13104                if (gameMode != EditGame) return;
13105         }
13106         ResurrectChessProgram();
13107         SendToProgram("analyze\n", &first);
13108         first.analyzing = TRUE;
13109         /*first.maybeThinking = TRUE;*/
13110         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13111         EngineOutputPopUp();
13112     }
13113     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13114     pausing = FALSE;
13115     ModeHighlight();
13116     SetGameInfo();
13117
13118     StartAnalysisClock();
13119     GetTimeMark(&lastNodeCountTime);
13120     lastNodeCount = 0;
13121 }
13122
13123 void
13124 AnalyzeFileEvent ()
13125 {
13126     if (appData.noChessProgram || gameMode == AnalyzeFile)
13127       return;
13128
13129     if (gameMode != AnalyzeMode) {
13130         EditGameEvent();
13131         if (gameMode != EditGame) return;
13132         ResurrectChessProgram();
13133         SendToProgram("analyze\n", &first);
13134         first.analyzing = TRUE;
13135         /*first.maybeThinking = TRUE;*/
13136         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13137         EngineOutputPopUp();
13138     }
13139     gameMode = AnalyzeFile;
13140     pausing = FALSE;
13141     ModeHighlight();
13142     SetGameInfo();
13143
13144     StartAnalysisClock();
13145     GetTimeMark(&lastNodeCountTime);
13146     lastNodeCount = 0;
13147     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13148 }
13149
13150 void
13151 MachineWhiteEvent ()
13152 {
13153     char buf[MSG_SIZ];
13154     char *bookHit = NULL;
13155
13156     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13157       return;
13158
13159
13160     if (gameMode == PlayFromGameFile ||
13161         gameMode == TwoMachinesPlay  ||
13162         gameMode == Training         ||
13163         gameMode == AnalyzeMode      ||
13164         gameMode == EndOfGame)
13165         EditGameEvent();
13166
13167     if (gameMode == EditPosition)
13168         EditPositionDone(TRUE);
13169
13170     if (!WhiteOnMove(currentMove)) {
13171         DisplayError(_("It is not White's turn"), 0);
13172         return;
13173     }
13174
13175     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13176       ExitAnalyzeMode();
13177
13178     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13179         gameMode == AnalyzeFile)
13180         TruncateGame();
13181
13182     ResurrectChessProgram();    /* in case it isn't running */
13183     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13184         gameMode = MachinePlaysWhite;
13185         ResetClocks();
13186     } else
13187     gameMode = MachinePlaysWhite;
13188     pausing = FALSE;
13189     ModeHighlight();
13190     SetGameInfo();
13191     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13192     DisplayTitle(buf);
13193     if (first.sendName) {
13194       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13195       SendToProgram(buf, &first);
13196     }
13197     if (first.sendTime) {
13198       if (first.useColors) {
13199         SendToProgram("black\n", &first); /*gnu kludge*/
13200       }
13201       SendTimeRemaining(&first, TRUE);
13202     }
13203     if (first.useColors) {
13204       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13205     }
13206     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13207     SetMachineThinkingEnables();
13208     first.maybeThinking = TRUE;
13209     StartClocks();
13210     firstMove = FALSE;
13211
13212     if (appData.autoFlipView && !flipView) {
13213       flipView = !flipView;
13214       DrawPosition(FALSE, NULL);
13215       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13216     }
13217
13218     if(bookHit) { // [HGM] book: simulate book reply
13219         static char bookMove[MSG_SIZ]; // a bit generous?
13220
13221         programStats.nodes = programStats.depth = programStats.time =
13222         programStats.score = programStats.got_only_move = 0;
13223         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13224
13225         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13226         strcat(bookMove, bookHit);
13227         HandleMachineMove(bookMove, &first);
13228     }
13229 }
13230
13231 void
13232 MachineBlackEvent ()
13233 {
13234   char buf[MSG_SIZ];
13235   char *bookHit = NULL;
13236
13237     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13238         return;
13239
13240
13241     if (gameMode == PlayFromGameFile ||
13242         gameMode == TwoMachinesPlay  ||
13243         gameMode == Training         ||
13244         gameMode == AnalyzeMode      ||
13245         gameMode == EndOfGame)
13246         EditGameEvent();
13247
13248     if (gameMode == EditPosition)
13249         EditPositionDone(TRUE);
13250
13251     if (WhiteOnMove(currentMove)) {
13252         DisplayError(_("It is not Black's turn"), 0);
13253         return;
13254     }
13255
13256     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13257       ExitAnalyzeMode();
13258
13259     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13260         gameMode == AnalyzeFile)
13261         TruncateGame();
13262
13263     ResurrectChessProgram();    /* in case it isn't running */
13264     gameMode = MachinePlaysBlack;
13265     pausing = FALSE;
13266     ModeHighlight();
13267     SetGameInfo();
13268     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13269     DisplayTitle(buf);
13270     if (first.sendName) {
13271       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13272       SendToProgram(buf, &first);
13273     }
13274     if (first.sendTime) {
13275       if (first.useColors) {
13276         SendToProgram("white\n", &first); /*gnu kludge*/
13277       }
13278       SendTimeRemaining(&first, FALSE);
13279     }
13280     if (first.useColors) {
13281       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13282     }
13283     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13284     SetMachineThinkingEnables();
13285     first.maybeThinking = TRUE;
13286     StartClocks();
13287
13288     if (appData.autoFlipView && flipView) {
13289       flipView = !flipView;
13290       DrawPosition(FALSE, NULL);
13291       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13292     }
13293     if(bookHit) { // [HGM] book: simulate book reply
13294         static char bookMove[MSG_SIZ]; // a bit generous?
13295
13296         programStats.nodes = programStats.depth = programStats.time =
13297         programStats.score = programStats.got_only_move = 0;
13298         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13299
13300         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13301         strcat(bookMove, bookHit);
13302         HandleMachineMove(bookMove, &first);
13303     }
13304 }
13305
13306
13307 void
13308 DisplayTwoMachinesTitle ()
13309 {
13310     char buf[MSG_SIZ];
13311     if (appData.matchGames > 0) {
13312         if(appData.tourneyFile[0]) {
13313           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13314                    gameInfo.white, _("vs."), gameInfo.black,
13315                    nextGame+1, appData.matchGames+1,
13316                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13317         } else 
13318         if (first.twoMachinesColor[0] == 'w') {
13319           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13320                    gameInfo.white, _("vs."),  gameInfo.black,
13321                    first.matchWins, second.matchWins,
13322                    matchGame - 1 - (first.matchWins + second.matchWins));
13323         } else {
13324           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13325                    gameInfo.white, _("vs."), gameInfo.black,
13326                    second.matchWins, first.matchWins,
13327                    matchGame - 1 - (first.matchWins + second.matchWins));
13328         }
13329     } else {
13330       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13331     }
13332     DisplayTitle(buf);
13333 }
13334
13335 void
13336 SettingsMenuIfReady ()
13337 {
13338   if (second.lastPing != second.lastPong) {
13339     DisplayMessage("", _("Waiting for second chess program"));
13340     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13341     return;
13342   }
13343   ThawUI();
13344   DisplayMessage("", "");
13345   SettingsPopUp(&second);
13346 }
13347
13348 int
13349 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13350 {
13351     char buf[MSG_SIZ];
13352     if (cps->pr == NoProc) {
13353         StartChessProgram(cps);
13354         if (cps->protocolVersion == 1) {
13355           retry();
13356         } else {
13357           /* kludge: allow timeout for initial "feature" command */
13358           FreezeUI();
13359           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13360           DisplayMessage("", buf);
13361           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13362         }
13363         return 1;
13364     }
13365     return 0;
13366 }
13367
13368 void
13369 TwoMachinesEvent P((void))
13370 {
13371     int i;
13372     char buf[MSG_SIZ];
13373     ChessProgramState *onmove;
13374     char *bookHit = NULL;
13375     static int stalling = 0;
13376     TimeMark now;
13377     long wait;
13378
13379     if (appData.noChessProgram) return;
13380
13381     switch (gameMode) {
13382       case TwoMachinesPlay:
13383         return;
13384       case MachinePlaysWhite:
13385       case MachinePlaysBlack:
13386         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13387             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13388             return;
13389         }
13390         /* fall through */
13391       case BeginningOfGame:
13392       case PlayFromGameFile:
13393       case EndOfGame:
13394         EditGameEvent();
13395         if (gameMode != EditGame) return;
13396         break;
13397       case EditPosition:
13398         EditPositionDone(TRUE);
13399         break;
13400       case AnalyzeMode:
13401       case AnalyzeFile:
13402         ExitAnalyzeMode();
13403         break;
13404       case EditGame:
13405       default:
13406         break;
13407     }
13408
13409 //    forwardMostMove = currentMove;
13410     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13411
13412     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13413
13414     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13415     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13416       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13417       return;
13418     }
13419     if(!stalling) {
13420       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13421       SendToProgram("force\n", &second);
13422       stalling = 1;
13423       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13424       return;
13425     }
13426     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13427     if(appData.matchPause>10000 || appData.matchPause<10)
13428                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13429     wait = SubtractTimeMarks(&now, &pauseStart);
13430     if(wait < appData.matchPause) {
13431         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13432         return;
13433     }
13434     // we are now committed to starting the game
13435     stalling = 0;
13436     DisplayMessage("", "");
13437     if (startedFromSetupPosition) {
13438         SendBoard(&second, backwardMostMove);
13439     if (appData.debugMode) {
13440         fprintf(debugFP, "Two Machines\n");
13441     }
13442     }
13443     for (i = backwardMostMove; i < forwardMostMove; i++) {
13444         SendMoveToProgram(i, &second);
13445     }
13446
13447     gameMode = TwoMachinesPlay;
13448     pausing = FALSE;
13449     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13450     SetGameInfo();
13451     DisplayTwoMachinesTitle();
13452     firstMove = TRUE;
13453     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13454         onmove = &first;
13455     } else {
13456         onmove = &second;
13457     }
13458     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13459     SendToProgram(first.computerString, &first);
13460     if (first.sendName) {
13461       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13462       SendToProgram(buf, &first);
13463     }
13464     SendToProgram(second.computerString, &second);
13465     if (second.sendName) {
13466       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13467       SendToProgram(buf, &second);
13468     }
13469
13470     ResetClocks();
13471     if (!first.sendTime || !second.sendTime) {
13472         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13473         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13474     }
13475     if (onmove->sendTime) {
13476       if (onmove->useColors) {
13477         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13478       }
13479       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13480     }
13481     if (onmove->useColors) {
13482       SendToProgram(onmove->twoMachinesColor, onmove);
13483     }
13484     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13485 //    SendToProgram("go\n", onmove);
13486     onmove->maybeThinking = TRUE;
13487     SetMachineThinkingEnables();
13488
13489     StartClocks();
13490
13491     if(bookHit) { // [HGM] book: simulate book reply
13492         static char bookMove[MSG_SIZ]; // a bit generous?
13493
13494         programStats.nodes = programStats.depth = programStats.time =
13495         programStats.score = programStats.got_only_move = 0;
13496         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13497
13498         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13499         strcat(bookMove, bookHit);
13500         savedMessage = bookMove; // args for deferred call
13501         savedState = onmove;
13502         ScheduleDelayedEvent(DeferredBookMove, 1);
13503     }
13504 }
13505
13506 void
13507 TrainingEvent ()
13508 {
13509     if (gameMode == Training) {
13510       SetTrainingModeOff();
13511       gameMode = PlayFromGameFile;
13512       DisplayMessage("", _("Training mode off"));
13513     } else {
13514       gameMode = Training;
13515       animateTraining = appData.animate;
13516
13517       /* make sure we are not already at the end of the game */
13518       if (currentMove < forwardMostMove) {
13519         SetTrainingModeOn();
13520         DisplayMessage("", _("Training mode on"));
13521       } else {
13522         gameMode = PlayFromGameFile;
13523         DisplayError(_("Already at end of game"), 0);
13524       }
13525     }
13526     ModeHighlight();
13527 }
13528
13529 void
13530 IcsClientEvent ()
13531 {
13532     if (!appData.icsActive) return;
13533     switch (gameMode) {
13534       case IcsPlayingWhite:
13535       case IcsPlayingBlack:
13536       case IcsObserving:
13537       case IcsIdle:
13538       case BeginningOfGame:
13539       case IcsExamining:
13540         return;
13541
13542       case EditGame:
13543         break;
13544
13545       case EditPosition:
13546         EditPositionDone(TRUE);
13547         break;
13548
13549       case AnalyzeMode:
13550       case AnalyzeFile:
13551         ExitAnalyzeMode();
13552         break;
13553
13554       default:
13555         EditGameEvent();
13556         break;
13557     }
13558
13559     gameMode = IcsIdle;
13560     ModeHighlight();
13561     return;
13562 }
13563
13564 void
13565 EditGameEvent ()
13566 {
13567     int i;
13568
13569     switch (gameMode) {
13570       case Training:
13571         SetTrainingModeOff();
13572         break;
13573       case MachinePlaysWhite:
13574       case MachinePlaysBlack:
13575       case BeginningOfGame:
13576         SendToProgram("force\n", &first);
13577         SetUserThinkingEnables();
13578         break;
13579       case PlayFromGameFile:
13580         (void) StopLoadGameTimer();
13581         if (gameFileFP != NULL) {
13582             gameFileFP = NULL;
13583         }
13584         break;
13585       case EditPosition:
13586         EditPositionDone(TRUE);
13587         break;
13588       case AnalyzeMode:
13589       case AnalyzeFile:
13590         ExitAnalyzeMode();
13591         SendToProgram("force\n", &first);
13592         break;
13593       case TwoMachinesPlay:
13594         GameEnds(EndOfFile, NULL, GE_PLAYER);
13595         ResurrectChessProgram();
13596         SetUserThinkingEnables();
13597         break;
13598       case EndOfGame:
13599         ResurrectChessProgram();
13600         break;
13601       case IcsPlayingBlack:
13602       case IcsPlayingWhite:
13603         DisplayError(_("Warning: You are still playing a game"), 0);
13604         break;
13605       case IcsObserving:
13606         DisplayError(_("Warning: You are still observing a game"), 0);
13607         break;
13608       case IcsExamining:
13609         DisplayError(_("Warning: You are still examining a game"), 0);
13610         break;
13611       case IcsIdle:
13612         break;
13613       case EditGame:
13614       default:
13615         return;
13616     }
13617
13618     pausing = FALSE;
13619     StopClocks();
13620     first.offeredDraw = second.offeredDraw = 0;
13621
13622     if (gameMode == PlayFromGameFile) {
13623         whiteTimeRemaining = timeRemaining[0][currentMove];
13624         blackTimeRemaining = timeRemaining[1][currentMove];
13625         DisplayTitle("");
13626     }
13627
13628     if (gameMode == MachinePlaysWhite ||
13629         gameMode == MachinePlaysBlack ||
13630         gameMode == TwoMachinesPlay ||
13631         gameMode == EndOfGame) {
13632         i = forwardMostMove;
13633         while (i > currentMove) {
13634             SendToProgram("undo\n", &first);
13635             i--;
13636         }
13637         if(!adjustedClock) {
13638         whiteTimeRemaining = timeRemaining[0][currentMove];
13639         blackTimeRemaining = timeRemaining[1][currentMove];
13640         DisplayBothClocks();
13641         }
13642         if (whiteFlag || blackFlag) {
13643             whiteFlag = blackFlag = 0;
13644         }
13645         DisplayTitle("");
13646     }
13647
13648     gameMode = EditGame;
13649     ModeHighlight();
13650     SetGameInfo();
13651 }
13652
13653
13654 void
13655 EditPositionEvent ()
13656 {
13657     if (gameMode == EditPosition) {
13658         EditGameEvent();
13659         return;
13660     }
13661
13662     EditGameEvent();
13663     if (gameMode != EditGame) return;
13664
13665     gameMode = EditPosition;
13666     ModeHighlight();
13667     SetGameInfo();
13668     if (currentMove > 0)
13669       CopyBoard(boards[0], boards[currentMove]);
13670
13671     blackPlaysFirst = !WhiteOnMove(currentMove);
13672     ResetClocks();
13673     currentMove = forwardMostMove = backwardMostMove = 0;
13674     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13675     DisplayMove(-1);
13676 }
13677
13678 void
13679 ExitAnalyzeMode ()
13680 {
13681     /* [DM] icsEngineAnalyze - possible call from other functions */
13682     if (appData.icsEngineAnalyze) {
13683         appData.icsEngineAnalyze = FALSE;
13684
13685         DisplayMessage("",_("Close ICS engine analyze..."));
13686     }
13687     if (first.analysisSupport && first.analyzing) {
13688       SendToProgram("exit\n", &first);
13689       first.analyzing = FALSE;
13690     }
13691     thinkOutput[0] = NULLCHAR;
13692 }
13693
13694 void
13695 EditPositionDone (Boolean fakeRights)
13696 {
13697     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13698
13699     startedFromSetupPosition = TRUE;
13700     InitChessProgram(&first, FALSE);
13701     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13702       boards[0][EP_STATUS] = EP_NONE;
13703       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13704     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13705         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13706         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13707       } else boards[0][CASTLING][2] = NoRights;
13708     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13709         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13710         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13711       } else boards[0][CASTLING][5] = NoRights;
13712     }
13713     SendToProgram("force\n", &first);
13714     if (blackPlaysFirst) {
13715         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13716         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13717         currentMove = forwardMostMove = backwardMostMove = 1;
13718         CopyBoard(boards[1], boards[0]);
13719     } else {
13720         currentMove = forwardMostMove = backwardMostMove = 0;
13721     }
13722     SendBoard(&first, forwardMostMove);
13723     if (appData.debugMode) {
13724         fprintf(debugFP, "EditPosDone\n");
13725     }
13726     DisplayTitle("");
13727     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13728     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13729     gameMode = EditGame;
13730     ModeHighlight();
13731     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13732     ClearHighlights(); /* [AS] */
13733 }
13734
13735 /* Pause for `ms' milliseconds */
13736 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13737 void
13738 TimeDelay (long ms)
13739 {
13740     TimeMark m1, m2;
13741
13742     GetTimeMark(&m1);
13743     do {
13744         GetTimeMark(&m2);
13745     } while (SubtractTimeMarks(&m2, &m1) < ms);
13746 }
13747
13748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13749 void
13750 SendMultiLineToICS (char *buf)
13751 {
13752     char temp[MSG_SIZ+1], *p;
13753     int len;
13754
13755     len = strlen(buf);
13756     if (len > MSG_SIZ)
13757       len = MSG_SIZ;
13758
13759     strncpy(temp, buf, len);
13760     temp[len] = 0;
13761
13762     p = temp;
13763     while (*p) {
13764         if (*p == '\n' || *p == '\r')
13765           *p = ' ';
13766         ++p;
13767     }
13768
13769     strcat(temp, "\n");
13770     SendToICS(temp);
13771     SendToPlayer(temp, strlen(temp));
13772 }
13773
13774 void
13775 SetWhiteToPlayEvent ()
13776 {
13777     if (gameMode == EditPosition) {
13778         blackPlaysFirst = FALSE;
13779         DisplayBothClocks();    /* works because currentMove is 0 */
13780     } else if (gameMode == IcsExamining) {
13781         SendToICS(ics_prefix);
13782         SendToICS("tomove white\n");
13783     }
13784 }
13785
13786 void
13787 SetBlackToPlayEvent ()
13788 {
13789     if (gameMode == EditPosition) {
13790         blackPlaysFirst = TRUE;
13791         currentMove = 1;        /* kludge */
13792         DisplayBothClocks();
13793         currentMove = 0;
13794     } else if (gameMode == IcsExamining) {
13795         SendToICS(ics_prefix);
13796         SendToICS("tomove black\n");
13797     }
13798 }
13799
13800 void
13801 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13802 {
13803     char buf[MSG_SIZ];
13804     ChessSquare piece = boards[0][y][x];
13805
13806     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13807
13808     switch (selection) {
13809       case ClearBoard:
13810         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13811             SendToICS(ics_prefix);
13812             SendToICS("bsetup clear\n");
13813         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13814             SendToICS(ics_prefix);
13815             SendToICS("clearboard\n");
13816         } else {
13817             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13818                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13819                 for (y = 0; y < BOARD_HEIGHT; y++) {
13820                     if (gameMode == IcsExamining) {
13821                         if (boards[currentMove][y][x] != EmptySquare) {
13822                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13823                                     AAA + x, ONE + y);
13824                             SendToICS(buf);
13825                         }
13826                     } else {
13827                         boards[0][y][x] = p;
13828                     }
13829                 }
13830             }
13831         }
13832         if (gameMode == EditPosition) {
13833             DrawPosition(FALSE, boards[0]);
13834         }
13835         break;
13836
13837       case WhitePlay:
13838         SetWhiteToPlayEvent();
13839         break;
13840
13841       case BlackPlay:
13842         SetBlackToPlayEvent();
13843         break;
13844
13845       case EmptySquare:
13846         if (gameMode == IcsExamining) {
13847             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13848             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13849             SendToICS(buf);
13850         } else {
13851             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13852                 if(x == BOARD_LEFT-2) {
13853                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13854                     boards[0][y][1] = 0;
13855                 } else
13856                 if(x == BOARD_RGHT+1) {
13857                     if(y >= gameInfo.holdingsSize) break;
13858                     boards[0][y][BOARD_WIDTH-2] = 0;
13859                 } else break;
13860             }
13861             boards[0][y][x] = EmptySquare;
13862             DrawPosition(FALSE, boards[0]);
13863         }
13864         break;
13865
13866       case PromotePiece:
13867         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13868            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13869             selection = (ChessSquare) (PROMOTED piece);
13870         } else if(piece == EmptySquare) selection = WhiteSilver;
13871         else selection = (ChessSquare)((int)piece - 1);
13872         goto defaultlabel;
13873
13874       case DemotePiece:
13875         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13876            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13877             selection = (ChessSquare) (DEMOTED piece);
13878         } else if(piece == EmptySquare) selection = BlackSilver;
13879         else selection = (ChessSquare)((int)piece + 1);
13880         goto defaultlabel;
13881
13882       case WhiteQueen:
13883       case BlackQueen:
13884         if(gameInfo.variant == VariantShatranj ||
13885            gameInfo.variant == VariantXiangqi  ||
13886            gameInfo.variant == VariantCourier  ||
13887            gameInfo.variant == VariantMakruk     )
13888             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13889         goto defaultlabel;
13890
13891       case WhiteKing:
13892       case BlackKing:
13893         if(gameInfo.variant == VariantXiangqi)
13894             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13895         if(gameInfo.variant == VariantKnightmate)
13896             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13897       default:
13898         defaultlabel:
13899         if (gameMode == IcsExamining) {
13900             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13901             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13902                      PieceToChar(selection), AAA + x, ONE + y);
13903             SendToICS(buf);
13904         } else {
13905             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13906                 int n;
13907                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13908                     n = PieceToNumber(selection - BlackPawn);
13909                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13910                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13911                     boards[0][BOARD_HEIGHT-1-n][1]++;
13912                 } else
13913                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13914                     n = PieceToNumber(selection);
13915                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13916                     boards[0][n][BOARD_WIDTH-1] = selection;
13917                     boards[0][n][BOARD_WIDTH-2]++;
13918                 }
13919             } else
13920             boards[0][y][x] = selection;
13921             DrawPosition(TRUE, boards[0]);
13922             ClearHighlights();
13923             fromX = fromY = -1;
13924         }
13925         break;
13926     }
13927 }
13928
13929
13930 void
13931 DropMenuEvent (ChessSquare selection, int x, int y)
13932 {
13933     ChessMove moveType;
13934
13935     switch (gameMode) {
13936       case IcsPlayingWhite:
13937       case MachinePlaysBlack:
13938         if (!WhiteOnMove(currentMove)) {
13939             DisplayMoveError(_("It is Black's turn"));
13940             return;
13941         }
13942         moveType = WhiteDrop;
13943         break;
13944       case IcsPlayingBlack:
13945       case MachinePlaysWhite:
13946         if (WhiteOnMove(currentMove)) {
13947             DisplayMoveError(_("It is White's turn"));
13948             return;
13949         }
13950         moveType = BlackDrop;
13951         break;
13952       case EditGame:
13953         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13954         break;
13955       default:
13956         return;
13957     }
13958
13959     if (moveType == BlackDrop && selection < BlackPawn) {
13960       selection = (ChessSquare) ((int) selection
13961                                  + (int) BlackPawn - (int) WhitePawn);
13962     }
13963     if (boards[currentMove][y][x] != EmptySquare) {
13964         DisplayMoveError(_("That square is occupied"));
13965         return;
13966     }
13967
13968     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13969 }
13970
13971 void
13972 AcceptEvent ()
13973 {
13974     /* Accept a pending offer of any kind from opponent */
13975
13976     if (appData.icsActive) {
13977         SendToICS(ics_prefix);
13978         SendToICS("accept\n");
13979     } else if (cmailMsgLoaded) {
13980         if (currentMove == cmailOldMove &&
13981             commentList[cmailOldMove] != NULL &&
13982             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13983                    "Black offers a draw" : "White offers a draw")) {
13984             TruncateGame();
13985             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13986             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13987         } else {
13988             DisplayError(_("There is no pending offer on this move"), 0);
13989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13990         }
13991     } else {
13992         /* Not used for offers from chess program */
13993     }
13994 }
13995
13996 void
13997 DeclineEvent ()
13998 {
13999     /* Decline a pending offer of any kind from opponent */
14000
14001     if (appData.icsActive) {
14002         SendToICS(ics_prefix);
14003         SendToICS("decline\n");
14004     } else if (cmailMsgLoaded) {
14005         if (currentMove == cmailOldMove &&
14006             commentList[cmailOldMove] != NULL &&
14007             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14008                    "Black offers a draw" : "White offers a draw")) {
14009 #ifdef NOTDEF
14010             AppendComment(cmailOldMove, "Draw declined", TRUE);
14011             DisplayComment(cmailOldMove - 1, "Draw declined");
14012 #endif /*NOTDEF*/
14013         } else {
14014             DisplayError(_("There is no pending offer on this move"), 0);
14015         }
14016     } else {
14017         /* Not used for offers from chess program */
14018     }
14019 }
14020
14021 void
14022 RematchEvent ()
14023 {
14024     /* Issue ICS rematch command */
14025     if (appData.icsActive) {
14026         SendToICS(ics_prefix);
14027         SendToICS("rematch\n");
14028     }
14029 }
14030
14031 void
14032 CallFlagEvent ()
14033 {
14034     /* Call your opponent's flag (claim a win on time) */
14035     if (appData.icsActive) {
14036         SendToICS(ics_prefix);
14037         SendToICS("flag\n");
14038     } else {
14039         switch (gameMode) {
14040           default:
14041             return;
14042           case MachinePlaysWhite:
14043             if (whiteFlag) {
14044                 if (blackFlag)
14045                   GameEnds(GameIsDrawn, "Both players ran out of time",
14046                            GE_PLAYER);
14047                 else
14048                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14049             } else {
14050                 DisplayError(_("Your opponent is not out of time"), 0);
14051             }
14052             break;
14053           case MachinePlaysBlack:
14054             if (blackFlag) {
14055                 if (whiteFlag)
14056                   GameEnds(GameIsDrawn, "Both players ran out of time",
14057                            GE_PLAYER);
14058                 else
14059                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14060             } else {
14061                 DisplayError(_("Your opponent is not out of time"), 0);
14062             }
14063             break;
14064         }
14065     }
14066 }
14067
14068 void
14069 ClockClick (int which)
14070 {       // [HGM] code moved to back-end from winboard.c
14071         if(which) { // black clock
14072           if (gameMode == EditPosition || gameMode == IcsExamining) {
14073             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14074             SetBlackToPlayEvent();
14075           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14076           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14077           } else if (shiftKey) {
14078             AdjustClock(which, -1);
14079           } else if (gameMode == IcsPlayingWhite ||
14080                      gameMode == MachinePlaysBlack) {
14081             CallFlagEvent();
14082           }
14083         } else { // white clock
14084           if (gameMode == EditPosition || gameMode == IcsExamining) {
14085             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14086             SetWhiteToPlayEvent();
14087           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14088           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14089           } else if (shiftKey) {
14090             AdjustClock(which, -1);
14091           } else if (gameMode == IcsPlayingBlack ||
14092                    gameMode == MachinePlaysWhite) {
14093             CallFlagEvent();
14094           }
14095         }
14096 }
14097
14098 void
14099 DrawEvent ()
14100 {
14101     /* Offer draw or accept pending draw offer from opponent */
14102
14103     if (appData.icsActive) {
14104         /* Note: tournament rules require draw offers to be
14105            made after you make your move but before you punch
14106            your clock.  Currently ICS doesn't let you do that;
14107            instead, you immediately punch your clock after making
14108            a move, but you can offer a draw at any time. */
14109
14110         SendToICS(ics_prefix);
14111         SendToICS("draw\n");
14112         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14113     } else if (cmailMsgLoaded) {
14114         if (currentMove == cmailOldMove &&
14115             commentList[cmailOldMove] != NULL &&
14116             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14117                    "Black offers a draw" : "White offers a draw")) {
14118             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14119             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14120         } else if (currentMove == cmailOldMove + 1) {
14121             char *offer = WhiteOnMove(cmailOldMove) ?
14122               "White offers a draw" : "Black offers a draw";
14123             AppendComment(currentMove, offer, TRUE);
14124             DisplayComment(currentMove - 1, offer);
14125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14126         } else {
14127             DisplayError(_("You must make your move before offering a draw"), 0);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14129         }
14130     } else if (first.offeredDraw) {
14131         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14132     } else {
14133         if (first.sendDrawOffers) {
14134             SendToProgram("draw\n", &first);
14135             userOfferedDraw = TRUE;
14136         }
14137     }
14138 }
14139
14140 void
14141 AdjournEvent ()
14142 {
14143     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14144
14145     if (appData.icsActive) {
14146         SendToICS(ics_prefix);
14147         SendToICS("adjourn\n");
14148     } else {
14149         /* Currently GNU Chess doesn't offer or accept Adjourns */
14150     }
14151 }
14152
14153
14154 void
14155 AbortEvent ()
14156 {
14157     /* Offer Abort or accept pending Abort offer from opponent */
14158
14159     if (appData.icsActive) {
14160         SendToICS(ics_prefix);
14161         SendToICS("abort\n");
14162     } else {
14163         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14164     }
14165 }
14166
14167 void
14168 ResignEvent ()
14169 {
14170     /* Resign.  You can do this even if it's not your turn. */
14171
14172     if (appData.icsActive) {
14173         SendToICS(ics_prefix);
14174         SendToICS("resign\n");
14175     } else {
14176         switch (gameMode) {
14177           case MachinePlaysWhite:
14178             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14179             break;
14180           case MachinePlaysBlack:
14181             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14182             break;
14183           case EditGame:
14184             if (cmailMsgLoaded) {
14185                 TruncateGame();
14186                 if (WhiteOnMove(cmailOldMove)) {
14187                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14188                 } else {
14189                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14190                 }
14191                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14192             }
14193             break;
14194           default:
14195             break;
14196         }
14197     }
14198 }
14199
14200
14201 void
14202 StopObservingEvent ()
14203 {
14204     /* Stop observing current games */
14205     SendToICS(ics_prefix);
14206     SendToICS("unobserve\n");
14207 }
14208
14209 void
14210 StopExaminingEvent ()
14211 {
14212     /* Stop observing current game */
14213     SendToICS(ics_prefix);
14214     SendToICS("unexamine\n");
14215 }
14216
14217 void
14218 ForwardInner (int target)
14219 {
14220     int limit; int oldSeekGraphUp = seekGraphUp;
14221
14222     if (appData.debugMode)
14223         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14224                 target, currentMove, forwardMostMove);
14225
14226     if (gameMode == EditPosition)
14227       return;
14228
14229     seekGraphUp = FALSE;
14230     MarkTargetSquares(1);
14231
14232     if (gameMode == PlayFromGameFile && !pausing)
14233       PauseEvent();
14234
14235     if (gameMode == IcsExamining && pausing)
14236       limit = pauseExamForwardMostMove;
14237     else
14238       limit = forwardMostMove;
14239
14240     if (target > limit) target = limit;
14241
14242     if (target > 0 && moveList[target - 1][0]) {
14243         int fromX, fromY, toX, toY;
14244         toX = moveList[target - 1][2] - AAA;
14245         toY = moveList[target - 1][3] - ONE;
14246         if (moveList[target - 1][1] == '@') {
14247             if (appData.highlightLastMove) {
14248                 SetHighlights(-1, -1, toX, toY);
14249             }
14250         } else {
14251             fromX = moveList[target - 1][0] - AAA;
14252             fromY = moveList[target - 1][1] - ONE;
14253             if (target == currentMove + 1) {
14254                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14255             }
14256             if (appData.highlightLastMove) {
14257                 SetHighlights(fromX, fromY, toX, toY);
14258             }
14259         }
14260     }
14261     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14262         gameMode == Training || gameMode == PlayFromGameFile ||
14263         gameMode == AnalyzeFile) {
14264         while (currentMove < target) {
14265             SendMoveToProgram(currentMove++, &first);
14266         }
14267     } else {
14268         currentMove = target;
14269     }
14270
14271     if (gameMode == EditGame || gameMode == EndOfGame) {
14272         whiteTimeRemaining = timeRemaining[0][currentMove];
14273         blackTimeRemaining = timeRemaining[1][currentMove];
14274     }
14275     DisplayBothClocks();
14276     DisplayMove(currentMove - 1);
14277     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14278     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14279     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14280         DisplayComment(currentMove - 1, commentList[currentMove]);
14281     }
14282 }
14283
14284
14285 void
14286 ForwardEvent ()
14287 {
14288     if (gameMode == IcsExamining && !pausing) {
14289         SendToICS(ics_prefix);
14290         SendToICS("forward\n");
14291     } else {
14292         ForwardInner(currentMove + 1);
14293     }
14294 }
14295
14296 void
14297 ToEndEvent ()
14298 {
14299     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14300         /* to optimze, we temporarily turn off analysis mode while we feed
14301          * the remaining moves to the engine. Otherwise we get analysis output
14302          * after each move.
14303          */
14304         if (first.analysisSupport) {
14305           SendToProgram("exit\nforce\n", &first);
14306           first.analyzing = FALSE;
14307         }
14308     }
14309
14310     if (gameMode == IcsExamining && !pausing) {
14311         SendToICS(ics_prefix);
14312         SendToICS("forward 999999\n");
14313     } else {
14314         ForwardInner(forwardMostMove);
14315     }
14316
14317     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14318         /* we have fed all the moves, so reactivate analysis mode */
14319         SendToProgram("analyze\n", &first);
14320         first.analyzing = TRUE;
14321         /*first.maybeThinking = TRUE;*/
14322         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14323     }
14324 }
14325
14326 void
14327 BackwardInner (int target)
14328 {
14329     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14330
14331     if (appData.debugMode)
14332         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14333                 target, currentMove, forwardMostMove);
14334
14335     if (gameMode == EditPosition) return;
14336     seekGraphUp = FALSE;
14337     MarkTargetSquares(1);
14338     if (currentMove <= backwardMostMove) {
14339         ClearHighlights();
14340         DrawPosition(full_redraw, boards[currentMove]);
14341         return;
14342     }
14343     if (gameMode == PlayFromGameFile && !pausing)
14344       PauseEvent();
14345
14346     if (moveList[target][0]) {
14347         int fromX, fromY, toX, toY;
14348         toX = moveList[target][2] - AAA;
14349         toY = moveList[target][3] - ONE;
14350         if (moveList[target][1] == '@') {
14351             if (appData.highlightLastMove) {
14352                 SetHighlights(-1, -1, toX, toY);
14353             }
14354         } else {
14355             fromX = moveList[target][0] - AAA;
14356             fromY = moveList[target][1] - ONE;
14357             if (target == currentMove - 1) {
14358                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14359             }
14360             if (appData.highlightLastMove) {
14361                 SetHighlights(fromX, fromY, toX, toY);
14362             }
14363         }
14364     }
14365     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14366         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14367         while (currentMove > target) {
14368             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14369                 // null move cannot be undone. Reload program with move history before it.
14370                 int i;
14371                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14372                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14373                 }
14374                 SendBoard(&first, i); 
14375                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14376                 break;
14377             }
14378             SendToProgram("undo\n", &first);
14379             currentMove--;
14380         }
14381     } else {
14382         currentMove = target;
14383     }
14384
14385     if (gameMode == EditGame || gameMode == EndOfGame) {
14386         whiteTimeRemaining = timeRemaining[0][currentMove];
14387         blackTimeRemaining = timeRemaining[1][currentMove];
14388     }
14389     DisplayBothClocks();
14390     DisplayMove(currentMove - 1);
14391     DrawPosition(full_redraw, boards[currentMove]);
14392     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14393     // [HGM] PV info: routine tests if comment empty
14394     DisplayComment(currentMove - 1, commentList[currentMove]);
14395 }
14396
14397 void
14398 BackwardEvent ()
14399 {
14400     if (gameMode == IcsExamining && !pausing) {
14401         SendToICS(ics_prefix);
14402         SendToICS("backward\n");
14403     } else {
14404         BackwardInner(currentMove - 1);
14405     }
14406 }
14407
14408 void
14409 ToStartEvent ()
14410 {
14411     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14412         /* to optimize, we temporarily turn off analysis mode while we undo
14413          * all the moves. Otherwise we get analysis output after each undo.
14414          */
14415         if (first.analysisSupport) {
14416           SendToProgram("exit\nforce\n", &first);
14417           first.analyzing = FALSE;
14418         }
14419     }
14420
14421     if (gameMode == IcsExamining && !pausing) {
14422         SendToICS(ics_prefix);
14423         SendToICS("backward 999999\n");
14424     } else {
14425         BackwardInner(backwardMostMove);
14426     }
14427
14428     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14429         /* we have fed all the moves, so reactivate analysis mode */
14430         SendToProgram("analyze\n", &first);
14431         first.analyzing = TRUE;
14432         /*first.maybeThinking = TRUE;*/
14433         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14434     }
14435 }
14436
14437 void
14438 ToNrEvent (int to)
14439 {
14440   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14441   if (to >= forwardMostMove) to = forwardMostMove;
14442   if (to <= backwardMostMove) to = backwardMostMove;
14443   if (to < currentMove) {
14444     BackwardInner(to);
14445   } else {
14446     ForwardInner(to);
14447   }
14448 }
14449
14450 void
14451 RevertEvent (Boolean annotate)
14452 {
14453     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14454         return;
14455     }
14456     if (gameMode != IcsExamining) {
14457         DisplayError(_("You are not examining a game"), 0);
14458         return;
14459     }
14460     if (pausing) {
14461         DisplayError(_("You can't revert while pausing"), 0);
14462         return;
14463     }
14464     SendToICS(ics_prefix);
14465     SendToICS("revert\n");
14466 }
14467
14468 void
14469 RetractMoveEvent ()
14470 {
14471     switch (gameMode) {
14472       case MachinePlaysWhite:
14473       case MachinePlaysBlack:
14474         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14475             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14476             return;
14477         }
14478         if (forwardMostMove < 2) return;
14479         currentMove = forwardMostMove = forwardMostMove - 2;
14480         whiteTimeRemaining = timeRemaining[0][currentMove];
14481         blackTimeRemaining = timeRemaining[1][currentMove];
14482         DisplayBothClocks();
14483         DisplayMove(currentMove - 1);
14484         ClearHighlights();/*!! could figure this out*/
14485         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14486         SendToProgram("remove\n", &first);
14487         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14488         break;
14489
14490       case BeginningOfGame:
14491       default:
14492         break;
14493
14494       case IcsPlayingWhite:
14495       case IcsPlayingBlack:
14496         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14497             SendToICS(ics_prefix);
14498             SendToICS("takeback 2\n");
14499         } else {
14500             SendToICS(ics_prefix);
14501             SendToICS("takeback 1\n");
14502         }
14503         break;
14504     }
14505 }
14506
14507 void
14508 MoveNowEvent ()
14509 {
14510     ChessProgramState *cps;
14511
14512     switch (gameMode) {
14513       case MachinePlaysWhite:
14514         if (!WhiteOnMove(forwardMostMove)) {
14515             DisplayError(_("It is your turn"), 0);
14516             return;
14517         }
14518         cps = &first;
14519         break;
14520       case MachinePlaysBlack:
14521         if (WhiteOnMove(forwardMostMove)) {
14522             DisplayError(_("It is your turn"), 0);
14523             return;
14524         }
14525         cps = &first;
14526         break;
14527       case TwoMachinesPlay:
14528         if (WhiteOnMove(forwardMostMove) ==
14529             (first.twoMachinesColor[0] == 'w')) {
14530             cps = &first;
14531         } else {
14532             cps = &second;
14533         }
14534         break;
14535       case BeginningOfGame:
14536       default:
14537         return;
14538     }
14539     SendToProgram("?\n", cps);
14540 }
14541
14542 void
14543 TruncateGameEvent ()
14544 {
14545     EditGameEvent();
14546     if (gameMode != EditGame) return;
14547     TruncateGame();
14548 }
14549
14550 void
14551 TruncateGame ()
14552 {
14553     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14554     if (forwardMostMove > currentMove) {
14555         if (gameInfo.resultDetails != NULL) {
14556             free(gameInfo.resultDetails);
14557             gameInfo.resultDetails = NULL;
14558             gameInfo.result = GameUnfinished;
14559         }
14560         forwardMostMove = currentMove;
14561         HistorySet(parseList, backwardMostMove, forwardMostMove,
14562                    currentMove-1);
14563     }
14564 }
14565
14566 void
14567 HintEvent ()
14568 {
14569     if (appData.noChessProgram) return;
14570     switch (gameMode) {
14571       case MachinePlaysWhite:
14572         if (WhiteOnMove(forwardMostMove)) {
14573             DisplayError(_("Wait until your turn"), 0);
14574             return;
14575         }
14576         break;
14577       case BeginningOfGame:
14578       case MachinePlaysBlack:
14579         if (!WhiteOnMove(forwardMostMove)) {
14580             DisplayError(_("Wait until your turn"), 0);
14581             return;
14582         }
14583         break;
14584       default:
14585         DisplayError(_("No hint available"), 0);
14586         return;
14587     }
14588     SendToProgram("hint\n", &first);
14589     hintRequested = TRUE;
14590 }
14591
14592 void
14593 BookEvent ()
14594 {
14595     if (appData.noChessProgram) return;
14596     switch (gameMode) {
14597       case MachinePlaysWhite:
14598         if (WhiteOnMove(forwardMostMove)) {
14599             DisplayError(_("Wait until your turn"), 0);
14600             return;
14601         }
14602         break;
14603       case BeginningOfGame:
14604       case MachinePlaysBlack:
14605         if (!WhiteOnMove(forwardMostMove)) {
14606             DisplayError(_("Wait until your turn"), 0);
14607             return;
14608         }
14609         break;
14610       case EditPosition:
14611         EditPositionDone(TRUE);
14612         break;
14613       case TwoMachinesPlay:
14614         return;
14615       default:
14616         break;
14617     }
14618     SendToProgram("bk\n", &first);
14619     bookOutput[0] = NULLCHAR;
14620     bookRequested = TRUE;
14621 }
14622
14623 void
14624 AboutGameEvent ()
14625 {
14626     char *tags = PGNTags(&gameInfo);
14627     TagsPopUp(tags, CmailMsg());
14628     free(tags);
14629 }
14630
14631 /* end button procedures */
14632
14633 void
14634 PrintPosition (FILE *fp, int move)
14635 {
14636     int i, j;
14637
14638     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14639         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14640             char c = PieceToChar(boards[move][i][j]);
14641             fputc(c == 'x' ? '.' : c, fp);
14642             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14643         }
14644     }
14645     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14646       fprintf(fp, "white to play\n");
14647     else
14648       fprintf(fp, "black to play\n");
14649 }
14650
14651 void
14652 PrintOpponents (FILE *fp)
14653 {
14654     if (gameInfo.white != NULL) {
14655         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14656     } else {
14657         fprintf(fp, "\n");
14658     }
14659 }
14660
14661 /* Find last component of program's own name, using some heuristics */
14662 void
14663 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14664 {
14665     char *p, *q, c;
14666     int local = (strcmp(host, "localhost") == 0);
14667     while (!local && (p = strchr(prog, ';')) != NULL) {
14668         p++;
14669         while (*p == ' ') p++;
14670         prog = p;
14671     }
14672     if (*prog == '"' || *prog == '\'') {
14673         q = strchr(prog + 1, *prog);
14674     } else {
14675         q = strchr(prog, ' ');
14676     }
14677     if (q == NULL) q = prog + strlen(prog);
14678     p = q;
14679     while (p >= prog && *p != '/' && *p != '\\') p--;
14680     p++;
14681     if(p == prog && *p == '"') p++;
14682     c = *q; *q = 0;
14683     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14684     memcpy(buf, p, q - p);
14685     buf[q - p] = NULLCHAR;
14686     if (!local) {
14687         strcat(buf, "@");
14688         strcat(buf, host);
14689     }
14690 }
14691
14692 char *
14693 TimeControlTagValue ()
14694 {
14695     char buf[MSG_SIZ];
14696     if (!appData.clockMode) {
14697       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14698     } else if (movesPerSession > 0) {
14699       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14700     } else if (timeIncrement == 0) {
14701       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14702     } else {
14703       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14704     }
14705     return StrSave(buf);
14706 }
14707
14708 void
14709 SetGameInfo ()
14710 {
14711     /* This routine is used only for certain modes */
14712     VariantClass v = gameInfo.variant;
14713     ChessMove r = GameUnfinished;
14714     char *p = NULL;
14715
14716     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14717         r = gameInfo.result;
14718         p = gameInfo.resultDetails;
14719         gameInfo.resultDetails = NULL;
14720     }
14721     ClearGameInfo(&gameInfo);
14722     gameInfo.variant = v;
14723
14724     switch (gameMode) {
14725       case MachinePlaysWhite:
14726         gameInfo.event = StrSave( appData.pgnEventHeader );
14727         gameInfo.site = StrSave(HostName());
14728         gameInfo.date = PGNDate();
14729         gameInfo.round = StrSave("-");
14730         gameInfo.white = StrSave(first.tidy);
14731         gameInfo.black = StrSave(UserName());
14732         gameInfo.timeControl = TimeControlTagValue();
14733         break;
14734
14735       case MachinePlaysBlack:
14736         gameInfo.event = StrSave( appData.pgnEventHeader );
14737         gameInfo.site = StrSave(HostName());
14738         gameInfo.date = PGNDate();
14739         gameInfo.round = StrSave("-");
14740         gameInfo.white = StrSave(UserName());
14741         gameInfo.black = StrSave(first.tidy);
14742         gameInfo.timeControl = TimeControlTagValue();
14743         break;
14744
14745       case TwoMachinesPlay:
14746         gameInfo.event = StrSave( appData.pgnEventHeader );
14747         gameInfo.site = StrSave(HostName());
14748         gameInfo.date = PGNDate();
14749         if (roundNr > 0) {
14750             char buf[MSG_SIZ];
14751             snprintf(buf, MSG_SIZ, "%d", roundNr);
14752             gameInfo.round = StrSave(buf);
14753         } else {
14754             gameInfo.round = StrSave("-");
14755         }
14756         if (first.twoMachinesColor[0] == 'w') {
14757             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14758             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14759         } else {
14760             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14761             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14762         }
14763         gameInfo.timeControl = TimeControlTagValue();
14764         break;
14765
14766       case EditGame:
14767         gameInfo.event = StrSave("Edited game");
14768         gameInfo.site = StrSave(HostName());
14769         gameInfo.date = PGNDate();
14770         gameInfo.round = StrSave("-");
14771         gameInfo.white = StrSave("-");
14772         gameInfo.black = StrSave("-");
14773         gameInfo.result = r;
14774         gameInfo.resultDetails = p;
14775         break;
14776
14777       case EditPosition:
14778         gameInfo.event = StrSave("Edited position");
14779         gameInfo.site = StrSave(HostName());
14780         gameInfo.date = PGNDate();
14781         gameInfo.round = StrSave("-");
14782         gameInfo.white = StrSave("-");
14783         gameInfo.black = StrSave("-");
14784         break;
14785
14786       case IcsPlayingWhite:
14787       case IcsPlayingBlack:
14788       case IcsObserving:
14789       case IcsExamining:
14790         break;
14791
14792       case PlayFromGameFile:
14793         gameInfo.event = StrSave("Game from non-PGN file");
14794         gameInfo.site = StrSave(HostName());
14795         gameInfo.date = PGNDate();
14796         gameInfo.round = StrSave("-");
14797         gameInfo.white = StrSave("?");
14798         gameInfo.black = StrSave("?");
14799         break;
14800
14801       default:
14802         break;
14803     }
14804 }
14805
14806 void
14807 ReplaceComment (int index, char *text)
14808 {
14809     int len;
14810     char *p;
14811     float score;
14812
14813     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14814        pvInfoList[index-1].depth == len &&
14815        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14816        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14817     while (*text == '\n') text++;
14818     len = strlen(text);
14819     while (len > 0 && text[len - 1] == '\n') len--;
14820
14821     if (commentList[index] != NULL)
14822       free(commentList[index]);
14823
14824     if (len == 0) {
14825         commentList[index] = NULL;
14826         return;
14827     }
14828   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14829       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14830       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14831     commentList[index] = (char *) malloc(len + 2);
14832     strncpy(commentList[index], text, len);
14833     commentList[index][len] = '\n';
14834     commentList[index][len + 1] = NULLCHAR;
14835   } else {
14836     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14837     char *p;
14838     commentList[index] = (char *) malloc(len + 7);
14839     safeStrCpy(commentList[index], "{\n", 3);
14840     safeStrCpy(commentList[index]+2, text, len+1);
14841     commentList[index][len+2] = NULLCHAR;
14842     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14843     strcat(commentList[index], "\n}\n");
14844   }
14845 }
14846
14847 void
14848 CrushCRs (char *text)
14849 {
14850   char *p = text;
14851   char *q = text;
14852   char ch;
14853
14854   do {
14855     ch = *p++;
14856     if (ch == '\r') continue;
14857     *q++ = ch;
14858   } while (ch != '\0');
14859 }
14860
14861 void
14862 AppendComment (int index, char *text, Boolean addBraces)
14863 /* addBraces  tells if we should add {} */
14864 {
14865     int oldlen, len;
14866     char *old;
14867
14868 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14869     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14870
14871     CrushCRs(text);
14872     while (*text == '\n') text++;
14873     len = strlen(text);
14874     while (len > 0 && text[len - 1] == '\n') len--;
14875     text[len] = NULLCHAR;
14876
14877     if (len == 0) return;
14878
14879     if (commentList[index] != NULL) {
14880       Boolean addClosingBrace = addBraces;
14881         old = commentList[index];
14882         oldlen = strlen(old);
14883         while(commentList[index][oldlen-1] ==  '\n')
14884           commentList[index][--oldlen] = NULLCHAR;
14885         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14886         safeStrCpy(commentList[index], old, oldlen + len + 6);
14887         free(old);
14888         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14889         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14890           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14891           while (*text == '\n') { text++; len--; }
14892           commentList[index][--oldlen] = NULLCHAR;
14893       }
14894         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14895         else          strcat(commentList[index], "\n");
14896         strcat(commentList[index], text);
14897         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14898         else          strcat(commentList[index], "\n");
14899     } else {
14900         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14901         if(addBraces)
14902           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14903         else commentList[index][0] = NULLCHAR;
14904         strcat(commentList[index], text);
14905         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14906         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14907     }
14908 }
14909
14910 static char *
14911 FindStr (char * text, char * sub_text)
14912 {
14913     char * result = strstr( text, sub_text );
14914
14915     if( result != NULL ) {
14916         result += strlen( sub_text );
14917     }
14918
14919     return result;
14920 }
14921
14922 /* [AS] Try to extract PV info from PGN comment */
14923 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14924 char *
14925 GetInfoFromComment (int index, char * text)
14926 {
14927     char * sep = text, *p;
14928
14929     if( text != NULL && index > 0 ) {
14930         int score = 0;
14931         int depth = 0;
14932         int time = -1, sec = 0, deci;
14933         char * s_eval = FindStr( text, "[%eval " );
14934         char * s_emt = FindStr( text, "[%emt " );
14935
14936         if( s_eval != NULL || s_emt != NULL ) {
14937             /* New style */
14938             char delim;
14939
14940             if( s_eval != NULL ) {
14941                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14942                     return text;
14943                 }
14944
14945                 if( delim != ']' ) {
14946                     return text;
14947                 }
14948             }
14949
14950             if( s_emt != NULL ) {
14951             }
14952                 return text;
14953         }
14954         else {
14955             /* We expect something like: [+|-]nnn.nn/dd */
14956             int score_lo = 0;
14957
14958             if(*text != '{') return text; // [HGM] braces: must be normal comment
14959
14960             sep = strchr( text, '/' );
14961             if( sep == NULL || sep < (text+4) ) {
14962                 return text;
14963             }
14964
14965             p = text;
14966             if(p[1] == '(') { // comment starts with PV
14967                p = strchr(p, ')'); // locate end of PV
14968                if(p == NULL || sep < p+5) return text;
14969                // at this point we have something like "{(.*) +0.23/6 ..."
14970                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14971                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14972                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14973             }
14974             time = -1; sec = -1; deci = -1;
14975             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14976                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14977                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14978                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14979                 return text;
14980             }
14981
14982             if( score_lo < 0 || score_lo >= 100 ) {
14983                 return text;
14984             }
14985
14986             if(sec >= 0) time = 600*time + 10*sec; else
14987             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14988
14989             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14990
14991             /* [HGM] PV time: now locate end of PV info */
14992             while( *++sep >= '0' && *sep <= '9'); // strip depth
14993             if(time >= 0)
14994             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14995             if(sec >= 0)
14996             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14997             if(deci >= 0)
14998             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14999             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15000         }
15001
15002         if( depth <= 0 ) {
15003             return text;
15004         }
15005
15006         if( time < 0 ) {
15007             time = -1;
15008         }
15009
15010         pvInfoList[index-1].depth = depth;
15011         pvInfoList[index-1].score = score;
15012         pvInfoList[index-1].time  = 10*time; // centi-sec
15013         if(*sep == '}') *sep = 0; else *--sep = '{';
15014         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15015     }
15016     return sep;
15017 }
15018
15019 void
15020 SendToProgram (char *message, ChessProgramState *cps)
15021 {
15022     int count, outCount, error;
15023     char buf[MSG_SIZ];
15024
15025     if (cps->pr == NoProc) return;
15026     Attention(cps);
15027
15028     if (appData.debugMode) {
15029         TimeMark now;
15030         GetTimeMark(&now);
15031         fprintf(debugFP, "%ld >%-6s: %s",
15032                 SubtractTimeMarks(&now, &programStartTime),
15033                 cps->which, message);
15034         if(serverFP)
15035             fprintf(serverFP, "%ld >%-6s: %s",
15036                 SubtractTimeMarks(&now, &programStartTime),
15037                 cps->which, message), fflush(serverFP);
15038     }
15039
15040     count = strlen(message);
15041     outCount = OutputToProcess(cps->pr, message, count, &error);
15042     if (outCount < count && !exiting
15043                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15044       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15045       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15046         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15047             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15048                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15049                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15050                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15051             } else {
15052                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15053                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15054                 gameInfo.result = res;
15055             }
15056             gameInfo.resultDetails = StrSave(buf);
15057         }
15058         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15059         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15060     }
15061 }
15062
15063 void
15064 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15065 {
15066     char *end_str;
15067     char buf[MSG_SIZ];
15068     ChessProgramState *cps = (ChessProgramState *)closure;
15069
15070     if (isr != cps->isr) return; /* Killed intentionally */
15071     if (count <= 0) {
15072         if (count == 0) {
15073             RemoveInputSource(cps->isr);
15074             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15075             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15076                     _(cps->which), cps->program);
15077         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15078                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15079                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15080                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15081                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15082                 } else {
15083                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15084                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15085                     gameInfo.result = res;
15086                 }
15087                 gameInfo.resultDetails = StrSave(buf);
15088             }
15089             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15090             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15091         } else {
15092             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15093                     _(cps->which), cps->program);
15094             RemoveInputSource(cps->isr);
15095
15096             /* [AS] Program is misbehaving badly... kill it */
15097             if( count == -2 ) {
15098                 DestroyChildProcess( cps->pr, 9 );
15099                 cps->pr = NoProc;
15100             }
15101
15102             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15103         }
15104         return;
15105     }
15106
15107     if ((end_str = strchr(message, '\r')) != NULL)
15108       *end_str = NULLCHAR;
15109     if ((end_str = strchr(message, '\n')) != NULL)
15110       *end_str = NULLCHAR;
15111
15112     if (appData.debugMode) {
15113         TimeMark now; int print = 1;
15114         char *quote = ""; char c; int i;
15115
15116         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15117                 char start = message[0];
15118                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15119                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15120                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15121                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15122                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15123                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15124                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15125                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15126                    sscanf(message, "hint: %c", &c)!=1 && 
15127                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15128                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15129                     print = (appData.engineComments >= 2);
15130                 }
15131                 message[0] = start; // restore original message
15132         }
15133         if(print) {
15134                 GetTimeMark(&now);
15135                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15136                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15137                         quote,
15138                         message);
15139                 if(serverFP)
15140                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15141                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15142                         quote,
15143                         message), fflush(serverFP);
15144         }
15145     }
15146
15147     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15148     if (appData.icsEngineAnalyze) {
15149         if (strstr(message, "whisper") != NULL ||
15150              strstr(message, "kibitz") != NULL ||
15151             strstr(message, "tellics") != NULL) return;
15152     }
15153
15154     HandleMachineMove(message, cps);
15155 }
15156
15157
15158 void
15159 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15160 {
15161     char buf[MSG_SIZ];
15162     int seconds;
15163
15164     if( timeControl_2 > 0 ) {
15165         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15166             tc = timeControl_2;
15167         }
15168     }
15169     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15170     inc /= cps->timeOdds;
15171     st  /= cps->timeOdds;
15172
15173     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15174
15175     if (st > 0) {
15176       /* Set exact time per move, normally using st command */
15177       if (cps->stKludge) {
15178         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15179         seconds = st % 60;
15180         if (seconds == 0) {
15181           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15182         } else {
15183           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15184         }
15185       } else {
15186         snprintf(buf, MSG_SIZ, "st %d\n", st);
15187       }
15188     } else {
15189       /* Set conventional or incremental time control, using level command */
15190       if (seconds == 0) {
15191         /* Note old gnuchess bug -- minutes:seconds used to not work.
15192            Fixed in later versions, but still avoid :seconds
15193            when seconds is 0. */
15194         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15195       } else {
15196         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15197                  seconds, inc/1000.);
15198       }
15199     }
15200     SendToProgram(buf, cps);
15201
15202     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15203     /* Orthogonally, limit search to given depth */
15204     if (sd > 0) {
15205       if (cps->sdKludge) {
15206         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15207       } else {
15208         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15209       }
15210       SendToProgram(buf, cps);
15211     }
15212
15213     if(cps->nps >= 0) { /* [HGM] nps */
15214         if(cps->supportsNPS == FALSE)
15215           cps->nps = -1; // don't use if engine explicitly says not supported!
15216         else {
15217           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15218           SendToProgram(buf, cps);
15219         }
15220     }
15221 }
15222
15223 ChessProgramState *
15224 WhitePlayer ()
15225 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15226 {
15227     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15228        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15229         return &second;
15230     return &first;
15231 }
15232
15233 void
15234 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15235 {
15236     char message[MSG_SIZ];
15237     long time, otime;
15238
15239     /* Note: this routine must be called when the clocks are stopped
15240        or when they have *just* been set or switched; otherwise
15241        it will be off by the time since the current tick started.
15242     */
15243     if (machineWhite) {
15244         time = whiteTimeRemaining / 10;
15245         otime = blackTimeRemaining / 10;
15246     } else {
15247         time = blackTimeRemaining / 10;
15248         otime = whiteTimeRemaining / 10;
15249     }
15250     /* [HGM] translate opponent's time by time-odds factor */
15251     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15252
15253     if (time <= 0) time = 1;
15254     if (otime <= 0) otime = 1;
15255
15256     snprintf(message, MSG_SIZ, "time %ld\n", time);
15257     SendToProgram(message, cps);
15258
15259     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15260     SendToProgram(message, cps);
15261 }
15262
15263 int
15264 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15265 {
15266   char buf[MSG_SIZ];
15267   int len = strlen(name);
15268   int val;
15269
15270   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15271     (*p) += len + 1;
15272     sscanf(*p, "%d", &val);
15273     *loc = (val != 0);
15274     while (**p && **p != ' ')
15275       (*p)++;
15276     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15277     SendToProgram(buf, cps);
15278     return TRUE;
15279   }
15280   return FALSE;
15281 }
15282
15283 int
15284 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15285 {
15286   char buf[MSG_SIZ];
15287   int len = strlen(name);
15288   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15289     (*p) += len + 1;
15290     sscanf(*p, "%d", loc);
15291     while (**p && **p != ' ') (*p)++;
15292     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15293     SendToProgram(buf, cps);
15294     return TRUE;
15295   }
15296   return FALSE;
15297 }
15298
15299 int
15300 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15301 {
15302   char buf[MSG_SIZ];
15303   int len = strlen(name);
15304   if (strncmp((*p), name, len) == 0
15305       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15306     (*p) += len + 2;
15307     sscanf(*p, "%[^\"]", loc);
15308     while (**p && **p != '\"') (*p)++;
15309     if (**p == '\"') (*p)++;
15310     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15311     SendToProgram(buf, cps);
15312     return TRUE;
15313   }
15314   return FALSE;
15315 }
15316
15317 int
15318 ParseOption (Option *opt, ChessProgramState *cps)
15319 // [HGM] options: process the string that defines an engine option, and determine
15320 // name, type, default value, and allowed value range
15321 {
15322         char *p, *q, buf[MSG_SIZ];
15323         int n, min = (-1)<<31, max = 1<<31, def;
15324
15325         if(p = strstr(opt->name, " -spin ")) {
15326             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15327             if(max < min) max = min; // enforce consistency
15328             if(def < min) def = min;
15329             if(def > max) def = max;
15330             opt->value = def;
15331             opt->min = min;
15332             opt->max = max;
15333             opt->type = Spin;
15334         } else if((p = strstr(opt->name, " -slider "))) {
15335             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15336             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15337             if(max < min) max = min; // enforce consistency
15338             if(def < min) def = min;
15339             if(def > max) def = max;
15340             opt->value = def;
15341             opt->min = min;
15342             opt->max = max;
15343             opt->type = Spin; // Slider;
15344         } else if((p = strstr(opt->name, " -string "))) {
15345             opt->textValue = p+9;
15346             opt->type = TextBox;
15347         } else if((p = strstr(opt->name, " -file "))) {
15348             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15349             opt->textValue = p+7;
15350             opt->type = FileName; // FileName;
15351         } else if((p = strstr(opt->name, " -path "))) {
15352             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15353             opt->textValue = p+7;
15354             opt->type = PathName; // PathName;
15355         } else if(p = strstr(opt->name, " -check ")) {
15356             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15357             opt->value = (def != 0);
15358             opt->type = CheckBox;
15359         } else if(p = strstr(opt->name, " -combo ")) {
15360             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15361             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15362             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15363             opt->value = n = 0;
15364             while(q = StrStr(q, " /// ")) {
15365                 n++; *q = 0;    // count choices, and null-terminate each of them
15366                 q += 5;
15367                 if(*q == '*') { // remember default, which is marked with * prefix
15368                     q++;
15369                     opt->value = n;
15370                 }
15371                 cps->comboList[cps->comboCnt++] = q;
15372             }
15373             cps->comboList[cps->comboCnt++] = NULL;
15374             opt->max = n + 1;
15375             opt->type = ComboBox;
15376         } else if(p = strstr(opt->name, " -button")) {
15377             opt->type = Button;
15378         } else if(p = strstr(opt->name, " -save")) {
15379             opt->type = SaveButton;
15380         } else return FALSE;
15381         *p = 0; // terminate option name
15382         // now look if the command-line options define a setting for this engine option.
15383         if(cps->optionSettings && cps->optionSettings[0])
15384             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15385         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15386           snprintf(buf, MSG_SIZ, "option %s", p);
15387                 if(p = strstr(buf, ",")) *p = 0;
15388                 if(q = strchr(buf, '=')) switch(opt->type) {
15389                     case ComboBox:
15390                         for(n=0; n<opt->max; n++)
15391                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15392                         break;
15393                     case TextBox:
15394                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15395                         break;
15396                     case Spin:
15397                     case CheckBox:
15398                         opt->value = atoi(q+1);
15399                     default:
15400                         break;
15401                 }
15402                 strcat(buf, "\n");
15403                 SendToProgram(buf, cps);
15404         }
15405         return TRUE;
15406 }
15407
15408 void
15409 FeatureDone (ChessProgramState *cps, int val)
15410 {
15411   DelayedEventCallback cb = GetDelayedEvent();
15412   if ((cb == InitBackEnd3 && cps == &first) ||
15413       (cb == SettingsMenuIfReady && cps == &second) ||
15414       (cb == LoadEngine) ||
15415       (cb == TwoMachinesEventIfReady)) {
15416     CancelDelayedEvent();
15417     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15418   }
15419   cps->initDone = val;
15420 }
15421
15422 /* Parse feature command from engine */
15423 void
15424 ParseFeatures (char *args, ChessProgramState *cps)
15425 {
15426   char *p = args;
15427   char *q;
15428   int val;
15429   char buf[MSG_SIZ];
15430
15431   for (;;) {
15432     while (*p == ' ') p++;
15433     if (*p == NULLCHAR) return;
15434
15435     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15436     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15437     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15438     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15439     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15440     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15441     if (BoolFeature(&p, "reuse", &val, cps)) {
15442       /* Engine can disable reuse, but can't enable it if user said no */
15443       if (!val) cps->reuse = FALSE;
15444       continue;
15445     }
15446     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15447     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15448       if (gameMode == TwoMachinesPlay) {
15449         DisplayTwoMachinesTitle();
15450       } else {
15451         DisplayTitle("");
15452       }
15453       continue;
15454     }
15455     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15456     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15457     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15458     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15459     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15460     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15461     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15462     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15463     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15464     if (IntFeature(&p, "done", &val, cps)) {
15465       FeatureDone(cps, val);
15466       continue;
15467     }
15468     /* Added by Tord: */
15469     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15470     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15471     /* End of additions by Tord */
15472
15473     /* [HGM] added features: */
15474     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15475     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15476     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15477     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15478     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15479     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15480     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15481         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15482           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15483             SendToProgram(buf, cps);
15484             continue;
15485         }
15486         if(cps->nrOptions >= MAX_OPTIONS) {
15487             cps->nrOptions--;
15488             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15489             DisplayError(buf, 0);
15490         }
15491         continue;
15492     }
15493     /* End of additions by HGM */
15494
15495     /* unknown feature: complain and skip */
15496     q = p;
15497     while (*q && *q != '=') q++;
15498     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15499     SendToProgram(buf, cps);
15500     p = q;
15501     if (*p == '=') {
15502       p++;
15503       if (*p == '\"') {
15504         p++;
15505         while (*p && *p != '\"') p++;
15506         if (*p == '\"') p++;
15507       } else {
15508         while (*p && *p != ' ') p++;
15509       }
15510     }
15511   }
15512
15513 }
15514
15515 void
15516 PeriodicUpdatesEvent (int newState)
15517 {
15518     if (newState == appData.periodicUpdates)
15519       return;
15520
15521     appData.periodicUpdates=newState;
15522
15523     /* Display type changes, so update it now */
15524 //    DisplayAnalysis();
15525
15526     /* Get the ball rolling again... */
15527     if (newState) {
15528         AnalysisPeriodicEvent(1);
15529         StartAnalysisClock();
15530     }
15531 }
15532
15533 void
15534 PonderNextMoveEvent (int newState)
15535 {
15536     if (newState == appData.ponderNextMove) return;
15537     if (gameMode == EditPosition) EditPositionDone(TRUE);
15538     if (newState) {
15539         SendToProgram("hard\n", &first);
15540         if (gameMode == TwoMachinesPlay) {
15541             SendToProgram("hard\n", &second);
15542         }
15543     } else {
15544         SendToProgram("easy\n", &first);
15545         thinkOutput[0] = NULLCHAR;
15546         if (gameMode == TwoMachinesPlay) {
15547             SendToProgram("easy\n", &second);
15548         }
15549     }
15550     appData.ponderNextMove = newState;
15551 }
15552
15553 void
15554 NewSettingEvent (int option, int *feature, char *command, int value)
15555 {
15556     char buf[MSG_SIZ];
15557
15558     if (gameMode == EditPosition) EditPositionDone(TRUE);
15559     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15560     if(feature == NULL || *feature) SendToProgram(buf, &first);
15561     if (gameMode == TwoMachinesPlay) {
15562         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15563     }
15564 }
15565
15566 void
15567 ShowThinkingEvent ()
15568 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15569 {
15570     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15571     int newState = appData.showThinking
15572         // [HGM] thinking: other features now need thinking output as well
15573         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15574
15575     if (oldState == newState) return;
15576     oldState = newState;
15577     if (gameMode == EditPosition) EditPositionDone(TRUE);
15578     if (oldState) {
15579         SendToProgram("post\n", &first);
15580         if (gameMode == TwoMachinesPlay) {
15581             SendToProgram("post\n", &second);
15582         }
15583     } else {
15584         SendToProgram("nopost\n", &first);
15585         thinkOutput[0] = NULLCHAR;
15586         if (gameMode == TwoMachinesPlay) {
15587             SendToProgram("nopost\n", &second);
15588         }
15589     }
15590 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15591 }
15592
15593 void
15594 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15595 {
15596   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15597   if (pr == NoProc) return;
15598   AskQuestion(title, question, replyPrefix, pr);
15599 }
15600
15601 void
15602 TypeInEvent (char firstChar)
15603 {
15604     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15605         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15606         gameMode == AnalyzeMode || gameMode == EditGame || 
15607         gameMode == EditPosition || gameMode == IcsExamining ||
15608         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15609         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15610                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15611                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15612         gameMode == Training) PopUpMoveDialog(firstChar);
15613 }
15614
15615 void
15616 TypeInDoneEvent (char *move)
15617 {
15618         Board board;
15619         int n, fromX, fromY, toX, toY;
15620         char promoChar;
15621         ChessMove moveType;
15622
15623         // [HGM] FENedit
15624         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15625                 EditPositionPasteFEN(move);
15626                 return;
15627         }
15628         // [HGM] movenum: allow move number to be typed in any mode
15629         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15630           ToNrEvent(2*n-1);
15631           return;
15632         }
15633         // undocumented kludge: allow command-line option to be typed in!
15634         // (potentially fatal, and does not implement the effect of the option.)
15635         // should only be used for options that are values on which future decisions will be made,
15636         // and definitely not on options that would be used during initialization.
15637         if(strstr(move, "!!! -") == move) {
15638             ParseArgsFromString(move+4);
15639             return;
15640         }
15641
15642       if (gameMode != EditGame && currentMove != forwardMostMove && 
15643         gameMode != Training) {
15644         DisplayMoveError(_("Displayed move is not current"));
15645       } else {
15646         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15647           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15648         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15649         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15650           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15651           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15652         } else {
15653           DisplayMoveError(_("Could not parse move"));
15654         }
15655       }
15656 }
15657
15658 void
15659 DisplayMove (int moveNumber)
15660 {
15661     char message[MSG_SIZ];
15662     char res[MSG_SIZ];
15663     char cpThinkOutput[MSG_SIZ];
15664
15665     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15666
15667     if (moveNumber == forwardMostMove - 1 ||
15668         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15669
15670         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15671
15672         if (strchr(cpThinkOutput, '\n')) {
15673             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15674         }
15675     } else {
15676         *cpThinkOutput = NULLCHAR;
15677     }
15678
15679     /* [AS] Hide thinking from human user */
15680     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15681         *cpThinkOutput = NULLCHAR;
15682         if( thinkOutput[0] != NULLCHAR ) {
15683             int i;
15684
15685             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15686                 cpThinkOutput[i] = '.';
15687             }
15688             cpThinkOutput[i] = NULLCHAR;
15689             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15690         }
15691     }
15692
15693     if (moveNumber == forwardMostMove - 1 &&
15694         gameInfo.resultDetails != NULL) {
15695         if (gameInfo.resultDetails[0] == NULLCHAR) {
15696           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15697         } else {
15698           snprintf(res, MSG_SIZ, " {%s} %s",
15699                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15700         }
15701     } else {
15702         res[0] = NULLCHAR;
15703     }
15704
15705     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15706         DisplayMessage(res, cpThinkOutput);
15707     } else {
15708       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15709                 WhiteOnMove(moveNumber) ? " " : ".. ",
15710                 parseList[moveNumber], res);
15711         DisplayMessage(message, cpThinkOutput);
15712     }
15713 }
15714
15715 void
15716 DisplayComment (int moveNumber, char *text)
15717 {
15718     char title[MSG_SIZ];
15719
15720     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15721       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15722     } else {
15723       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15724               WhiteOnMove(moveNumber) ? " " : ".. ",
15725               parseList[moveNumber]);
15726     }
15727     if (text != NULL && (appData.autoDisplayComment || commentUp))
15728         CommentPopUp(title, text);
15729 }
15730
15731 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15732  * might be busy thinking or pondering.  It can be omitted if your
15733  * gnuchess is configured to stop thinking immediately on any user
15734  * input.  However, that gnuchess feature depends on the FIONREAD
15735  * ioctl, which does not work properly on some flavors of Unix.
15736  */
15737 void
15738 Attention (ChessProgramState *cps)
15739 {
15740 #if ATTENTION
15741     if (!cps->useSigint) return;
15742     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15743     switch (gameMode) {
15744       case MachinePlaysWhite:
15745       case MachinePlaysBlack:
15746       case TwoMachinesPlay:
15747       case IcsPlayingWhite:
15748       case IcsPlayingBlack:
15749       case AnalyzeMode:
15750       case AnalyzeFile:
15751         /* Skip if we know it isn't thinking */
15752         if (!cps->maybeThinking) return;
15753         if (appData.debugMode)
15754           fprintf(debugFP, "Interrupting %s\n", cps->which);
15755         InterruptChildProcess(cps->pr);
15756         cps->maybeThinking = FALSE;
15757         break;
15758       default:
15759         break;
15760     }
15761 #endif /*ATTENTION*/
15762 }
15763
15764 int
15765 CheckFlags ()
15766 {
15767     if (whiteTimeRemaining <= 0) {
15768         if (!whiteFlag) {
15769             whiteFlag = TRUE;
15770             if (appData.icsActive) {
15771                 if (appData.autoCallFlag &&
15772                     gameMode == IcsPlayingBlack && !blackFlag) {
15773                   SendToICS(ics_prefix);
15774                   SendToICS("flag\n");
15775                 }
15776             } else {
15777                 if (blackFlag) {
15778                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15779                 } else {
15780                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15781                     if (appData.autoCallFlag) {
15782                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15783                         return TRUE;
15784                     }
15785                 }
15786             }
15787         }
15788     }
15789     if (blackTimeRemaining <= 0) {
15790         if (!blackFlag) {
15791             blackFlag = TRUE;
15792             if (appData.icsActive) {
15793                 if (appData.autoCallFlag &&
15794                     gameMode == IcsPlayingWhite && !whiteFlag) {
15795                   SendToICS(ics_prefix);
15796                   SendToICS("flag\n");
15797                 }
15798             } else {
15799                 if (whiteFlag) {
15800                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15801                 } else {
15802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15803                     if (appData.autoCallFlag) {
15804                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15805                         return TRUE;
15806                     }
15807                 }
15808             }
15809         }
15810     }
15811     return FALSE;
15812 }
15813
15814 void
15815 CheckTimeControl ()
15816 {
15817     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15818         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15819
15820     /*
15821      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15822      */
15823     if ( !WhiteOnMove(forwardMostMove) ) {
15824         /* White made time control */
15825         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15826         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15827         /* [HGM] time odds: correct new time quota for time odds! */
15828                                             / WhitePlayer()->timeOdds;
15829         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15830     } else {
15831         lastBlack -= blackTimeRemaining;
15832         /* Black made time control */
15833         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15834                                             / WhitePlayer()->other->timeOdds;
15835         lastWhite = whiteTimeRemaining;
15836     }
15837 }
15838
15839 void
15840 DisplayBothClocks ()
15841 {
15842     int wom = gameMode == EditPosition ?
15843       !blackPlaysFirst : WhiteOnMove(currentMove);
15844     DisplayWhiteClock(whiteTimeRemaining, wom);
15845     DisplayBlackClock(blackTimeRemaining, !wom);
15846 }
15847
15848
15849 /* Timekeeping seems to be a portability nightmare.  I think everyone
15850    has ftime(), but I'm really not sure, so I'm including some ifdefs
15851    to use other calls if you don't.  Clocks will be less accurate if
15852    you have neither ftime nor gettimeofday.
15853 */
15854
15855 /* VS 2008 requires the #include outside of the function */
15856 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15857 #include <sys/timeb.h>
15858 #endif
15859
15860 /* Get the current time as a TimeMark */
15861 void
15862 GetTimeMark (TimeMark *tm)
15863 {
15864 #if HAVE_GETTIMEOFDAY
15865
15866     struct timeval timeVal;
15867     struct timezone timeZone;
15868
15869     gettimeofday(&timeVal, &timeZone);
15870     tm->sec = (long) timeVal.tv_sec;
15871     tm->ms = (int) (timeVal.tv_usec / 1000L);
15872
15873 #else /*!HAVE_GETTIMEOFDAY*/
15874 #if HAVE_FTIME
15875
15876 // include <sys/timeb.h> / moved to just above start of function
15877     struct timeb timeB;
15878
15879     ftime(&timeB);
15880     tm->sec = (long) timeB.time;
15881     tm->ms = (int) timeB.millitm;
15882
15883 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15884     tm->sec = (long) time(NULL);
15885     tm->ms = 0;
15886 #endif
15887 #endif
15888 }
15889
15890 /* Return the difference in milliseconds between two
15891    time marks.  We assume the difference will fit in a long!
15892 */
15893 long
15894 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15895 {
15896     return 1000L*(tm2->sec - tm1->sec) +
15897            (long) (tm2->ms - tm1->ms);
15898 }
15899
15900
15901 /*
15902  * Code to manage the game clocks.
15903  *
15904  * In tournament play, black starts the clock and then white makes a move.
15905  * We give the human user a slight advantage if he is playing white---the
15906  * clocks don't run until he makes his first move, so it takes zero time.
15907  * Also, we don't account for network lag, so we could get out of sync
15908  * with GNU Chess's clock -- but then, referees are always right.
15909  */
15910
15911 static TimeMark tickStartTM;
15912 static long intendedTickLength;
15913
15914 long
15915 NextTickLength (long timeRemaining)
15916 {
15917     long nominalTickLength, nextTickLength;
15918
15919     if (timeRemaining > 0L && timeRemaining <= 10000L)
15920       nominalTickLength = 100L;
15921     else
15922       nominalTickLength = 1000L;
15923     nextTickLength = timeRemaining % nominalTickLength;
15924     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15925
15926     return nextTickLength;
15927 }
15928
15929 /* Adjust clock one minute up or down */
15930 void
15931 AdjustClock (Boolean which, int dir)
15932 {
15933     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15934     if(which) blackTimeRemaining += 60000*dir;
15935     else      whiteTimeRemaining += 60000*dir;
15936     DisplayBothClocks();
15937     adjustedClock = TRUE;
15938 }
15939
15940 /* Stop clocks and reset to a fresh time control */
15941 void
15942 ResetClocks ()
15943 {
15944     (void) StopClockTimer();
15945     if (appData.icsActive) {
15946         whiteTimeRemaining = blackTimeRemaining = 0;
15947     } else if (searchTime) {
15948         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15949         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15950     } else { /* [HGM] correct new time quote for time odds */
15951         whiteTC = blackTC = fullTimeControlString;
15952         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15953         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15954     }
15955     if (whiteFlag || blackFlag) {
15956         DisplayTitle("");
15957         whiteFlag = blackFlag = FALSE;
15958     }
15959     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15960     DisplayBothClocks();
15961     adjustedClock = FALSE;
15962 }
15963
15964 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15965
15966 /* Decrement running clock by amount of time that has passed */
15967 void
15968 DecrementClocks ()
15969 {
15970     long timeRemaining;
15971     long lastTickLength, fudge;
15972     TimeMark now;
15973
15974     if (!appData.clockMode) return;
15975     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15976
15977     GetTimeMark(&now);
15978
15979     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15980
15981     /* Fudge if we woke up a little too soon */
15982     fudge = intendedTickLength - lastTickLength;
15983     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15984
15985     if (WhiteOnMove(forwardMostMove)) {
15986         if(whiteNPS >= 0) lastTickLength = 0;
15987         timeRemaining = whiteTimeRemaining -= lastTickLength;
15988         if(timeRemaining < 0 && !appData.icsActive) {
15989             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15990             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15991                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15992                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15993             }
15994         }
15995         DisplayWhiteClock(whiteTimeRemaining - fudge,
15996                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15997     } else {
15998         if(blackNPS >= 0) lastTickLength = 0;
15999         timeRemaining = blackTimeRemaining -= lastTickLength;
16000         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16001             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16002             if(suddenDeath) {
16003                 blackStartMove = forwardMostMove;
16004                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16005             }
16006         }
16007         DisplayBlackClock(blackTimeRemaining - fudge,
16008                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16009     }
16010     if (CheckFlags()) return;
16011
16012     tickStartTM = now;
16013     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16014     StartClockTimer(intendedTickLength);
16015
16016     /* if the time remaining has fallen below the alarm threshold, sound the
16017      * alarm. if the alarm has sounded and (due to a takeback or time control
16018      * with increment) the time remaining has increased to a level above the
16019      * threshold, reset the alarm so it can sound again.
16020      */
16021
16022     if (appData.icsActive && appData.icsAlarm) {
16023
16024         /* make sure we are dealing with the user's clock */
16025         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16026                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16027            )) return;
16028
16029         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16030             alarmSounded = FALSE;
16031         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16032             PlayAlarmSound();
16033             alarmSounded = TRUE;
16034         }
16035     }
16036 }
16037
16038
16039 /* A player has just moved, so stop the previously running
16040    clock and (if in clock mode) start the other one.
16041    We redisplay both clocks in case we're in ICS mode, because
16042    ICS gives us an update to both clocks after every move.
16043    Note that this routine is called *after* forwardMostMove
16044    is updated, so the last fractional tick must be subtracted
16045    from the color that is *not* on move now.
16046 */
16047 void
16048 SwitchClocks (int newMoveNr)
16049 {
16050     long lastTickLength;
16051     TimeMark now;
16052     int flagged = FALSE;
16053
16054     GetTimeMark(&now);
16055
16056     if (StopClockTimer() && appData.clockMode) {
16057         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16058         if (!WhiteOnMove(forwardMostMove)) {
16059             if(blackNPS >= 0) lastTickLength = 0;
16060             blackTimeRemaining -= lastTickLength;
16061            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16062 //         if(pvInfoList[forwardMostMove].time == -1)
16063                  pvInfoList[forwardMostMove].time =               // use GUI time
16064                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16065         } else {
16066            if(whiteNPS >= 0) lastTickLength = 0;
16067            whiteTimeRemaining -= lastTickLength;
16068            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16069 //         if(pvInfoList[forwardMostMove].time == -1)
16070                  pvInfoList[forwardMostMove].time =
16071                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16072         }
16073         flagged = CheckFlags();
16074     }
16075     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16076     CheckTimeControl();
16077
16078     if (flagged || !appData.clockMode) return;
16079
16080     switch (gameMode) {
16081       case MachinePlaysBlack:
16082       case MachinePlaysWhite:
16083       case BeginningOfGame:
16084         if (pausing) return;
16085         break;
16086
16087       case EditGame:
16088       case PlayFromGameFile:
16089       case IcsExamining:
16090         return;
16091
16092       default:
16093         break;
16094     }
16095
16096     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16097         if(WhiteOnMove(forwardMostMove))
16098              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16099         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16100     }
16101
16102     tickStartTM = now;
16103     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16104       whiteTimeRemaining : blackTimeRemaining);
16105     StartClockTimer(intendedTickLength);
16106 }
16107
16108
16109 /* Stop both clocks */
16110 void
16111 StopClocks ()
16112 {
16113     long lastTickLength;
16114     TimeMark now;
16115
16116     if (!StopClockTimer()) return;
16117     if (!appData.clockMode) return;
16118
16119     GetTimeMark(&now);
16120
16121     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16122     if (WhiteOnMove(forwardMostMove)) {
16123         if(whiteNPS >= 0) lastTickLength = 0;
16124         whiteTimeRemaining -= lastTickLength;
16125         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16126     } else {
16127         if(blackNPS >= 0) lastTickLength = 0;
16128         blackTimeRemaining -= lastTickLength;
16129         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16130     }
16131     CheckFlags();
16132 }
16133
16134 /* Start clock of player on move.  Time may have been reset, so
16135    if clock is already running, stop and restart it. */
16136 void
16137 StartClocks ()
16138 {
16139     (void) StopClockTimer(); /* in case it was running already */
16140     DisplayBothClocks();
16141     if (CheckFlags()) return;
16142
16143     if (!appData.clockMode) return;
16144     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16145
16146     GetTimeMark(&tickStartTM);
16147     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16148       whiteTimeRemaining : blackTimeRemaining);
16149
16150    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16151     whiteNPS = blackNPS = -1;
16152     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16153        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16154         whiteNPS = first.nps;
16155     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16156        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16157         blackNPS = first.nps;
16158     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16159         whiteNPS = second.nps;
16160     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16161         blackNPS = second.nps;
16162     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16163
16164     StartClockTimer(intendedTickLength);
16165 }
16166
16167 char *
16168 TimeString (long ms)
16169 {
16170     long second, minute, hour, day;
16171     char *sign = "";
16172     static char buf[32];
16173
16174     if (ms > 0 && ms <= 9900) {
16175       /* convert milliseconds to tenths, rounding up */
16176       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16177
16178       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16179       return buf;
16180     }
16181
16182     /* convert milliseconds to seconds, rounding up */
16183     /* use floating point to avoid strangeness of integer division
16184        with negative dividends on many machines */
16185     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16186
16187     if (second < 0) {
16188         sign = "-";
16189         second = -second;
16190     }
16191
16192     day = second / (60 * 60 * 24);
16193     second = second % (60 * 60 * 24);
16194     hour = second / (60 * 60);
16195     second = second % (60 * 60);
16196     minute = second / 60;
16197     second = second % 60;
16198
16199     if (day > 0)
16200       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16201               sign, day, hour, minute, second);
16202     else if (hour > 0)
16203       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16204     else
16205       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16206
16207     return buf;
16208 }
16209
16210
16211 /*
16212  * This is necessary because some C libraries aren't ANSI C compliant yet.
16213  */
16214 char *
16215 StrStr (char *string, char *match)
16216 {
16217     int i, length;
16218
16219     length = strlen(match);
16220
16221     for (i = strlen(string) - length; i >= 0; i--, string++)
16222       if (!strncmp(match, string, length))
16223         return string;
16224
16225     return NULL;
16226 }
16227
16228 char *
16229 StrCaseStr (char *string, char *match)
16230 {
16231     int i, j, length;
16232
16233     length = strlen(match);
16234
16235     for (i = strlen(string) - length; i >= 0; i--, string++) {
16236         for (j = 0; j < length; j++) {
16237             if (ToLower(match[j]) != ToLower(string[j]))
16238               break;
16239         }
16240         if (j == length) return string;
16241     }
16242
16243     return NULL;
16244 }
16245
16246 #ifndef _amigados
16247 int
16248 StrCaseCmp (char *s1, char *s2)
16249 {
16250     char c1, c2;
16251
16252     for (;;) {
16253         c1 = ToLower(*s1++);
16254         c2 = ToLower(*s2++);
16255         if (c1 > c2) return 1;
16256         if (c1 < c2) return -1;
16257         if (c1 == NULLCHAR) return 0;
16258     }
16259 }
16260
16261
16262 int
16263 ToLower (int c)
16264 {
16265     return isupper(c) ? tolower(c) : c;
16266 }
16267
16268
16269 int
16270 ToUpper (int c)
16271 {
16272     return islower(c) ? toupper(c) : c;
16273 }
16274 #endif /* !_amigados    */
16275
16276 char *
16277 StrSave (char *s)
16278 {
16279   char *ret;
16280
16281   if ((ret = (char *) malloc(strlen(s) + 1)))
16282     {
16283       safeStrCpy(ret, s, strlen(s)+1);
16284     }
16285   return ret;
16286 }
16287
16288 char *
16289 StrSavePtr (char *s, char **savePtr)
16290 {
16291     if (*savePtr) {
16292         free(*savePtr);
16293     }
16294     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16295       safeStrCpy(*savePtr, s, strlen(s)+1);
16296     }
16297     return(*savePtr);
16298 }
16299
16300 char *
16301 PGNDate ()
16302 {
16303     time_t clock;
16304     struct tm *tm;
16305     char buf[MSG_SIZ];
16306
16307     clock = time((time_t *)NULL);
16308     tm = localtime(&clock);
16309     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16310             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16311     return StrSave(buf);
16312 }
16313
16314
16315 char *
16316 PositionToFEN (int move, char *overrideCastling)
16317 {
16318     int i, j, fromX, fromY, toX, toY;
16319     int whiteToPlay;
16320     char buf[MSG_SIZ];
16321     char *p, *q;
16322     int emptycount;
16323     ChessSquare piece;
16324
16325     whiteToPlay = (gameMode == EditPosition) ?
16326       !blackPlaysFirst : (move % 2 == 0);
16327     p = buf;
16328
16329     /* Piece placement data */
16330     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16331         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16332         emptycount = 0;
16333         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16334             if (boards[move][i][j] == EmptySquare) {
16335                 emptycount++;
16336             } else { ChessSquare piece = boards[move][i][j];
16337                 if (emptycount > 0) {
16338                     if(emptycount<10) /* [HGM] can be >= 10 */
16339                         *p++ = '0' + emptycount;
16340                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16341                     emptycount = 0;
16342                 }
16343                 if(PieceToChar(piece) == '+') {
16344                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16345                     *p++ = '+';
16346                     piece = (ChessSquare)(DEMOTED piece);
16347                 }
16348                 *p++ = PieceToChar(piece);
16349                 if(p[-1] == '~') {
16350                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16351                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16352                     *p++ = '~';
16353                 }
16354             }
16355         }
16356         if (emptycount > 0) {
16357             if(emptycount<10) /* [HGM] can be >= 10 */
16358                 *p++ = '0' + emptycount;
16359             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16360             emptycount = 0;
16361         }
16362         *p++ = '/';
16363     }
16364     *(p - 1) = ' ';
16365
16366     /* [HGM] print Crazyhouse or Shogi holdings */
16367     if( gameInfo.holdingsWidth ) {
16368         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16369         q = p;
16370         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16371             piece = boards[move][i][BOARD_WIDTH-1];
16372             if( piece != EmptySquare )
16373               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16374                   *p++ = PieceToChar(piece);
16375         }
16376         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16377             piece = boards[move][BOARD_HEIGHT-i-1][0];
16378             if( piece != EmptySquare )
16379               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16380                   *p++ = PieceToChar(piece);
16381         }
16382
16383         if( q == p ) *p++ = '-';
16384         *p++ = ']';
16385         *p++ = ' ';
16386     }
16387
16388     /* Active color */
16389     *p++ = whiteToPlay ? 'w' : 'b';
16390     *p++ = ' ';
16391
16392   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16393     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16394   } else {
16395   if(nrCastlingRights) {
16396      q = p;
16397      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16398        /* [HGM] write directly from rights */
16399            if(boards[move][CASTLING][2] != NoRights &&
16400               boards[move][CASTLING][0] != NoRights   )
16401                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16402            if(boards[move][CASTLING][2] != NoRights &&
16403               boards[move][CASTLING][1] != NoRights   )
16404                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16405            if(boards[move][CASTLING][5] != NoRights &&
16406               boards[move][CASTLING][3] != NoRights   )
16407                 *p++ = boards[move][CASTLING][3] + AAA;
16408            if(boards[move][CASTLING][5] != NoRights &&
16409               boards[move][CASTLING][4] != NoRights   )
16410                 *p++ = boards[move][CASTLING][4] + AAA;
16411      } else {
16412
16413         /* [HGM] write true castling rights */
16414         if( nrCastlingRights == 6 ) {
16415             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16416                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16417             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16418                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16419             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16420                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16421             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16422                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16423         }
16424      }
16425      if (q == p) *p++ = '-'; /* No castling rights */
16426      *p++ = ' ';
16427   }
16428
16429   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16430      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16431     /* En passant target square */
16432     if (move > backwardMostMove) {
16433         fromX = moveList[move - 1][0] - AAA;
16434         fromY = moveList[move - 1][1] - ONE;
16435         toX = moveList[move - 1][2] - AAA;
16436         toY = moveList[move - 1][3] - ONE;
16437         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16438             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16439             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16440             fromX == toX) {
16441             /* 2-square pawn move just happened */
16442             *p++ = toX + AAA;
16443             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16444         } else {
16445             *p++ = '-';
16446         }
16447     } else if(move == backwardMostMove) {
16448         // [HGM] perhaps we should always do it like this, and forget the above?
16449         if((signed char)boards[move][EP_STATUS] >= 0) {
16450             *p++ = boards[move][EP_STATUS] + AAA;
16451             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16452         } else {
16453             *p++ = '-';
16454         }
16455     } else {
16456         *p++ = '-';
16457     }
16458     *p++ = ' ';
16459   }
16460   }
16461
16462     /* [HGM] find reversible plies */
16463     {   int i = 0, j=move;
16464
16465         if (appData.debugMode) { int k;
16466             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16467             for(k=backwardMostMove; k<=forwardMostMove; k++)
16468                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16469
16470         }
16471
16472         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16473         if( j == backwardMostMove ) i += initialRulePlies;
16474         sprintf(p, "%d ", i);
16475         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16476     }
16477     /* Fullmove number */
16478     sprintf(p, "%d", (move / 2) + 1);
16479
16480     return StrSave(buf);
16481 }
16482
16483 Boolean
16484 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16485 {
16486     int i, j;
16487     char *p, c;
16488     int emptycount;
16489     ChessSquare piece;
16490
16491     p = fen;
16492
16493     /* [HGM] by default clear Crazyhouse holdings, if present */
16494     if(gameInfo.holdingsWidth) {
16495        for(i=0; i<BOARD_HEIGHT; i++) {
16496            board[i][0]             = EmptySquare; /* black holdings */
16497            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16498            board[i][1]             = (ChessSquare) 0; /* black counts */
16499            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16500        }
16501     }
16502
16503     /* Piece placement data */
16504     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16505         j = 0;
16506         for (;;) {
16507             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16508                 if (*p == '/') p++;
16509                 emptycount = gameInfo.boardWidth - j;
16510                 while (emptycount--)
16511                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16512                 break;
16513 #if(BOARD_FILES >= 10)
16514             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16515                 p++; emptycount=10;
16516                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16517                 while (emptycount--)
16518                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16519 #endif
16520             } else if (isdigit(*p)) {
16521                 emptycount = *p++ - '0';
16522                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16523                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16524                 while (emptycount--)
16525                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16526             } else if (*p == '+' || isalpha(*p)) {
16527                 if (j >= gameInfo.boardWidth) return FALSE;
16528                 if(*p=='+') {
16529                     piece = CharToPiece(*++p);
16530                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16531                     piece = (ChessSquare) (PROMOTED piece ); p++;
16532                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16533                 } else piece = CharToPiece(*p++);
16534
16535                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16536                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16537                     piece = (ChessSquare) (PROMOTED piece);
16538                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16539                     p++;
16540                 }
16541                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16542             } else {
16543                 return FALSE;
16544             }
16545         }
16546     }
16547     while (*p == '/' || *p == ' ') p++;
16548
16549     /* [HGM] look for Crazyhouse holdings here */
16550     while(*p==' ') p++;
16551     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16552         if(*p == '[') p++;
16553         if(*p == '-' ) p++; /* empty holdings */ else {
16554             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16555             /* if we would allow FEN reading to set board size, we would   */
16556             /* have to add holdings and shift the board read so far here   */
16557             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16558                 p++;
16559                 if((int) piece >= (int) BlackPawn ) {
16560                     i = (int)piece - (int)BlackPawn;
16561                     i = PieceToNumber((ChessSquare)i);
16562                     if( i >= gameInfo.holdingsSize ) return FALSE;
16563                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16564                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16565                 } else {
16566                     i = (int)piece - (int)WhitePawn;
16567                     i = PieceToNumber((ChessSquare)i);
16568                     if( i >= gameInfo.holdingsSize ) return FALSE;
16569                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16570                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16571                 }
16572             }
16573         }
16574         if(*p == ']') p++;
16575     }
16576
16577     while(*p == ' ') p++;
16578
16579     /* Active color */
16580     c = *p++;
16581     if(appData.colorNickNames) {
16582       if( c == appData.colorNickNames[0] ) c = 'w'; else
16583       if( c == appData.colorNickNames[1] ) c = 'b';
16584     }
16585     switch (c) {
16586       case 'w':
16587         *blackPlaysFirst = FALSE;
16588         break;
16589       case 'b':
16590         *blackPlaysFirst = TRUE;
16591         break;
16592       default:
16593         return FALSE;
16594     }
16595
16596     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16597     /* return the extra info in global variiables             */
16598
16599     /* set defaults in case FEN is incomplete */
16600     board[EP_STATUS] = EP_UNKNOWN;
16601     for(i=0; i<nrCastlingRights; i++ ) {
16602         board[CASTLING][i] =
16603             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16604     }   /* assume possible unless obviously impossible */
16605     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16606     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16607     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16608                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16609     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16610     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16611     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16612                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16613     FENrulePlies = 0;
16614
16615     while(*p==' ') p++;
16616     if(nrCastlingRights) {
16617       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16618           /* castling indicator present, so default becomes no castlings */
16619           for(i=0; i<nrCastlingRights; i++ ) {
16620                  board[CASTLING][i] = NoRights;
16621           }
16622       }
16623       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16624              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16625              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16626              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16627         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16628
16629         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16630             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16631             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16632         }
16633         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16634             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16635         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16636                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16637         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16638                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16639         switch(c) {
16640           case'K':
16641               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16642               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16643               board[CASTLING][2] = whiteKingFile;
16644               break;
16645           case'Q':
16646               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16647               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16648               board[CASTLING][2] = whiteKingFile;
16649               break;
16650           case'k':
16651               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16652               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16653               board[CASTLING][5] = blackKingFile;
16654               break;
16655           case'q':
16656               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16657               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16658               board[CASTLING][5] = blackKingFile;
16659           case '-':
16660               break;
16661           default: /* FRC castlings */
16662               if(c >= 'a') { /* black rights */
16663                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16664                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16665                   if(i == BOARD_RGHT) break;
16666                   board[CASTLING][5] = i;
16667                   c -= AAA;
16668                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16669                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16670                   if(c > i)
16671                       board[CASTLING][3] = c;
16672                   else
16673                       board[CASTLING][4] = c;
16674               } else { /* white rights */
16675                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16676                     if(board[0][i] == WhiteKing) break;
16677                   if(i == BOARD_RGHT) break;
16678                   board[CASTLING][2] = i;
16679                   c -= AAA - 'a' + 'A';
16680                   if(board[0][c] >= WhiteKing) break;
16681                   if(c > i)
16682                       board[CASTLING][0] = c;
16683                   else
16684                       board[CASTLING][1] = c;
16685               }
16686         }
16687       }
16688       for(i=0; i<nrCastlingRights; i++)
16689         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16690     if (appData.debugMode) {
16691         fprintf(debugFP, "FEN castling rights:");
16692         for(i=0; i<nrCastlingRights; i++)
16693         fprintf(debugFP, " %d", board[CASTLING][i]);
16694         fprintf(debugFP, "\n");
16695     }
16696
16697       while(*p==' ') p++;
16698     }
16699
16700     /* read e.p. field in games that know e.p. capture */
16701     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16702        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16703       if(*p=='-') {
16704         p++; board[EP_STATUS] = EP_NONE;
16705       } else {
16706          char c = *p++ - AAA;
16707
16708          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16709          if(*p >= '0' && *p <='9') p++;
16710          board[EP_STATUS] = c;
16711       }
16712     }
16713
16714
16715     if(sscanf(p, "%d", &i) == 1) {
16716         FENrulePlies = i; /* 50-move ply counter */
16717         /* (The move number is still ignored)    */
16718     }
16719
16720     return TRUE;
16721 }
16722
16723 void
16724 EditPositionPasteFEN (char *fen)
16725 {
16726   if (fen != NULL) {
16727     Board initial_position;
16728
16729     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16730       DisplayError(_("Bad FEN position in clipboard"), 0);
16731       return ;
16732     } else {
16733       int savedBlackPlaysFirst = blackPlaysFirst;
16734       EditPositionEvent();
16735       blackPlaysFirst = savedBlackPlaysFirst;
16736       CopyBoard(boards[0], initial_position);
16737       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16738       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16739       DisplayBothClocks();
16740       DrawPosition(FALSE, boards[currentMove]);
16741     }
16742   }
16743 }
16744
16745 static char cseq[12] = "\\   ";
16746
16747 Boolean
16748 set_cont_sequence (char *new_seq)
16749 {
16750     int len;
16751     Boolean ret;
16752
16753     // handle bad attempts to set the sequence
16754         if (!new_seq)
16755                 return 0; // acceptable error - no debug
16756
16757     len = strlen(new_seq);
16758     ret = (len > 0) && (len < sizeof(cseq));
16759     if (ret)
16760       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16761     else if (appData.debugMode)
16762       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16763     return ret;
16764 }
16765
16766 /*
16767     reformat a source message so words don't cross the width boundary.  internal
16768     newlines are not removed.  returns the wrapped size (no null character unless
16769     included in source message).  If dest is NULL, only calculate the size required
16770     for the dest buffer.  lp argument indicats line position upon entry, and it's
16771     passed back upon exit.
16772 */
16773 int
16774 wrap (char *dest, char *src, int count, int width, int *lp)
16775 {
16776     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16777
16778     cseq_len = strlen(cseq);
16779     old_line = line = *lp;
16780     ansi = len = clen = 0;
16781
16782     for (i=0; i < count; i++)
16783     {
16784         if (src[i] == '\033')
16785             ansi = 1;
16786
16787         // if we hit the width, back up
16788         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16789         {
16790             // store i & len in case the word is too long
16791             old_i = i, old_len = len;
16792
16793             // find the end of the last word
16794             while (i && src[i] != ' ' && src[i] != '\n')
16795             {
16796                 i--;
16797                 len--;
16798             }
16799
16800             // word too long?  restore i & len before splitting it
16801             if ((old_i-i+clen) >= width)
16802             {
16803                 i = old_i;
16804                 len = old_len;
16805             }
16806
16807             // extra space?
16808             if (i && src[i-1] == ' ')
16809                 len--;
16810
16811             if (src[i] != ' ' && src[i] != '\n')
16812             {
16813                 i--;
16814                 if (len)
16815                     len--;
16816             }
16817
16818             // now append the newline and continuation sequence
16819             if (dest)
16820                 dest[len] = '\n';
16821             len++;
16822             if (dest)
16823                 strncpy(dest+len, cseq, cseq_len);
16824             len += cseq_len;
16825             line = cseq_len;
16826             clen = cseq_len;
16827             continue;
16828         }
16829
16830         if (dest)
16831             dest[len] = src[i];
16832         len++;
16833         if (!ansi)
16834             line++;
16835         if (src[i] == '\n')
16836             line = 0;
16837         if (src[i] == 'm')
16838             ansi = 0;
16839     }
16840     if (dest && appData.debugMode)
16841     {
16842         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16843             count, width, line, len, *lp);
16844         show_bytes(debugFP, src, count);
16845         fprintf(debugFP, "\ndest: ");
16846         show_bytes(debugFP, dest, len);
16847         fprintf(debugFP, "\n");
16848     }
16849     *lp = dest ? line : old_line;
16850
16851     return len;
16852 }
16853
16854 // [HGM] vari: routines for shelving variations
16855 Boolean modeRestore = FALSE;
16856
16857 void
16858 PushInner (int firstMove, int lastMove)
16859 {
16860         int i, j, nrMoves = lastMove - firstMove;
16861
16862         // push current tail of game on stack
16863         savedResult[storedGames] = gameInfo.result;
16864         savedDetails[storedGames] = gameInfo.resultDetails;
16865         gameInfo.resultDetails = NULL;
16866         savedFirst[storedGames] = firstMove;
16867         savedLast [storedGames] = lastMove;
16868         savedFramePtr[storedGames] = framePtr;
16869         framePtr -= nrMoves; // reserve space for the boards
16870         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16871             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16872             for(j=0; j<MOVE_LEN; j++)
16873                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16874             for(j=0; j<2*MOVE_LEN; j++)
16875                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16876             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16877             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16878             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16879             pvInfoList[firstMove+i-1].depth = 0;
16880             commentList[framePtr+i] = commentList[firstMove+i];
16881             commentList[firstMove+i] = NULL;
16882         }
16883
16884         storedGames++;
16885         forwardMostMove = firstMove; // truncate game so we can start variation
16886 }
16887
16888 void
16889 PushTail (int firstMove, int lastMove)
16890 {
16891         if(appData.icsActive) { // only in local mode
16892                 forwardMostMove = currentMove; // mimic old ICS behavior
16893                 return;
16894         }
16895         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16896
16897         PushInner(firstMove, lastMove);
16898         if(storedGames == 1) GreyRevert(FALSE);
16899         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16900 }
16901
16902 void
16903 PopInner (Boolean annotate)
16904 {
16905         int i, j, nrMoves;
16906         char buf[8000], moveBuf[20];
16907
16908         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16909         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16910         nrMoves = savedLast[storedGames] - currentMove;
16911         if(annotate) {
16912                 int cnt = 10;
16913                 if(!WhiteOnMove(currentMove))
16914                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16915                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16916                 for(i=currentMove; i<forwardMostMove; i++) {
16917                         if(WhiteOnMove(i))
16918                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16919                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16920                         strcat(buf, moveBuf);
16921                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16922                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16923                 }
16924                 strcat(buf, ")");
16925         }
16926         for(i=1; i<=nrMoves; i++) { // copy last variation back
16927             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16928             for(j=0; j<MOVE_LEN; j++)
16929                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16930             for(j=0; j<2*MOVE_LEN; j++)
16931                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16932             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16933             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16934             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16935             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16936             commentList[currentMove+i] = commentList[framePtr+i];
16937             commentList[framePtr+i] = NULL;
16938         }
16939         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16940         framePtr = savedFramePtr[storedGames];
16941         gameInfo.result = savedResult[storedGames];
16942         if(gameInfo.resultDetails != NULL) {
16943             free(gameInfo.resultDetails);
16944       }
16945         gameInfo.resultDetails = savedDetails[storedGames];
16946         forwardMostMove = currentMove + nrMoves;
16947 }
16948
16949 Boolean
16950 PopTail (Boolean annotate)
16951 {
16952         if(appData.icsActive) return FALSE; // only in local mode
16953         if(!storedGames) return FALSE; // sanity
16954         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16955
16956         PopInner(annotate);
16957         if(currentMove < forwardMostMove) ForwardEvent(); else
16958         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16959
16960         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16961         return TRUE;
16962 }
16963
16964 void
16965 CleanupTail ()
16966 {       // remove all shelved variations
16967         int i;
16968         for(i=0; i<storedGames; i++) {
16969             if(savedDetails[i])
16970                 free(savedDetails[i]);
16971             savedDetails[i] = NULL;
16972         }
16973         for(i=framePtr; i<MAX_MOVES; i++) {
16974                 if(commentList[i]) free(commentList[i]);
16975                 commentList[i] = NULL;
16976         }
16977         framePtr = MAX_MOVES-1;
16978         storedGames = 0;
16979 }
16980
16981 void
16982 LoadVariation (int index, char *text)
16983 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16984         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16985         int level = 0, move;
16986
16987         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16988         // first find outermost bracketing variation
16989         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16990             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16991                 if(*p == '{') wait = '}'; else
16992                 if(*p == '[') wait = ']'; else
16993                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16994                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16995             }
16996             if(*p == wait) wait = NULLCHAR; // closing ]} found
16997             p++;
16998         }
16999         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17000         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17001         end[1] = NULLCHAR; // clip off comment beyond variation
17002         ToNrEvent(currentMove-1);
17003         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17004         // kludge: use ParsePV() to append variation to game
17005         move = currentMove;
17006         ParsePV(start, TRUE, TRUE);
17007         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17008         ClearPremoveHighlights();
17009         CommentPopDown();
17010         ToNrEvent(currentMove+1);
17011 }
17012