Implement -pgnNumberTag option
[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;
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 "first",
738 "second"
739 };
740
741 void
742 InitEngine (ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine ()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine (ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 FloatToFront(char **list, char *engineLine)
878 {
879     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
880     int i=0;
881     if(appData.recentEngines <= 0) return;
882     TidyProgramName(engineLine, "localhost", tidy+1);
883     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
884     strncpy(buf+1, *list, MSG_SIZ-50);
885     if(p = strstr(buf, tidy)) { // tidy name appears in list
886         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
887         while(*p++ = *++q); // squeeze out
888     }
889     strcat(tidy, buf+1); // put list behind tidy name
890     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
891     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
892     ASSIGN(*list, tidy+1);
893 }
894
895 void
896 Load (ChessProgramState *cps, int i)
897 {
898     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
899     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
900         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
901         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
902         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
903         appData.firstProtocolVersion = PROTOVER;
904         ParseArgsFromString(buf);
905         SwapEngines(i);
906         ReplaceEngine(cps, i);
907         FloatToFront(&appData.recentEngineList, engineLine);
908         return;
909     }
910     p = engineName;
911     while(q = strchr(p, SLASH)) p = q+1;
912     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
913     if(engineDir[0] != NULLCHAR)
914         appData.directory[i] = engineDir;
915     else if(p != engineName) { // derive directory from engine path, when not given
916         p[-1] = 0;
917         appData.directory[i] = strdup(engineName);
918         p[-1] = SLASH;
919     } else appData.directory[i] = ".";
920     if(params[0]) {
921         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
922         snprintf(command, MSG_SIZ, "%s %s", p, params);
923         p = command;
924     }
925     appData.chessProgram[i] = strdup(p);
926     appData.isUCI[i] = isUCI;
927     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
928     appData.hasOwnBookUCI[i] = hasBook;
929     if(!nickName[0]) useNick = FALSE;
930     if(useNick) ASSIGN(appData.pgnName[i], nickName);
931     if(addToList) {
932         int len;
933         char quote;
934         q = firstChessProgramNames;
935         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
936         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
938                         quote, p, quote, appData.directory[i], 
939                         useNick ? " -fn \"" : "",
940                         useNick ? nickName : "",
941                         useNick ? "\"" : "",
942                         v1 ? " -firstProtocolVersion 1" : "",
943                         hasBook ? "" : " -fNoOwnBookUCI",
944                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
945                         storeVariant ? " -variant " : "",
946                         storeVariant ? VariantName(gameInfo.variant) : "");
947         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
948         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
949         if(q)   free(q);
950         FloatToFront(&appData.recentEngineList, buf);
951     }
952     ReplaceEngine(cps, i);
953 }
954
955 void
956 InitTimeControls ()
957 {
958     int matched, min, sec;
959     /*
960      * Parse timeControl resource
961      */
962     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
963                           appData.movesPerSession)) {
964         char buf[MSG_SIZ];
965         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
966         DisplayFatalError(buf, 0, 2);
967     }
968
969     /*
970      * Parse searchTime resource
971      */
972     if (*appData.searchTime != NULLCHAR) {
973         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
974         if (matched == 1) {
975             searchTime = min * 60;
976         } else if (matched == 2) {
977             searchTime = min * 60 + sec;
978         } else {
979             char buf[MSG_SIZ];
980             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
981             DisplayFatalError(buf, 0, 2);
982         }
983     }
984 }
985
986 void
987 InitBackEnd1 ()
988 {
989
990     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
991     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
992
993     GetTimeMark(&programStartTime);
994     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
995     appData.seedBase = random() + (random()<<15);
996     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
997
998     ClearProgramStats();
999     programStats.ok_to_send = 1;
1000     programStats.seen_stat = 0;
1001
1002     /*
1003      * Initialize game list
1004      */
1005     ListNew(&gameList);
1006
1007
1008     /*
1009      * Internet chess server status
1010      */
1011     if (appData.icsActive) {
1012         appData.matchMode = FALSE;
1013         appData.matchGames = 0;
1014 #if ZIPPY
1015         appData.noChessProgram = !appData.zippyPlay;
1016 #else
1017         appData.zippyPlay = FALSE;
1018         appData.zippyTalk = FALSE;
1019         appData.noChessProgram = TRUE;
1020 #endif
1021         if (*appData.icsHelper != NULLCHAR) {
1022             appData.useTelnet = TRUE;
1023             appData.telnetProgram = appData.icsHelper;
1024         }
1025     } else {
1026         appData.zippyTalk = appData.zippyPlay = FALSE;
1027     }
1028
1029     /* [AS] Initialize pv info list [HGM] and game state */
1030     {
1031         int i, j;
1032
1033         for( i=0; i<=framePtr; i++ ) {
1034             pvInfoList[i].depth = -1;
1035             boards[i][EP_STATUS] = EP_NONE;
1036             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1037         }
1038     }
1039
1040     InitTimeControls();
1041
1042     /* [AS] Adjudication threshold */
1043     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1044
1045     InitEngine(&first, 0);
1046     InitEngine(&second, 1);
1047     CommonEngineInit();
1048
1049     pairing.which = "pairing"; // pairing engine
1050     pairing.pr = NoProc;
1051     pairing.isr = NULL;
1052     pairing.program = appData.pairingEngine;
1053     pairing.host = "localhost";
1054     pairing.dir = ".";
1055
1056     if (appData.icsActive) {
1057         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1058     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1059         appData.clockMode = FALSE;
1060         first.sendTime = second.sendTime = 0;
1061     }
1062
1063 #if ZIPPY
1064     /* Override some settings from environment variables, for backward
1065        compatibility.  Unfortunately it's not feasible to have the env
1066        vars just set defaults, at least in xboard.  Ugh.
1067     */
1068     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1069       ZippyInit();
1070     }
1071 #endif
1072
1073     if (!appData.icsActive) {
1074       char buf[MSG_SIZ];
1075       int len;
1076
1077       /* Check for variants that are supported only in ICS mode,
1078          or not at all.  Some that are accepted here nevertheless
1079          have bugs; see comments below.
1080       */
1081       VariantClass variant = StringToVariant(appData.variant);
1082       switch (variant) {
1083       case VariantBughouse:     /* need four players and two boards */
1084       case VariantKriegspiel:   /* need to hide pieces and move details */
1085         /* case VariantFischeRandom: (Fabien: moved below) */
1086         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1087         if( (len >= MSG_SIZ) && appData.debugMode )
1088           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1089
1090         DisplayFatalError(buf, 0, 2);
1091         return;
1092
1093       case VariantUnknown:
1094       case VariantLoadable:
1095       case Variant29:
1096       case Variant30:
1097       case Variant31:
1098       case Variant32:
1099       case Variant33:
1100       case Variant34:
1101       case Variant35:
1102       case Variant36:
1103       default:
1104         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1105         if( (len >= MSG_SIZ) && appData.debugMode )
1106           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1107
1108         DisplayFatalError(buf, 0, 2);
1109         return;
1110
1111       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1112       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1113       case VariantGothic:     /* [HGM] should work */
1114       case VariantCapablanca: /* [HGM] should work */
1115       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1116       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1117       case VariantKnightmate: /* [HGM] should work */
1118       case VariantCylinder:   /* [HGM] untested */
1119       case VariantFalcon:     /* [HGM] untested */
1120       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1121                                  offboard interposition not understood */
1122       case VariantNormal:     /* definitely works! */
1123       case VariantWildCastle: /* pieces not automatically shuffled */
1124       case VariantNoCastle:   /* pieces not automatically shuffled */
1125       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1126       case VariantLosers:     /* should work except for win condition,
1127                                  and doesn't know captures are mandatory */
1128       case VariantSuicide:    /* should work except for win condition,
1129                                  and doesn't know captures are mandatory */
1130       case VariantGiveaway:   /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantTwoKings:   /* should work */
1133       case VariantAtomic:     /* should work except for win condition */
1134       case Variant3Check:     /* should work except for win condition */
1135       case VariantShatranj:   /* should work except for all win conditions */
1136       case VariantMakruk:     /* should work except for draw countdown */
1137       case VariantBerolina:   /* might work if TestLegality is off */
1138       case VariantCapaRandom: /* should work */
1139       case VariantJanus:      /* should work */
1140       case VariantSuper:      /* experimental */
1141       case VariantGreat:      /* experimental, requires legality testing to be off */
1142       case VariantSChess:     /* S-Chess, should work */
1143       case VariantGrand:      /* should work */
1144       case VariantSpartan:    /* should work */
1145         break;
1146       }
1147     }
1148
1149 }
1150
1151 int
1152 NextIntegerFromString (char ** str, long * value)
1153 {
1154     int result = -1;
1155     char * s = *str;
1156
1157     while( *s == ' ' || *s == '\t' ) {
1158         s++;
1159     }
1160
1161     *value = 0;
1162
1163     if( *s >= '0' && *s <= '9' ) {
1164         while( *s >= '0' && *s <= '9' ) {
1165             *value = *value * 10 + (*s - '0');
1166             s++;
1167         }
1168
1169         result = 0;
1170     }
1171
1172     *str = s;
1173
1174     return result;
1175 }
1176
1177 int
1178 NextTimeControlFromString (char ** str, long * value)
1179 {
1180     long temp;
1181     int result = NextIntegerFromString( str, &temp );
1182
1183     if( result == 0 ) {
1184         *value = temp * 60; /* Minutes */
1185         if( **str == ':' ) {
1186             (*str)++;
1187             result = NextIntegerFromString( str, &temp );
1188             *value += temp; /* Seconds */
1189         }
1190     }
1191
1192     return result;
1193 }
1194
1195 int
1196 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1197 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1198     int result = -1, type = 0; long temp, temp2;
1199
1200     if(**str != ':') return -1; // old params remain in force!
1201     (*str)++;
1202     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1203     if( NextIntegerFromString( str, &temp ) ) return -1;
1204     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1205
1206     if(**str != '/') {
1207         /* time only: incremental or sudden-death time control */
1208         if(**str == '+') { /* increment follows; read it */
1209             (*str)++;
1210             if(**str == '!') type = *(*str)++; // Bronstein TC
1211             if(result = NextIntegerFromString( str, &temp2)) return -1;
1212             *inc = temp2 * 1000;
1213             if(**str == '.') { // read fraction of increment
1214                 char *start = ++(*str);
1215                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1216                 temp2 *= 1000;
1217                 while(start++ < *str) temp2 /= 10;
1218                 *inc += temp2;
1219             }
1220         } else *inc = 0;
1221         *moves = 0; *tc = temp * 1000; *incType = type;
1222         return 0;
1223     }
1224
1225     (*str)++; /* classical time control */
1226     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1227
1228     if(result == 0) {
1229         *moves = temp;
1230         *tc    = temp2 * 1000;
1231         *inc   = 0;
1232         *incType = type;
1233     }
1234     return result;
1235 }
1236
1237 int
1238 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1239 {   /* [HGM] get time to add from the multi-session time-control string */
1240     int incType, moves=1; /* kludge to force reading of first session */
1241     long time, increment;
1242     char *s = tcString;
1243
1244     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1245     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1246     do {
1247         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1248         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1249         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1250         if(movenr == -1) return time;    /* last move before new session     */
1251         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1252         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1253         if(!moves) return increment;     /* current session is incremental   */
1254         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1255     } while(movenr >= -1);               /* try again for next session       */
1256
1257     return 0; // no new time quota on this move
1258 }
1259
1260 int
1261 ParseTimeControl (char *tc, float ti, int mps)
1262 {
1263   long tc1;
1264   long tc2;
1265   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1266   int min, sec=0;
1267
1268   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1269   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1270       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1271   if(ti > 0) {
1272
1273     if(mps)
1274       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1275     else 
1276       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1277   } else {
1278     if(mps)
1279       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1280     else 
1281       snprintf(buf, MSG_SIZ, ":%s", mytc);
1282   }
1283   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1284   
1285   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1286     return FALSE;
1287   }
1288
1289   if( *tc == '/' ) {
1290     /* Parse second time control */
1291     tc++;
1292
1293     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1294       return FALSE;
1295     }
1296
1297     if( tc2 == 0 ) {
1298       return FALSE;
1299     }
1300
1301     timeControl_2 = tc2 * 1000;
1302   }
1303   else {
1304     timeControl_2 = 0;
1305   }
1306
1307   if( tc1 == 0 ) {
1308     return FALSE;
1309   }
1310
1311   timeControl = tc1 * 1000;
1312
1313   if (ti >= 0) {
1314     timeIncrement = ti * 1000;  /* convert to ms */
1315     movesPerSession = 0;
1316   } else {
1317     timeIncrement = 0;
1318     movesPerSession = mps;
1319   }
1320   return TRUE;
1321 }
1322
1323 void
1324 InitBackEnd2 ()
1325 {
1326     if (appData.debugMode) {
1327         fprintf(debugFP, "%s\n", programVersion);
1328     }
1329     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1330
1331     set_cont_sequence(appData.wrapContSeq);
1332     if (appData.matchGames > 0) {
1333         appData.matchMode = TRUE;
1334     } else if (appData.matchMode) {
1335         appData.matchGames = 1;
1336     }
1337     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1338         appData.matchGames = appData.sameColorGames;
1339     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1340         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1341         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1342     }
1343     Reset(TRUE, FALSE);
1344     if (appData.noChessProgram || first.protocolVersion == 1) {
1345       InitBackEnd3();
1346     } else {
1347       /* kludge: allow timeout for initial "feature" commands */
1348       FreezeUI();
1349       DisplayMessage("", _("Starting chess program"));
1350       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1351     }
1352 }
1353
1354 int
1355 CalculateIndex (int index, int gameNr)
1356 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1357     int res;
1358     if(index > 0) return index; // fixed nmber
1359     if(index == 0) return 1;
1360     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1361     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1362     return res;
1363 }
1364
1365 int
1366 LoadGameOrPosition (int gameNr)
1367 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1368     if (*appData.loadGameFile != NULLCHAR) {
1369         if (!LoadGameFromFile(appData.loadGameFile,
1370                 CalculateIndex(appData.loadGameIndex, gameNr),
1371                               appData.loadGameFile, FALSE)) {
1372             DisplayFatalError(_("Bad game file"), 0, 1);
1373             return 0;
1374         }
1375     } else if (*appData.loadPositionFile != NULLCHAR) {
1376         if (!LoadPositionFromFile(appData.loadPositionFile,
1377                 CalculateIndex(appData.loadPositionIndex, gameNr),
1378                                   appData.loadPositionFile)) {
1379             DisplayFatalError(_("Bad position file"), 0, 1);
1380             return 0;
1381         }
1382     }
1383     return 1;
1384 }
1385
1386 void
1387 ReserveGame (int gameNr, char resChar)
1388 {
1389     FILE *tf = fopen(appData.tourneyFile, "r+");
1390     char *p, *q, c, buf[MSG_SIZ];
1391     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1392     safeStrCpy(buf, lastMsg, MSG_SIZ);
1393     DisplayMessage(_("Pick new game"), "");
1394     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1395     ParseArgsFromFile(tf);
1396     p = q = appData.results;
1397     if(appData.debugMode) {
1398       char *r = appData.participants;
1399       fprintf(debugFP, "results = '%s'\n", p);
1400       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1401       fprintf(debugFP, "\n");
1402     }
1403     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1404     nextGame = q - p;
1405     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1406     safeStrCpy(q, p, strlen(p) + 2);
1407     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1408     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1409     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1410         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1411         q[nextGame] = '*';
1412     }
1413     fseek(tf, -(strlen(p)+4), SEEK_END);
1414     c = fgetc(tf);
1415     if(c != '"') // depending on DOS or Unix line endings we can be one off
1416          fseek(tf, -(strlen(p)+2), SEEK_END);
1417     else fseek(tf, -(strlen(p)+3), SEEK_END);
1418     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1419     DisplayMessage(buf, "");
1420     free(p); appData.results = q;
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1422        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1423       int round = appData.defaultMatchGames * appData.tourneyType;
1424       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1425          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1426         UnloadEngine(&first);  // next game belongs to other pairing;
1427         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1428     }
1429     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1430 }
1431
1432 void
1433 MatchEvent (int mode)
1434 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1435         int dummy;
1436         if(matchMode) { // already in match mode: switch it off
1437             abortMatch = TRUE;
1438             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1439             return;
1440         }
1441 //      if(gameMode != BeginningOfGame) {
1442 //          DisplayError(_("You can only start a match from the initial position."), 0);
1443 //          return;
1444 //      }
1445         abortMatch = FALSE;
1446         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1447         /* Set up machine vs. machine match */
1448         nextGame = 0;
1449         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1450         if(appData.tourneyFile[0]) {
1451             ReserveGame(-1, 0);
1452             if(nextGame > appData.matchGames) {
1453                 char buf[MSG_SIZ];
1454                 if(strchr(appData.results, '*') == NULL) {
1455                     FILE *f;
1456                     appData.tourneyCycles++;
1457                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1458                         fclose(f);
1459                         NextTourneyGame(-1, &dummy);
1460                         ReserveGame(-1, 0);
1461                         if(nextGame <= appData.matchGames) {
1462                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1463                             matchMode = mode;
1464                             ScheduleDelayedEvent(NextMatchGame, 10000);
1465                             return;
1466                         }
1467                     }
1468                 }
1469                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1470                 DisplayError(buf, 0);
1471                 appData.tourneyFile[0] = 0;
1472                 return;
1473             }
1474         } else
1475         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1476             DisplayFatalError(_("Can't have a match with no chess programs"),
1477                               0, 2);
1478             return;
1479         }
1480         matchMode = mode;
1481         matchGame = roundNr = 1;
1482         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1483         NextMatchGame();
1484 }
1485
1486 void
1487 InitBackEnd3 P((void))
1488 {
1489     GameMode initialMode;
1490     char buf[MSG_SIZ];
1491     int err, len;
1492
1493     InitChessProgram(&first, startedFromSetupPosition);
1494
1495     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1496         free(programVersion);
1497         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1498         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1499         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1500     }
1501
1502     if (appData.icsActive) {
1503 #ifdef WIN32
1504         /* [DM] Make a console window if needed [HGM] merged ifs */
1505         ConsoleCreate();
1506 #endif
1507         err = establish();
1508         if (err != 0)
1509           {
1510             if (*appData.icsCommPort != NULLCHAR)
1511               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1512                              appData.icsCommPort);
1513             else
1514               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1515                         appData.icsHost, appData.icsPort);
1516
1517             if( (len >= MSG_SIZ) && appData.debugMode )
1518               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1519
1520             DisplayFatalError(buf, err, 1);
1521             return;
1522         }
1523         SetICSMode();
1524         telnetISR =
1525           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1526         fromUserISR =
1527           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1528         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1529             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1530     } else if (appData.noChessProgram) {
1531         SetNCPMode();
1532     } else {
1533         SetGNUMode();
1534     }
1535
1536     if (*appData.cmailGameName != NULLCHAR) {
1537         SetCmailMode();
1538         OpenLoopback(&cmailPR);
1539         cmailISR =
1540           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1541     }
1542
1543     ThawUI();
1544     DisplayMessage("", "");
1545     if (StrCaseCmp(appData.initialMode, "") == 0) {
1546       initialMode = BeginningOfGame;
1547       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1548         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1549         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1550         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1551         ModeHighlight();
1552       }
1553     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1554       initialMode = TwoMachinesPlay;
1555     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1556       initialMode = AnalyzeFile;
1557     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1558       initialMode = AnalyzeMode;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1560       initialMode = MachinePlaysWhite;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1562       initialMode = MachinePlaysBlack;
1563     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1564       initialMode = EditGame;
1565     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1566       initialMode = EditPosition;
1567     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1568       initialMode = Training;
1569     } else {
1570       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1571       if( (len >= MSG_SIZ) && appData.debugMode )
1572         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1573
1574       DisplayFatalError(buf, 0, 2);
1575       return;
1576     }
1577
1578     if (appData.matchMode) {
1579         if(appData.tourneyFile[0]) { // start tourney from command line
1580             FILE *f;
1581             if(f = fopen(appData.tourneyFile, "r")) {
1582                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1583                 fclose(f);
1584                 appData.clockMode = TRUE;
1585                 SetGNUMode();
1586             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1587         }
1588         MatchEvent(TRUE);
1589     } else if (*appData.cmailGameName != NULLCHAR) {
1590         /* Set up cmail mode */
1591         ReloadCmailMsgEvent(TRUE);
1592     } else {
1593         /* Set up other modes */
1594         if (initialMode == AnalyzeFile) {
1595           if (*appData.loadGameFile == NULLCHAR) {
1596             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1597             return;
1598           }
1599         }
1600         if (*appData.loadGameFile != NULLCHAR) {
1601             (void) LoadGameFromFile(appData.loadGameFile,
1602                                     appData.loadGameIndex,
1603                                     appData.loadGameFile, TRUE);
1604         } else if (*appData.loadPositionFile != NULLCHAR) {
1605             (void) LoadPositionFromFile(appData.loadPositionFile,
1606                                         appData.loadPositionIndex,
1607                                         appData.loadPositionFile);
1608             /* [HGM] try to make self-starting even after FEN load */
1609             /* to allow automatic setup of fairy variants with wtm */
1610             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1611                 gameMode = BeginningOfGame;
1612                 setboardSpoiledMachineBlack = 1;
1613             }
1614             /* [HGM] loadPos: make that every new game uses the setup */
1615             /* from file as long as we do not switch variant          */
1616             if(!blackPlaysFirst) {
1617                 startedFromPositionFile = TRUE;
1618                 CopyBoard(filePosition, boards[0]);
1619             }
1620         }
1621         if (initialMode == AnalyzeMode) {
1622           if (appData.noChessProgram) {
1623             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1624             return;
1625           }
1626           if (appData.icsActive) {
1627             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1628             return;
1629           }
1630           AnalyzeModeEvent();
1631         } else if (initialMode == AnalyzeFile) {
1632           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1633           ShowThinkingEvent();
1634           AnalyzeFileEvent();
1635           AnalysisPeriodicEvent(1);
1636         } else if (initialMode == MachinePlaysWhite) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1639                               0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1644                               0, 2);
1645             return;
1646           }
1647           MachineWhiteEvent();
1648         } else if (initialMode == MachinePlaysBlack) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineBlackEvent();
1660         } else if (initialMode == TwoMachinesPlay) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           TwoMachinesEvent();
1672         } else if (initialMode == EditGame) {
1673           EditGameEvent();
1674         } else if (initialMode == EditPosition) {
1675           EditPositionEvent();
1676         } else if (initialMode == Training) {
1677           if (*appData.loadGameFile == NULLCHAR) {
1678             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1679             return;
1680           }
1681           TrainingEvent();
1682         }
1683     }
1684 }
1685
1686 void
1687 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1688 {
1689     DisplayBook(current+1);
1690
1691     MoveHistorySet( movelist, first, last, current, pvInfoList );
1692
1693     EvalGraphSet( first, last, current, pvInfoList );
1694
1695     MakeEngineOutputTitle();
1696 }
1697
1698 /*
1699  * Establish will establish a contact to a remote host.port.
1700  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1701  *  used to talk to the host.
1702  * Returns 0 if okay, error code if not.
1703  */
1704 int
1705 establish ()
1706 {
1707     char buf[MSG_SIZ];
1708
1709     if (*appData.icsCommPort != NULLCHAR) {
1710         /* Talk to the host through a serial comm port */
1711         return OpenCommPort(appData.icsCommPort, &icsPR);
1712
1713     } else if (*appData.gateway != NULLCHAR) {
1714         if (*appData.remoteShell == NULLCHAR) {
1715             /* Use the rcmd protocol to run telnet program on a gateway host */
1716             snprintf(buf, sizeof(buf), "%s %s %s",
1717                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1718             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1719
1720         } else {
1721             /* Use the rsh program to run telnet program on a gateway host */
1722             if (*appData.remoteUser == NULLCHAR) {
1723                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1724                         appData.gateway, appData.telnetProgram,
1725                         appData.icsHost, appData.icsPort);
1726             } else {
1727                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1728                         appData.remoteShell, appData.gateway,
1729                         appData.remoteUser, appData.telnetProgram,
1730                         appData.icsHost, appData.icsPort);
1731             }
1732             return StartChildProcess(buf, "", &icsPR);
1733
1734         }
1735     } else if (appData.useTelnet) {
1736         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1737
1738     } else {
1739         /* TCP socket interface differs somewhat between
1740            Unix and NT; handle details in the front end.
1741            */
1742         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1743     }
1744 }
1745
1746 void
1747 EscapeExpand (char *p, char *q)
1748 {       // [HGM] initstring: routine to shape up string arguments
1749         while(*p++ = *q++) if(p[-1] == '\\')
1750             switch(*q++) {
1751                 case 'n': p[-1] = '\n'; break;
1752                 case 'r': p[-1] = '\r'; break;
1753                 case 't': p[-1] = '\t'; break;
1754                 case '\\': p[-1] = '\\'; break;
1755                 case 0: *p = 0; return;
1756                 default: p[-1] = q[-1]; break;
1757             }
1758 }
1759
1760 void
1761 show_bytes (FILE *fp, char *buf, int count)
1762 {
1763     while (count--) {
1764         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1765             fprintf(fp, "\\%03o", *buf & 0xff);
1766         } else {
1767             putc(*buf, fp);
1768         }
1769         buf++;
1770     }
1771     fflush(fp);
1772 }
1773
1774 /* Returns an errno value */
1775 int
1776 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1777 {
1778     char buf[8192], *p, *q, *buflim;
1779     int left, newcount, outcount;
1780
1781     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1782         *appData.gateway != NULLCHAR) {
1783         if (appData.debugMode) {
1784             fprintf(debugFP, ">ICS: ");
1785             show_bytes(debugFP, message, count);
1786             fprintf(debugFP, "\n");
1787         }
1788         return OutputToProcess(pr, message, count, outError);
1789     }
1790
1791     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1792     p = message;
1793     q = buf;
1794     left = count;
1795     newcount = 0;
1796     while (left) {
1797         if (q >= buflim) {
1798             if (appData.debugMode) {
1799                 fprintf(debugFP, ">ICS: ");
1800                 show_bytes(debugFP, buf, newcount);
1801                 fprintf(debugFP, "\n");
1802             }
1803             outcount = OutputToProcess(pr, buf, newcount, outError);
1804             if (outcount < newcount) return -1; /* to be sure */
1805             q = buf;
1806             newcount = 0;
1807         }
1808         if (*p == '\n') {
1809             *q++ = '\r';
1810             newcount++;
1811         } else if (((unsigned char) *p) == TN_IAC) {
1812             *q++ = (char) TN_IAC;
1813             newcount ++;
1814         }
1815         *q++ = *p++;
1816         newcount++;
1817         left--;
1818     }
1819     if (appData.debugMode) {
1820         fprintf(debugFP, ">ICS: ");
1821         show_bytes(debugFP, buf, newcount);
1822         fprintf(debugFP, "\n");
1823     }
1824     outcount = OutputToProcess(pr, buf, newcount, outError);
1825     if (outcount < newcount) return -1; /* to be sure */
1826     return count;
1827 }
1828
1829 void
1830 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1831 {
1832     int outError, outCount;
1833     static int gotEof = 0;
1834
1835     /* Pass data read from player on to ICS */
1836     if (count > 0) {
1837         gotEof = 0;
1838         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1839         if (outCount < count) {
1840             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841         }
1842     } else if (count < 0) {
1843         RemoveInputSource(isr);
1844         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1845     } else if (gotEof++ > 0) {
1846         RemoveInputSource(isr);
1847         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1848     }
1849 }
1850
1851 void
1852 KeepAlive ()
1853 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1854     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1855     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1856     SendToICS("date\n");
1857     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1858 }
1859
1860 /* added routine for printf style output to ics */
1861 void
1862 ics_printf (char *format, ...)
1863 {
1864     char buffer[MSG_SIZ];
1865     va_list args;
1866
1867     va_start(args, format);
1868     vsnprintf(buffer, sizeof(buffer), format, args);
1869     buffer[sizeof(buffer)-1] = '\0';
1870     SendToICS(buffer);
1871     va_end(args);
1872 }
1873
1874 void
1875 SendToICS (char *s)
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NoProc) return;
1880
1881     count = strlen(s);
1882     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1883     if (outCount < count) {
1884         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1885     }
1886 }
1887
1888 /* This is used for sending logon scripts to the ICS. Sending
1889    without a delay causes problems when using timestamp on ICC
1890    (at least on my machine). */
1891 void
1892 SendToICSDelayed (char *s, long msdelay)
1893 {
1894     int count, outCount, outError;
1895
1896     if (icsPR == NoProc) return;
1897
1898     count = strlen(s);
1899     if (appData.debugMode) {
1900         fprintf(debugFP, ">ICS: ");
1901         show_bytes(debugFP, s, count);
1902         fprintf(debugFP, "\n");
1903     }
1904     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1905                                       msdelay);
1906     if (outCount < count) {
1907         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1908     }
1909 }
1910
1911
1912 /* Remove all highlighting escape sequences in s
1913    Also deletes any suffix starting with '('
1914    */
1915 char *
1916 StripHighlightAndTitle (char *s)
1917 {
1918     static char retbuf[MSG_SIZ];
1919     char *p = retbuf;
1920
1921     while (*s != NULLCHAR) {
1922         while (*s == '\033') {
1923             while (*s != NULLCHAR && !isalpha(*s)) s++;
1924             if (*s != NULLCHAR) s++;
1925         }
1926         while (*s != NULLCHAR && *s != '\033') {
1927             if (*s == '(' || *s == '[') {
1928                 *p = NULLCHAR;
1929                 return retbuf;
1930             }
1931             *p++ = *s++;
1932         }
1933     }
1934     *p = NULLCHAR;
1935     return retbuf;
1936 }
1937
1938 /* Remove all highlighting escape sequences in s */
1939 char *
1940 StripHighlight (char *s)
1941 {
1942     static char retbuf[MSG_SIZ];
1943     char *p = retbuf;
1944
1945     while (*s != NULLCHAR) {
1946         while (*s == '\033') {
1947             while (*s != NULLCHAR && !isalpha(*s)) s++;
1948             if (*s != NULLCHAR) s++;
1949         }
1950         while (*s != NULLCHAR && *s != '\033') {
1951             *p++ = *s++;
1952         }
1953     }
1954     *p = NULLCHAR;
1955     return retbuf;
1956 }
1957
1958 char *variantNames[] = VARIANT_NAMES;
1959 char *
1960 VariantName (VariantClass v)
1961 {
1962     return variantNames[v];
1963 }
1964
1965
1966 /* Identify a variant from the strings the chess servers use or the
1967    PGN Variant tag names we use. */
1968 VariantClass
1969 StringToVariant (char *e)
1970 {
1971     char *p;
1972     int wnum = -1;
1973     VariantClass v = VariantNormal;
1974     int i, found = FALSE;
1975     char buf[MSG_SIZ];
1976     int len;
1977
1978     if (!e) return v;
1979
1980     /* [HGM] skip over optional board-size prefixes */
1981     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1982         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1983         while( *e++ != '_');
1984     }
1985
1986     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1987         v = VariantNormal;
1988         found = TRUE;
1989     } else
1990     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1991       if (StrCaseStr(e, variantNames[i])) {
1992         v = (VariantClass) i;
1993         found = TRUE;
1994         break;
1995       }
1996     }
1997
1998     if (!found) {
1999       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2000           || StrCaseStr(e, "wild/fr")
2001           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2002         v = VariantFischeRandom;
2003       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2004                  (i = 1, p = StrCaseStr(e, "w"))) {
2005         p += i;
2006         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2007         if (isdigit(*p)) {
2008           wnum = atoi(p);
2009         } else {
2010           wnum = -1;
2011         }
2012         switch (wnum) {
2013         case 0: /* FICS only, actually */
2014         case 1:
2015           /* Castling legal even if K starts on d-file */
2016           v = VariantWildCastle;
2017           break;
2018         case 2:
2019         case 3:
2020         case 4:
2021           /* Castling illegal even if K & R happen to start in
2022              normal positions. */
2023           v = VariantNoCastle;
2024           break;
2025         case 5:
2026         case 7:
2027         case 8:
2028         case 10:
2029         case 11:
2030         case 12:
2031         case 13:
2032         case 14:
2033         case 15:
2034         case 18:
2035         case 19:
2036           /* Castling legal iff K & R start in normal positions */
2037           v = VariantNormal;
2038           break;
2039         case 6:
2040         case 20:
2041         case 21:
2042           /* Special wilds for position setup; unclear what to do here */
2043           v = VariantLoadable;
2044           break;
2045         case 9:
2046           /* Bizarre ICC game */
2047           v = VariantTwoKings;
2048           break;
2049         case 16:
2050           v = VariantKriegspiel;
2051           break;
2052         case 17:
2053           v = VariantLosers;
2054           break;
2055         case 22:
2056           v = VariantFischeRandom;
2057           break;
2058         case 23:
2059           v = VariantCrazyhouse;
2060           break;
2061         case 24:
2062           v = VariantBughouse;
2063           break;
2064         case 25:
2065           v = Variant3Check;
2066           break;
2067         case 26:
2068           /* Not quite the same as FICS suicide! */
2069           v = VariantGiveaway;
2070           break;
2071         case 27:
2072           v = VariantAtomic;
2073           break;
2074         case 28:
2075           v = VariantShatranj;
2076           break;
2077
2078         /* Temporary names for future ICC types.  The name *will* change in
2079            the next xboard/WinBoard release after ICC defines it. */
2080         case 29:
2081           v = Variant29;
2082           break;
2083         case 30:
2084           v = Variant30;
2085           break;
2086         case 31:
2087           v = Variant31;
2088           break;
2089         case 32:
2090           v = Variant32;
2091           break;
2092         case 33:
2093           v = Variant33;
2094           break;
2095         case 34:
2096           v = Variant34;
2097           break;
2098         case 35:
2099           v = Variant35;
2100           break;
2101         case 36:
2102           v = Variant36;
2103           break;
2104         case 37:
2105           v = VariantShogi;
2106           break;
2107         case 38:
2108           v = VariantXiangqi;
2109           break;
2110         case 39:
2111           v = VariantCourier;
2112           break;
2113         case 40:
2114           v = VariantGothic;
2115           break;
2116         case 41:
2117           v = VariantCapablanca;
2118           break;
2119         case 42:
2120           v = VariantKnightmate;
2121           break;
2122         case 43:
2123           v = VariantFairy;
2124           break;
2125         case 44:
2126           v = VariantCylinder;
2127           break;
2128         case 45:
2129           v = VariantFalcon;
2130           break;
2131         case 46:
2132           v = VariantCapaRandom;
2133           break;
2134         case 47:
2135           v = VariantBerolina;
2136           break;
2137         case 48:
2138           v = VariantJanus;
2139           break;
2140         case 49:
2141           v = VariantSuper;
2142           break;
2143         case 50:
2144           v = VariantGreat;
2145           break;
2146         case -1:
2147           /* Found "wild" or "w" in the string but no number;
2148              must assume it's normal chess. */
2149           v = VariantNormal;
2150           break;
2151         default:
2152           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2153           if( (len >= MSG_SIZ) && appData.debugMode )
2154             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2155
2156           DisplayError(buf, 0);
2157           v = VariantUnknown;
2158           break;
2159         }
2160       }
2161     }
2162     if (appData.debugMode) {
2163       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2164               e, wnum, VariantName(v));
2165     }
2166     return v;
2167 }
2168
2169 static int leftover_start = 0, leftover_len = 0;
2170 char star_match[STAR_MATCH_N][MSG_SIZ];
2171
2172 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2173    advance *index beyond it, and set leftover_start to the new value of
2174    *index; else return FALSE.  If pattern contains the character '*', it
2175    matches any sequence of characters not containing '\r', '\n', or the
2176    character following the '*' (if any), and the matched sequence(s) are
2177    copied into star_match.
2178    */
2179 int
2180 looking_at ( char *buf, int *index, char *pattern)
2181 {
2182     char *bufp = &buf[*index], *patternp = pattern;
2183     int star_count = 0;
2184     char *matchp = star_match[0];
2185
2186     for (;;) {
2187         if (*patternp == NULLCHAR) {
2188             *index = leftover_start = bufp - buf;
2189             *matchp = NULLCHAR;
2190             return TRUE;
2191         }
2192         if (*bufp == NULLCHAR) return FALSE;
2193         if (*patternp == '*') {
2194             if (*bufp == *(patternp + 1)) {
2195                 *matchp = NULLCHAR;
2196                 matchp = star_match[++star_count];
2197                 patternp += 2;
2198                 bufp++;
2199                 continue;
2200             } else if (*bufp == '\n' || *bufp == '\r') {
2201                 patternp++;
2202                 if (*patternp == NULLCHAR)
2203                   continue;
2204                 else
2205                   return FALSE;
2206             } else {
2207                 *matchp++ = *bufp++;
2208                 continue;
2209             }
2210         }
2211         if (*patternp != *bufp) return FALSE;
2212         patternp++;
2213         bufp++;
2214     }
2215 }
2216
2217 void
2218 SendToPlayer (char *data, int length)
2219 {
2220     int error, outCount;
2221     outCount = OutputToProcess(NoProc, data, length, &error);
2222     if (outCount < length) {
2223         DisplayFatalError(_("Error writing to display"), error, 1);
2224     }
2225 }
2226
2227 void
2228 PackHolding (char packed[], char *holding)
2229 {
2230     char *p = holding;
2231     char *q = packed;
2232     int runlength = 0;
2233     int curr = 9999;
2234     do {
2235         if (*p == curr) {
2236             runlength++;
2237         } else {
2238             switch (runlength) {
2239               case 0:
2240                 break;
2241               case 1:
2242                 *q++ = curr;
2243                 break;
2244               case 2:
2245                 *q++ = curr;
2246                 *q++ = curr;
2247                 break;
2248               default:
2249                 sprintf(q, "%d", runlength);
2250                 while (*q) q++;
2251                 *q++ = curr;
2252                 break;
2253             }
2254             runlength = 1;
2255             curr = *p;
2256         }
2257     } while (*p++);
2258     *q = NULLCHAR;
2259 }
2260
2261 /* Telnet protocol requests from the front end */
2262 void
2263 TelnetRequest (unsigned char ddww, unsigned char option)
2264 {
2265     unsigned char msg[3];
2266     int outCount, outError;
2267
2268     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2269
2270     if (appData.debugMode) {
2271         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2272         switch (ddww) {
2273           case TN_DO:
2274             ddwwStr = "DO";
2275             break;
2276           case TN_DONT:
2277             ddwwStr = "DONT";
2278             break;
2279           case TN_WILL:
2280             ddwwStr = "WILL";
2281             break;
2282           case TN_WONT:
2283             ddwwStr = "WONT";
2284             break;
2285           default:
2286             ddwwStr = buf1;
2287             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2288             break;
2289         }
2290         switch (option) {
2291           case TN_ECHO:
2292             optionStr = "ECHO";
2293             break;
2294           default:
2295             optionStr = buf2;
2296             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2297             break;
2298         }
2299         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2300     }
2301     msg[0] = TN_IAC;
2302     msg[1] = ddww;
2303     msg[2] = option;
2304     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2305     if (outCount < 3) {
2306         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2307     }
2308 }
2309
2310 void
2311 DoEcho ()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DO, TN_ECHO);
2315 }
2316
2317 void
2318 DontEcho ()
2319 {
2320     if (!appData.icsActive) return;
2321     TelnetRequest(TN_DONT, TN_ECHO);
2322 }
2323
2324 void
2325 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2326 {
2327     /* put the holdings sent to us by the server on the board holdings area */
2328     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2329     char p;
2330     ChessSquare piece;
2331
2332     if(gameInfo.holdingsWidth < 2)  return;
2333     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2334         return; // prevent overwriting by pre-board holdings
2335
2336     if( (int)lowestPiece >= BlackPawn ) {
2337         holdingsColumn = 0;
2338         countsColumn = 1;
2339         holdingsStartRow = BOARD_HEIGHT-1;
2340         direction = -1;
2341     } else {
2342         holdingsColumn = BOARD_WIDTH-1;
2343         countsColumn = BOARD_WIDTH-2;
2344         holdingsStartRow = 0;
2345         direction = 1;
2346     }
2347
2348     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2349         board[i][holdingsColumn] = EmptySquare;
2350         board[i][countsColumn]   = (ChessSquare) 0;
2351     }
2352     while( (p=*holdings++) != NULLCHAR ) {
2353         piece = CharToPiece( ToUpper(p) );
2354         if(piece == EmptySquare) continue;
2355         /*j = (int) piece - (int) WhitePawn;*/
2356         j = PieceToNumber(piece);
2357         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2358         if(j < 0) continue;               /* should not happen */
2359         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2360         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2361         board[holdingsStartRow+j*direction][countsColumn]++;
2362     }
2363 }
2364
2365
2366 void
2367 VariantSwitch (Board board, VariantClass newVariant)
2368 {
2369    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2370    static Board oldBoard;
2371
2372    startedFromPositionFile = FALSE;
2373    if(gameInfo.variant == newVariant) return;
2374
2375    /* [HGM] This routine is called each time an assignment is made to
2376     * gameInfo.variant during a game, to make sure the board sizes
2377     * are set to match the new variant. If that means adding or deleting
2378     * holdings, we shift the playing board accordingly
2379     * This kludge is needed because in ICS observe mode, we get boards
2380     * of an ongoing game without knowing the variant, and learn about the
2381     * latter only later. This can be because of the move list we requested,
2382     * in which case the game history is refilled from the beginning anyway,
2383     * but also when receiving holdings of a crazyhouse game. In the latter
2384     * case we want to add those holdings to the already received position.
2385     */
2386
2387
2388    if (appData.debugMode) {
2389      fprintf(debugFP, "Switch board from %s to %s\n",
2390              VariantName(gameInfo.variant), VariantName(newVariant));
2391      setbuf(debugFP, NULL);
2392    }
2393    shuffleOpenings = 0;       /* [HGM] shuffle */
2394    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2395    switch(newVariant)
2396      {
2397      case VariantShogi:
2398        newWidth = 9;  newHeight = 9;
2399        gameInfo.holdingsSize = 7;
2400      case VariantBughouse:
2401      case VariantCrazyhouse:
2402        newHoldingsWidth = 2; break;
2403      case VariantGreat:
2404        newWidth = 10;
2405      case VariantSuper:
2406        newHoldingsWidth = 2;
2407        gameInfo.holdingsSize = 8;
2408        break;
2409      case VariantGothic:
2410      case VariantCapablanca:
2411      case VariantCapaRandom:
2412        newWidth = 10;
2413      default:
2414        newHoldingsWidth = gameInfo.holdingsSize = 0;
2415      };
2416
2417    if(newWidth  != gameInfo.boardWidth  ||
2418       newHeight != gameInfo.boardHeight ||
2419       newHoldingsWidth != gameInfo.holdingsWidth ) {
2420
2421      /* shift position to new playing area, if needed */
2422      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2423        for(i=0; i<BOARD_HEIGHT; i++)
2424          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2425            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2426              board[i][j];
2427        for(i=0; i<newHeight; i++) {
2428          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2429          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2430        }
2431      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2432        for(i=0; i<BOARD_HEIGHT; i++)
2433          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2434            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2435              board[i][j];
2436      }
2437      gameInfo.boardWidth  = newWidth;
2438      gameInfo.boardHeight = newHeight;
2439      gameInfo.holdingsWidth = newHoldingsWidth;
2440      gameInfo.variant = newVariant;
2441      InitDrawingSizes(-2, 0);
2442    } else gameInfo.variant = newVariant;
2443    CopyBoard(oldBoard, board);   // remember correctly formatted board
2444      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2445    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2446 }
2447
2448 static int loggedOn = FALSE;
2449
2450 /*-- Game start info cache: --*/
2451 int gs_gamenum;
2452 char gs_kind[MSG_SIZ];
2453 static char player1Name[128] = "";
2454 static char player2Name[128] = "";
2455 static char cont_seq[] = "\n\\   ";
2456 static int player1Rating = -1;
2457 static int player2Rating = -1;
2458 /*----------------------------*/
2459
2460 ColorClass curColor = ColorNormal;
2461 int suppressKibitz = 0;
2462
2463 // [HGM] seekgraph
2464 Boolean soughtPending = FALSE;
2465 Boolean seekGraphUp;
2466 #define MAX_SEEK_ADS 200
2467 #define SQUARE 0x80
2468 char *seekAdList[MAX_SEEK_ADS];
2469 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2470 float tcList[MAX_SEEK_ADS];
2471 char colorList[MAX_SEEK_ADS];
2472 int nrOfSeekAds = 0;
2473 int minRating = 1010, maxRating = 2800;
2474 int hMargin = 10, vMargin = 20, h, w;
2475 extern int squareSize, lineGap;
2476
2477 void
2478 PlotSeekAd (int i)
2479 {
2480         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2481         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2482         if(r < minRating+100 && r >=0 ) r = minRating+100;
2483         if(r > maxRating) r = maxRating;
2484         if(tc < 1.) tc = 1.;
2485         if(tc > 95.) tc = 95.;
2486         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2487         y = ((double)r - minRating)/(maxRating - minRating)
2488             * (h-vMargin-squareSize/8-1) + vMargin;
2489         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2490         if(strstr(seekAdList[i], " u ")) color = 1;
2491         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2492            !strstr(seekAdList[i], "bullet") &&
2493            !strstr(seekAdList[i], "blitz") &&
2494            !strstr(seekAdList[i], "standard") ) color = 2;
2495         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2496         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2497 }
2498
2499 void
2500 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2501 {
2502         char buf[MSG_SIZ], *ext = "";
2503         VariantClass v = StringToVariant(type);
2504         if(strstr(type, "wild")) {
2505             ext = type + 4; // append wild number
2506             if(v == VariantFischeRandom) type = "chess960"; else
2507             if(v == VariantLoadable) type = "setup"; else
2508             type = VariantName(v);
2509         }
2510         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2511         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2512             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2513             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2514             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2515             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2516             seekNrList[nrOfSeekAds] = nr;
2517             zList[nrOfSeekAds] = 0;
2518             seekAdList[nrOfSeekAds++] = StrSave(buf);
2519             if(plot) PlotSeekAd(nrOfSeekAds-1);
2520         }
2521 }
2522
2523 void
2524 EraseSeekDot (int i)
2525 {
2526     int x = xList[i], y = yList[i], d=squareSize/4, k;
2527     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2528     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2529     // now replot every dot that overlapped
2530     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2531         int xx = xList[k], yy = yList[k];
2532         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2533             DrawSeekDot(xx, yy, colorList[k]);
2534     }
2535 }
2536
2537 void
2538 RemoveSeekAd (int nr)
2539 {
2540         int i;
2541         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2542             EraseSeekDot(i);
2543             if(seekAdList[i]) free(seekAdList[i]);
2544             seekAdList[i] = seekAdList[--nrOfSeekAds];
2545             seekNrList[i] = seekNrList[nrOfSeekAds];
2546             ratingList[i] = ratingList[nrOfSeekAds];
2547             colorList[i]  = colorList[nrOfSeekAds];
2548             tcList[i] = tcList[nrOfSeekAds];
2549             xList[i]  = xList[nrOfSeekAds];
2550             yList[i]  = yList[nrOfSeekAds];
2551             zList[i]  = zList[nrOfSeekAds];
2552             seekAdList[nrOfSeekAds] = NULL;
2553             break;
2554         }
2555 }
2556
2557 Boolean
2558 MatchSoughtLine (char *line)
2559 {
2560     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2561     int nr, base, inc, u=0; char dummy;
2562
2563     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2564        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2565        (u=1) &&
2566        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2568         // match: compact and save the line
2569         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2570         return TRUE;
2571     }
2572     return FALSE;
2573 }
2574
2575 int
2576 DrawSeekGraph ()
2577 {
2578     int i;
2579     if(!seekGraphUp) return FALSE;
2580     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2581     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2582
2583     DrawSeekBackground(0, 0, w, h);
2584     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2585     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2586     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2587         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2588         yy = h-1-yy;
2589         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2590         if(i%500 == 0) {
2591             char buf[MSG_SIZ];
2592             snprintf(buf, MSG_SIZ, "%d", i);
2593             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2594         }
2595     }
2596     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2597     for(i=1; i<100; i+=(i<10?1:5)) {
2598         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2599         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2600         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2601             char buf[MSG_SIZ];
2602             snprintf(buf, MSG_SIZ, "%d", i);
2603             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2604         }
2605     }
2606     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2607     return TRUE;
2608 }
2609
2610 int
2611 SeekGraphClick (ClickType click, int x, int y, int moving)
2612 {
2613     static int lastDown = 0, displayed = 0, lastSecond;
2614     if(y < 0) return FALSE;
2615     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616         if(click == Release || moving) return FALSE;
2617         nrOfSeekAds = 0;
2618         soughtPending = TRUE;
2619         SendToICS(ics_prefix);
2620         SendToICS("sought\n"); // should this be "sought all"?
2621     } else { // issue challenge based on clicked ad
2622         int dist = 10000; int i, closest = 0, second = 0;
2623         for(i=0; i<nrOfSeekAds; i++) {
2624             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2625             if(d < dist) { dist = d; closest = i; }
2626             second += (d - zList[i] < 120); // count in-range ads
2627             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2628         }
2629         if(dist < 120) {
2630             char buf[MSG_SIZ];
2631             second = (second > 1);
2632             if(displayed != closest || second != lastSecond) {
2633                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634                 lastSecond = second; displayed = closest;
2635             }
2636             if(click == Press) {
2637                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638                 lastDown = closest;
2639                 return TRUE;
2640             } // on press 'hit', only show info
2641             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643             SendToICS(ics_prefix);
2644             SendToICS(buf);
2645             return TRUE; // let incoming board of started game pop down the graph
2646         } else if(click == Release) { // release 'miss' is ignored
2647             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648             if(moving == 2) { // right up-click
2649                 nrOfSeekAds = 0; // refresh graph
2650                 soughtPending = TRUE;
2651                 SendToICS(ics_prefix);
2652                 SendToICS("sought\n"); // should this be "sought all"?
2653             }
2654             return TRUE;
2655         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656         // press miss or release hit 'pop down' seek graph
2657         seekGraphUp = FALSE;
2658         DrawPosition(TRUE, NULL);
2659     }
2660     return TRUE;
2661 }
2662
2663 void
2664 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2665 {
2666 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2667 #define STARTED_NONE 0
2668 #define STARTED_MOVES 1
2669 #define STARTED_BOARD 2
2670 #define STARTED_OBSERVE 3
2671 #define STARTED_HOLDINGS 4
2672 #define STARTED_CHATTER 5
2673 #define STARTED_COMMENT 6
2674 #define STARTED_MOVES_NOHIDE 7
2675
2676     static int started = STARTED_NONE;
2677     static char parse[20000];
2678     static int parse_pos = 0;
2679     static char buf[BUF_SIZE + 1];
2680     static int firstTime = TRUE, intfSet = FALSE;
2681     static ColorClass prevColor = ColorNormal;
2682     static int savingComment = FALSE;
2683     static int cmatch = 0; // continuation sequence match
2684     char *bp;
2685     char str[MSG_SIZ];
2686     int i, oldi;
2687     int buf_len;
2688     int next_out;
2689     int tkind;
2690     int backup;    /* [DM] For zippy color lines */
2691     char *p;
2692     char talker[MSG_SIZ]; // [HGM] chat
2693     int channel;
2694
2695     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2696
2697     if (appData.debugMode) {
2698       if (!error) {
2699         fprintf(debugFP, "<ICS: ");
2700         show_bytes(debugFP, data, count);
2701         fprintf(debugFP, "\n");
2702       }
2703     }
2704
2705     if (appData.debugMode) { int f = forwardMostMove;
2706         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2707                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2708                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2709     }
2710     if (count > 0) {
2711         /* If last read ended with a partial line that we couldn't parse,
2712            prepend it to the new read and try again. */
2713         if (leftover_len > 0) {
2714             for (i=0; i<leftover_len; i++)
2715               buf[i] = buf[leftover_start + i];
2716         }
2717
2718     /* copy new characters into the buffer */
2719     bp = buf + leftover_len;
2720     buf_len=leftover_len;
2721     for (i=0; i<count; i++)
2722     {
2723         // ignore these
2724         if (data[i] == '\r')
2725             continue;
2726
2727         // join lines split by ICS?
2728         if (!appData.noJoin)
2729         {
2730             /*
2731                 Joining just consists of finding matches against the
2732                 continuation sequence, and discarding that sequence
2733                 if found instead of copying it.  So, until a match
2734                 fails, there's nothing to do since it might be the
2735                 complete sequence, and thus, something we don't want
2736                 copied.
2737             */
2738             if (data[i] == cont_seq[cmatch])
2739             {
2740                 cmatch++;
2741                 if (cmatch == strlen(cont_seq))
2742                 {
2743                     cmatch = 0; // complete match.  just reset the counter
2744
2745                     /*
2746                         it's possible for the ICS to not include the space
2747                         at the end of the last word, making our [correct]
2748                         join operation fuse two separate words.  the server
2749                         does this when the space occurs at the width setting.
2750                     */
2751                     if (!buf_len || buf[buf_len-1] != ' ')
2752                     {
2753                         *bp++ = ' ';
2754                         buf_len++;
2755                     }
2756                 }
2757                 continue;
2758             }
2759             else if (cmatch)
2760             {
2761                 /*
2762                     match failed, so we have to copy what matched before
2763                     falling through and copying this character.  In reality,
2764                     this will only ever be just the newline character, but
2765                     it doesn't hurt to be precise.
2766                 */
2767                 strncpy(bp, cont_seq, cmatch);
2768                 bp += cmatch;
2769                 buf_len += cmatch;
2770                 cmatch = 0;
2771             }
2772         }
2773
2774         // copy this char
2775         *bp++ = data[i];
2776         buf_len++;
2777     }
2778
2779         buf[buf_len] = NULLCHAR;
2780 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2781         next_out = 0;
2782         leftover_start = 0;
2783
2784         i = 0;
2785         while (i < buf_len) {
2786             /* Deal with part of the TELNET option negotiation
2787                protocol.  We refuse to do anything beyond the
2788                defaults, except that we allow the WILL ECHO option,
2789                which ICS uses to turn off password echoing when we are
2790                directly connected to it.  We reject this option
2791                if localLineEditing mode is on (always on in xboard)
2792                and we are talking to port 23, which might be a real
2793                telnet server that will try to keep WILL ECHO on permanently.
2794              */
2795             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2796                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2797                 unsigned char option;
2798                 oldi = i;
2799                 switch ((unsigned char) buf[++i]) {
2800                   case TN_WILL:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<WILL ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       case TN_ECHO:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "ECHO ");
2807                         /* Reply only if this is a change, according
2808                            to the protocol rules. */
2809                         if (remoteEchoOption) break;
2810                         if (appData.localLineEditing &&
2811                             atoi(appData.icsPort) == TN_PORT) {
2812                             TelnetRequest(TN_DONT, TN_ECHO);
2813                         } else {
2814                             EchoOff();
2815                             TelnetRequest(TN_DO, TN_ECHO);
2816                             remoteEchoOption = TRUE;
2817                         }
2818                         break;
2819                       default:
2820                         if (appData.debugMode)
2821                           fprintf(debugFP, "%d ", option);
2822                         /* Whatever this is, we don't want it. */
2823                         TelnetRequest(TN_DONT, option);
2824                         break;
2825                     }
2826                     break;
2827                   case TN_WONT:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<WONT ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       case TN_ECHO:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "ECHO ");
2834                         /* Reply only if this is a change, according
2835                            to the protocol rules. */
2836                         if (!remoteEchoOption) break;
2837                         EchoOn();
2838                         TelnetRequest(TN_DONT, TN_ECHO);
2839                         remoteEchoOption = FALSE;
2840                         break;
2841                       default:
2842                         if (appData.debugMode)
2843                           fprintf(debugFP, "%d ", (unsigned char) option);
2844                         /* Whatever this is, it must already be turned
2845                            off, because we never agree to turn on
2846                            anything non-default, so according to the
2847                            protocol rules, we don't reply. */
2848                         break;
2849                     }
2850                     break;
2851                   case TN_DO:
2852                     if (appData.debugMode)
2853                       fprintf(debugFP, "\n<DO ");
2854                     switch (option = (unsigned char) buf[++i]) {
2855                       default:
2856                         /* Whatever this is, we refuse to do it. */
2857                         if (appData.debugMode)
2858                           fprintf(debugFP, "%d ", option);
2859                         TelnetRequest(TN_WONT, option);
2860                         break;
2861                     }
2862                     break;
2863                   case TN_DONT:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<DONT ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       default:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         /* Whatever this is, we are already not doing
2871                            it, because we never agree to do anything
2872                            non-default, so according to the protocol
2873                            rules, we don't reply. */
2874                         break;
2875                     }
2876                     break;
2877                   case TN_IAC:
2878                     if (appData.debugMode)
2879                       fprintf(debugFP, "\n<IAC ");
2880                     /* Doubled IAC; pass it through */
2881                     i--;
2882                     break;
2883                   default:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2886                     /* Drop all other telnet commands on the floor */
2887                     break;
2888                 }
2889                 if (oldi > next_out)
2890                   SendToPlayer(&buf[next_out], oldi - next_out);
2891                 if (++i > next_out)
2892                   next_out = i;
2893                 continue;
2894             }
2895
2896             /* OK, this at least will *usually* work */
2897             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2898                 loggedOn = TRUE;
2899             }
2900
2901             if (loggedOn && !intfSet) {
2902                 if (ics_type == ICS_ICC) {
2903                   snprintf(str, MSG_SIZ,
2904                           "/set-quietly interface %s\n/set-quietly style 12\n",
2905                           programVersion);
2906                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2907                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2908                 } else if (ics_type == ICS_CHESSNET) {
2909                   snprintf(str, MSG_SIZ, "/style 12\n");
2910                 } else {
2911                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2912                   strcat(str, programVersion);
2913                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2914                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2916 #ifdef WIN32
2917                   strcat(str, "$iset nohighlight 1\n");
2918 #endif
2919                   strcat(str, "$iset lock 1\n$style 12\n");
2920                 }
2921                 SendToICS(str);
2922                 NotifyFrontendLogin();
2923                 intfSet = TRUE;
2924             }
2925
2926             if (started == STARTED_COMMENT) {
2927                 /* Accumulate characters in comment */
2928                 parse[parse_pos++] = buf[i];
2929                 if (buf[i] == '\n') {
2930                     parse[parse_pos] = NULLCHAR;
2931                     if(chattingPartner>=0) {
2932                         char mess[MSG_SIZ];
2933                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2934                         OutputChatMessage(chattingPartner, mess);
2935                         chattingPartner = -1;
2936                         next_out = i+1; // [HGM] suppress printing in ICS window
2937                     } else
2938                     if(!suppressKibitz) // [HGM] kibitz
2939                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2940                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2941                         int nrDigit = 0, nrAlph = 0, j;
2942                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2943                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2944                         parse[parse_pos] = NULLCHAR;
2945                         // try to be smart: if it does not look like search info, it should go to
2946                         // ICS interaction window after all, not to engine-output window.
2947                         for(j=0; j<parse_pos; j++) { // count letters and digits
2948                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2949                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2950                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2951                         }
2952                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2953                             int depth=0; float score;
2954                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2955                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2956                                 pvInfoList[forwardMostMove-1].depth = depth;
2957                                 pvInfoList[forwardMostMove-1].score = 100*score;
2958                             }
2959                             OutputKibitz(suppressKibitz, parse);
2960                         } else {
2961                             char tmp[MSG_SIZ];
2962                             if(gameMode == IcsObserving) // restore original ICS messages
2963                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2964                             else
2965                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2966                             SendToPlayer(tmp, strlen(tmp));
2967                         }
2968                         next_out = i+1; // [HGM] suppress printing in ICS window
2969                     }
2970                     started = STARTED_NONE;
2971                 } else {
2972                     /* Don't match patterns against characters in comment */
2973                     i++;
2974                     continue;
2975                 }
2976             }
2977             if (started == STARTED_CHATTER) {
2978                 if (buf[i] != '\n') {
2979                     /* Don't match patterns against characters in chatter */
2980                     i++;
2981                     continue;
2982                 }
2983                 started = STARTED_NONE;
2984                 if(suppressKibitz) next_out = i+1;
2985             }
2986
2987             /* Kludge to deal with rcmd protocol */
2988             if (firstTime && looking_at(buf, &i, "\001*")) {
2989                 DisplayFatalError(&buf[1], 0, 1);
2990                 continue;
2991             } else {
2992                 firstTime = FALSE;
2993             }
2994
2995             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2996                 ics_type = ICS_ICC;
2997                 ics_prefix = "/";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3003                 ics_type = ICS_FICS;
3004                 ics_prefix = "$";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3010                 ics_type = ICS_CHESSNET;
3011                 ics_prefix = "/";
3012                 if (appData.debugMode)
3013                   fprintf(debugFP, "ics_type %d\n", ics_type);
3014                 continue;
3015             }
3016
3017             if (!loggedOn &&
3018                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3019                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3020                  looking_at(buf, &i, "will be \"*\""))) {
3021               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3022               continue;
3023             }
3024
3025             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3026               char buf[MSG_SIZ];
3027               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3028               DisplayIcsInteractionTitle(buf);
3029               have_set_title = TRUE;
3030             }
3031
3032             /* skip finger notes */
3033             if (started == STARTED_NONE &&
3034                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3035                  (buf[i] == '1' && buf[i+1] == '0')) &&
3036                 buf[i+2] == ':' && buf[i+3] == ' ') {
3037               started = STARTED_CHATTER;
3038               i += 3;
3039               continue;
3040             }
3041
3042             oldi = i;
3043             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3044             if(appData.seekGraph) {
3045                 if(soughtPending && MatchSoughtLine(buf+i)) {
3046                     i = strstr(buf+i, "rated") - buf;
3047                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3048                     next_out = leftover_start = i;
3049                     started = STARTED_CHATTER;
3050                     suppressKibitz = TRUE;
3051                     continue;
3052                 }
3053                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3054                         && looking_at(buf, &i, "* ads displayed")) {
3055                     soughtPending = FALSE;
3056                     seekGraphUp = TRUE;
3057                     DrawSeekGraph();
3058                     continue;
3059                 }
3060                 if(appData.autoRefresh) {
3061                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3062                         int s = (ics_type == ICS_ICC); // ICC format differs
3063                         if(seekGraphUp)
3064                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3065                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3066                         looking_at(buf, &i, "*% "); // eat prompt
3067                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3068                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069                         next_out = i; // suppress
3070                         continue;
3071                     }
3072                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3073                         char *p = star_match[0];
3074                         while(*p) {
3075                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3076                             while(*p && *p++ != ' '); // next
3077                         }
3078                         looking_at(buf, &i, "*% "); // eat prompt
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         continue;
3082                     }
3083                 }
3084             }
3085
3086             /* skip formula vars */
3087             if (started == STARTED_NONE &&
3088                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3089               started = STARTED_CHATTER;
3090               i += 3;
3091               continue;
3092             }
3093
3094             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3095             if (appData.autoKibitz && started == STARTED_NONE &&
3096                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3097                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3098                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3099                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3100                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3101                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3102                         suppressKibitz = TRUE;
3103                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3104                         next_out = i;
3105                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3106                                 && (gameMode == IcsPlayingWhite)) ||
3107                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3108                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3109                             started = STARTED_CHATTER; // own kibitz we simply discard
3110                         else {
3111                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3112                             parse_pos = 0; parse[0] = NULLCHAR;
3113                             savingComment = TRUE;
3114                             suppressKibitz = gameMode != IcsObserving ? 2 :
3115                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3116                         }
3117                         continue;
3118                 } else
3119                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3120                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3121                          && atoi(star_match[0])) {
3122                     // suppress the acknowledgements of our own autoKibitz
3123                     char *p;
3124                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3126                     SendToPlayer(star_match[0], strlen(star_match[0]));
3127                     if(looking_at(buf, &i, "*% ")) // eat prompt
3128                         suppressKibitz = FALSE;
3129                     next_out = i;
3130                     continue;
3131                 }
3132             } // [HGM] kibitz: end of patch
3133
3134             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3135
3136             // [HGM] chat: intercept tells by users for which we have an open chat window
3137             channel = -1;
3138             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139                                            looking_at(buf, &i, "* whispers:") ||
3140                                            looking_at(buf, &i, "* kibitzes:") ||
3141                                            looking_at(buf, &i, "* shouts:") ||
3142                                            looking_at(buf, &i, "* c-shouts:") ||
3143                                            looking_at(buf, &i, "--> * ") ||
3144                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3148                 int p;
3149                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150                 chattingPartner = -1;
3151
3152                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155                     talker[0] = '['; strcat(talker, "] ");
3156                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157                     chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("kibitzes", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168                 for(p=0; p<MAX_CHAT; p++) {
3169                     if(!strcmp("whispers", chatPartner[p])) {
3170                         talker[0] = '['; strcat(talker, "] ");
3171                         chattingPartner = p; break;
3172                     }
3173                 } else
3174                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175                   if(buf[i-8] == '-' && buf[i-3] == 't')
3176                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177                     if(!strcmp("c-shouts", chatPartner[p])) {
3178                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                   if(chattingPartner < 0)
3183                   for(p=0; p<MAX_CHAT; p++) {
3184                     if(!strcmp("shouts", chatPartner[p])) {
3185                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188                         chattingPartner = p; break;
3189                     }
3190                   }
3191                 }
3192                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194                     talker[0] = 0; Colorize(ColorTell, FALSE);
3195                     chattingPartner = p; break;
3196                 }
3197                 if(chattingPartner<0) i = oldi; else {
3198                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     started = STARTED_COMMENT;
3202                     parse_pos = 0; parse[0] = NULLCHAR;
3203                     savingComment = 3 + chattingPartner; // counts as TRUE
3204                     suppressKibitz = TRUE;
3205                     continue;
3206                 }
3207             } // [HGM] chat: end of patch
3208
3209           backup = i;
3210             if (appData.zippyTalk || appData.zippyPlay) {
3211                 /* [DM] Backup address for color zippy lines */
3212 #if ZIPPY
3213                if (loggedOn == TRUE)
3214                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3216 #endif
3217             } // [DM] 'else { ' deleted
3218                 if (
3219                     /* Regular tells and says */
3220                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3222                     looking_at(buf, &i, "* says: ") ||
3223                     /* Don't color "message" or "messages" output */
3224                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225                     looking_at(buf, &i, "*. * at *:*: ") ||
3226                     looking_at(buf, &i, "--* (*:*): ") ||
3227                     /* Message notifications (same color as tells) */
3228                     looking_at(buf, &i, "* has left a message ") ||
3229                     looking_at(buf, &i, "* just sent you a message:\n") ||
3230                     /* Whispers and kibitzes */
3231                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232                     looking_at(buf, &i, "* kibitzes: ") ||
3233                     /* Channel tells */
3234                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3235
3236                   if (tkind == 1 && strchr(star_match[0], ':')) {
3237                       /* Avoid "tells you:" spoofs in channels */
3238                      tkind = 3;
3239                   }
3240                   if (star_match[0][0] == NULLCHAR ||
3241                       strchr(star_match[0], ' ') ||
3242                       (tkind == 3 && strchr(star_match[1], ' '))) {
3243                     /* Reject bogus matches */
3244                     i = oldi;
3245                   } else {
3246                     if (appData.colorize) {
3247                       if (oldi > next_out) {
3248                         SendToPlayer(&buf[next_out], oldi - next_out);
3249                         next_out = oldi;
3250                       }
3251                       switch (tkind) {
3252                       case 1:
3253                         Colorize(ColorTell, FALSE);
3254                         curColor = ColorTell;
3255                         break;
3256                       case 2:
3257                         Colorize(ColorKibitz, FALSE);
3258                         curColor = ColorKibitz;
3259                         break;
3260                       case 3:
3261                         p = strrchr(star_match[1], '(');
3262                         if (p == NULL) {
3263                           p = star_match[1];
3264                         } else {
3265                           p++;
3266                         }
3267                         if (atoi(p) == 1) {
3268                           Colorize(ColorChannel1, FALSE);
3269                           curColor = ColorChannel1;
3270                         } else {
3271                           Colorize(ColorChannel, FALSE);
3272                           curColor = ColorChannel;
3273                         }
3274                         break;
3275                       case 5:
3276                         curColor = ColorNormal;
3277                         break;
3278                       }
3279                     }
3280                     if (started == STARTED_NONE && appData.autoComment &&
3281                         (gameMode == IcsObserving ||
3282                          gameMode == IcsPlayingWhite ||
3283                          gameMode == IcsPlayingBlack)) {
3284                       parse_pos = i - oldi;
3285                       memcpy(parse, &buf[oldi], parse_pos);
3286                       parse[parse_pos] = NULLCHAR;
3287                       started = STARTED_COMMENT;
3288                       savingComment = TRUE;
3289                     } else {
3290                       started = STARTED_CHATTER;
3291                       savingComment = FALSE;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                   }
3296                 }
3297
3298                 if (looking_at(buf, &i, "* s-shouts: ") ||
3299                     looking_at(buf, &i, "* c-shouts: ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorSShout, FALSE);
3306                         curColor = ColorSShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "--->")) {
3314                     loggedOn = TRUE;
3315                     continue;
3316                 }
3317
3318                 if (looking_at(buf, &i, "* shouts: ") ||
3319                     looking_at(buf, &i, "--> ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorShout, FALSE);
3326                         curColor = ColorShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at( buf, &i, "Challenge:")) {
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorChallenge, FALSE);
3340                         curColor = ColorChallenge;
3341                     }
3342                     loggedOn = TRUE;
3343                     continue;
3344                 }
3345
3346                 if (looking_at(buf, &i, "* offers you") ||
3347                     looking_at(buf, &i, "* offers to be") ||
3348                     looking_at(buf, &i, "* would like to") ||
3349                     looking_at(buf, &i, "* requests to") ||
3350                     looking_at(buf, &i, "Your opponent offers") ||
3351                     looking_at(buf, &i, "Your opponent requests")) {
3352
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorRequest, FALSE);
3359                         curColor = ColorRequest;
3360                     }
3361                     continue;
3362                 }
3363
3364                 if (looking_at(buf, &i, "* (*) seeking")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSeek, FALSE);
3371                         curColor = ColorSeek;
3372                     }
3373                     continue;
3374             }
3375
3376           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3377
3378             if (looking_at(buf, &i, "\\   ")) {
3379                 if (prevColor != ColorNormal) {
3380                     if (oldi > next_out) {
3381                         SendToPlayer(&buf[next_out], oldi - next_out);
3382                         next_out = oldi;
3383                     }
3384                     Colorize(prevColor, TRUE);
3385                     curColor = prevColor;
3386                 }
3387                 if (savingComment) {
3388                     parse_pos = i - oldi;
3389                     memcpy(parse, &buf[oldi], parse_pos);
3390                     parse[parse_pos] = NULLCHAR;
3391                     started = STARTED_COMMENT;
3392                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393                         chattingPartner = savingComment - 3; // kludge to remember the box
3394                 } else {
3395                     started = STARTED_CHATTER;
3396                 }
3397                 continue;
3398             }
3399
3400             if (looking_at(buf, &i, "Black Strength :") ||
3401                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402                 looking_at(buf, &i, "<10>") ||
3403                 looking_at(buf, &i, "#@#")) {
3404                 /* Wrong board style */
3405                 loggedOn = TRUE;
3406                 SendToICS(ics_prefix);
3407                 SendToICS("set style 12\n");
3408                 SendToICS(ics_prefix);
3409                 SendToICS("refresh\n");
3410                 continue;
3411             }
3412
3413             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3414                 ICSInitScript();
3415                 have_sent_ICS_logon = 1;
3416                 continue;
3417             }
3418
3419             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420                 (looking_at(buf, &i, "\n<12> ") ||
3421                  looking_at(buf, &i, "<12> "))) {
3422                 loggedOn = TRUE;
3423                 if (oldi > next_out) {
3424                     SendToPlayer(&buf[next_out], oldi - next_out);
3425                 }
3426                 next_out = i;
3427                 started = STARTED_BOARD;
3428                 parse_pos = 0;
3429                 continue;
3430             }
3431
3432             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3433                 looking_at(buf, &i, "<b1> ")) {
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_HOLDINGS;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3444                 loggedOn = TRUE;
3445                 /* Header for a move list -- first line */
3446
3447                 switch (ics_getting_history) {
3448                   case H_FALSE:
3449                     switch (gameMode) {
3450                       case IcsIdle:
3451                       case BeginningOfGame:
3452                         /* User typed "moves" or "oldmoves" while we
3453                            were idle.  Pretend we asked for these
3454                            moves and soak them up so user can step
3455                            through them and/or save them.
3456                            */
3457                         Reset(FALSE, TRUE);
3458                         gameMode = IcsObserving;
3459                         ModeHighlight();
3460                         ics_gamenum = -1;
3461                         ics_getting_history = H_GOT_UNREQ_HEADER;
3462                         break;
3463                       case EditGame: /*?*/
3464                       case EditPosition: /*?*/
3465                         /* Should above feature work in these modes too? */
3466                         /* For now it doesn't */
3467                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3468                         break;
3469                       default:
3470                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3471                         break;
3472                     }
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Is this the right one? */
3476                     if (gameInfo.white && gameInfo.black &&
3477                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3478                         strcmp(gameInfo.black, star_match[2]) == 0) {
3479                         /* All is well */
3480                         ics_getting_history = H_GOT_REQ_HEADER;
3481                     }
3482                     break;
3483                   case H_GOT_REQ_HEADER:
3484                   case H_GOT_UNREQ_HEADER:
3485                   case H_GOT_UNWANTED_HEADER:
3486                   case H_GETTING_MOVES:
3487                     /* Should not happen */
3488                     DisplayError(_("Error gathering move list: two headers"), 0);
3489                     ics_getting_history = H_FALSE;
3490                     break;
3491                 }
3492
3493                 /* Save player ratings into gameInfo if needed */
3494                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3495                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3496                     (gameInfo.whiteRating == -1 ||
3497                      gameInfo.blackRating == -1)) {
3498
3499                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3500                     gameInfo.blackRating = string_to_rating(star_match[3]);
3501                     if (appData.debugMode)
3502                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3503                               gameInfo.whiteRating, gameInfo.blackRating);
3504                 }
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i,
3509               "* * match, initial time: * minute*, increment: * second")) {
3510                 /* Header for a move list -- second line */
3511                 /* Initial board will follow if this is a wild game */
3512                 if (gameInfo.event != NULL) free(gameInfo.event);
3513                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3514                 gameInfo.event = StrSave(str);
3515                 /* [HGM] we switched variant. Translate boards if needed. */
3516                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i, "Move  ")) {
3521                 /* Beginning of a move list */
3522                 switch (ics_getting_history) {
3523                   case H_FALSE:
3524                     /* Normally should not happen */
3525                     /* Maybe user hit reset while we were parsing */
3526                     break;
3527                   case H_REQUESTED:
3528                     /* Happens if we are ignoring a move list that is not
3529                      * the one we just requested.  Common if the user
3530                      * tries to observe two games without turning off
3531                      * getMoveList */
3532                     break;
3533                   case H_GETTING_MOVES:
3534                     /* Should not happen */
3535                     DisplayError(_("Error gathering move list: nested"), 0);
3536                     ics_getting_history = H_FALSE;
3537                     break;
3538                   case H_GOT_REQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES;
3541                     parse_pos = 0;
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                     }
3545                     break;
3546                   case H_GOT_UNREQ_HEADER:
3547                     ics_getting_history = H_GETTING_MOVES;
3548                     started = STARTED_MOVES_NOHIDE;
3549                     parse_pos = 0;
3550                     break;
3551                   case H_GOT_UNWANTED_HEADER:
3552                     ics_getting_history = H_FALSE;
3553                     break;
3554                 }
3555                 continue;
3556             }
3557
3558             if (looking_at(buf, &i, "% ") ||
3559                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3560                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3561                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3562                     soughtPending = FALSE;
3563                     seekGraphUp = TRUE;
3564                     DrawSeekGraph();
3565                 }
3566                 if(suppressKibitz) next_out = i;
3567                 savingComment = FALSE;
3568                 suppressKibitz = 0;
3569                 switch (started) {
3570                   case STARTED_MOVES:
3571                   case STARTED_MOVES_NOHIDE:
3572                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3573                     parse[parse_pos + i - oldi] = NULLCHAR;
3574                     ParseGameHistory(parse);
3575 #if ZIPPY
3576                     if (appData.zippyPlay && first.initDone) {
3577                         FeedMovesToProgram(&first, forwardMostMove);
3578                         if (gameMode == IcsPlayingWhite) {
3579                             if (WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("black\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, TRUE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, TRUE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         } else if (gameMode == IcsPlayingBlack) {
3603                             if (!WhiteOnMove(forwardMostMove)) {
3604                                 if (first.sendTime) {
3605                                   if (first.useColors) {
3606                                     SendToProgram("white\n", &first);
3607                                   }
3608                                   SendTimeRemaining(&first, FALSE);
3609                                 }
3610                                 if (first.useColors) {
3611                                   SendToProgram("black\n", &first);
3612                                 }
3613                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3614                                 first.maybeThinking = TRUE;
3615                             } else {
3616                                 if (first.usePlayother) {
3617                                   if (first.sendTime) {
3618                                     SendTimeRemaining(&first, FALSE);
3619                                   }
3620                                   SendToProgram("playother\n", &first);
3621                                   firstMove = FALSE;
3622                                 } else {
3623                                   firstMove = TRUE;
3624                                 }
3625                             }
3626                         }
3627                     }
3628 #endif
3629                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3630                         /* Moves came from oldmoves or moves command
3631                            while we weren't doing anything else.
3632                            */
3633                         currentMove = forwardMostMove;
3634                         ClearHighlights();/*!!could figure this out*/
3635                         flipView = appData.flipView;
3636                         DrawPosition(TRUE, boards[currentMove]);
3637                         DisplayBothClocks();
3638                         snprintf(str, MSG_SIZ, "%s %s %s",
3639                                 gameInfo.white, _("vs."),  gameInfo.black);
3640                         DisplayTitle(str);
3641                         gameMode = IcsIdle;
3642                     } else {
3643                         /* Moves were history of an active game */
3644                         if (gameInfo.resultDetails != NULL) {
3645                             free(gameInfo.resultDetails);
3646                             gameInfo.resultDetails = NULL;
3647                         }
3648                     }
3649                     HistorySet(parseList, backwardMostMove,
3650                                forwardMostMove, currentMove-1);
3651                     DisplayMove(currentMove - 1);
3652                     if (started == STARTED_MOVES) next_out = i;
3653                     started = STARTED_NONE;
3654                     ics_getting_history = H_FALSE;
3655                     break;
3656
3657                   case STARTED_OBSERVE:
3658                     started = STARTED_NONE;
3659                     SendToICS(ics_prefix);
3660                     SendToICS("refresh\n");
3661                     break;
3662
3663                   default:
3664                     break;
3665                 }
3666                 if(bookHit) { // [HGM] book: simulate book reply
3667                     static char bookMove[MSG_SIZ]; // a bit generous?
3668
3669                     programStats.nodes = programStats.depth = programStats.time =
3670                     programStats.score = programStats.got_only_move = 0;
3671                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3672
3673                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3674                     strcat(bookMove, bookHit);
3675                     HandleMachineMove(bookMove, &first);
3676                 }
3677                 continue;
3678             }
3679
3680             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3681                  started == STARTED_HOLDINGS ||
3682                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3683                 /* Accumulate characters in move list or board */
3684                 parse[parse_pos++] = buf[i];
3685             }
3686
3687             /* Start of game messages.  Mostly we detect start of game
3688                when the first board image arrives.  On some versions
3689                of the ICS, though, we need to do a "refresh" after starting
3690                to observe in order to get the current board right away. */
3691             if (looking_at(buf, &i, "Adding game * to observation list")) {
3692                 started = STARTED_OBSERVE;
3693                 continue;
3694             }
3695
3696             /* Handle auto-observe */
3697             if (appData.autoObserve &&
3698                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3699                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3700                 char *player;
3701                 /* Choose the player that was highlighted, if any. */
3702                 if (star_match[0][0] == '\033' ||
3703                     star_match[1][0] != '\033') {
3704                     player = star_match[0];
3705                 } else {
3706                     player = star_match[2];
3707                 }
3708                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3709                         ics_prefix, StripHighlightAndTitle(player));
3710                 SendToICS(str);
3711
3712                 /* Save ratings from notify string */
3713                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3714                 player1Rating = string_to_rating(star_match[1]);
3715                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3716                 player2Rating = string_to_rating(star_match[3]);
3717
3718                 if (appData.debugMode)
3719                   fprintf(debugFP,
3720                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3721                           player1Name, player1Rating,
3722                           player2Name, player2Rating);
3723
3724                 continue;
3725             }
3726
3727             /* Deal with automatic examine mode after a game,
3728                and with IcsObserving -> IcsExamining transition */
3729             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3730                 looking_at(buf, &i, "has made you an examiner of game *")) {
3731
3732                 int gamenum = atoi(star_match[0]);
3733                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3734                     gamenum == ics_gamenum) {
3735                     /* We were already playing or observing this game;
3736                        no need to refetch history */
3737                     gameMode = IcsExamining;
3738                     if (pausing) {
3739                         pauseExamForwardMostMove = forwardMostMove;
3740                     } else if (currentMove < forwardMostMove) {
3741                         ForwardInner(forwardMostMove);
3742                     }
3743                 } else {
3744                     /* I don't think this case really can happen */
3745                     SendToICS(ics_prefix);
3746                     SendToICS("refresh\n");
3747                 }
3748                 continue;
3749             }
3750
3751             /* Error messages */
3752 //          if (ics_user_moved) {
3753             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3754                 if (looking_at(buf, &i, "Illegal move") ||
3755                     looking_at(buf, &i, "Not a legal move") ||
3756                     looking_at(buf, &i, "Your king is in check") ||
3757                     looking_at(buf, &i, "It isn't your turn") ||
3758                     looking_at(buf, &i, "It is not your move")) {
3759                     /* Illegal move */
3760                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3761                         currentMove = forwardMostMove-1;
3762                         DisplayMove(currentMove - 1); /* before DMError */
3763                         DrawPosition(FALSE, boards[currentMove]);
3764                         SwitchClocks(forwardMostMove-1); // [HGM] race
3765                         DisplayBothClocks();
3766                     }
3767                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3768                     ics_user_moved = 0;
3769                     continue;
3770                 }
3771             }
3772
3773             if (looking_at(buf, &i, "still have time") ||
3774                 looking_at(buf, &i, "not out of time") ||
3775                 looking_at(buf, &i, "either player is out of time") ||
3776                 looking_at(buf, &i, "has timeseal; checking")) {
3777                 /* We must have called his flag a little too soon */
3778                 whiteFlag = blackFlag = FALSE;
3779                 continue;
3780             }
3781
3782             if (looking_at(buf, &i, "added * seconds to") ||
3783                 looking_at(buf, &i, "seconds were added to")) {
3784                 /* Update the clocks */
3785                 SendToICS(ics_prefix);
3786                 SendToICS("refresh\n");
3787                 continue;
3788             }
3789
3790             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3791                 ics_clock_paused = TRUE;
3792                 StopClocks();
3793                 continue;
3794             }
3795
3796             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3797                 ics_clock_paused = FALSE;
3798                 StartClocks();
3799                 continue;
3800             }
3801
3802             /* Grab player ratings from the Creating: message.
3803                Note we have to check for the special case when
3804                the ICS inserts things like [white] or [black]. */
3805             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3806                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3807                 /* star_matches:
3808                    0    player 1 name (not necessarily white)
3809                    1    player 1 rating
3810                    2    empty, white, or black (IGNORED)
3811                    3    player 2 name (not necessarily black)
3812                    4    player 2 rating
3813
3814                    The names/ratings are sorted out when the game
3815                    actually starts (below).
3816                 */
3817                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3818                 player1Rating = string_to_rating(star_match[1]);
3819                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3820                 player2Rating = string_to_rating(star_match[4]);
3821
3822                 if (appData.debugMode)
3823                   fprintf(debugFP,
3824                           "Ratings from 'Creating:' %s %d, %s %d\n",
3825                           player1Name, player1Rating,
3826                           player2Name, player2Rating);
3827
3828                 continue;
3829             }
3830
3831             /* Improved generic start/end-of-game messages */
3832             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3833                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3834                 /* If tkind == 0: */
3835                 /* star_match[0] is the game number */
3836                 /*           [1] is the white player's name */
3837                 /*           [2] is the black player's name */
3838                 /* For end-of-game: */
3839                 /*           [3] is the reason for the game end */
3840                 /*           [4] is a PGN end game-token, preceded by " " */
3841                 /* For start-of-game: */
3842                 /*           [3] begins with "Creating" or "Continuing" */
3843                 /*           [4] is " *" or empty (don't care). */
3844                 int gamenum = atoi(star_match[0]);
3845                 char *whitename, *blackname, *why, *endtoken;
3846                 ChessMove endtype = EndOfFile;
3847
3848                 if (tkind == 0) {
3849                   whitename = star_match[1];
3850                   blackname = star_match[2];
3851                   why = star_match[3];
3852                   endtoken = star_match[4];
3853                 } else {
3854                   whitename = star_match[1];
3855                   blackname = star_match[3];
3856                   why = star_match[5];
3857                   endtoken = star_match[6];
3858                 }
3859
3860                 /* Game start messages */
3861                 if (strncmp(why, "Creating ", 9) == 0 ||
3862                     strncmp(why, "Continuing ", 11) == 0) {
3863                     gs_gamenum = gamenum;
3864                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3865                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3866                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3867 #if ZIPPY
3868                     if (appData.zippyPlay) {
3869                         ZippyGameStart(whitename, blackname);
3870                     }
3871 #endif /*ZIPPY*/
3872                     partnerBoardValid = FALSE; // [HGM] bughouse
3873                     continue;
3874                 }
3875
3876                 /* Game end messages */
3877                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3878                     ics_gamenum != gamenum) {
3879                     continue;
3880                 }
3881                 while (endtoken[0] == ' ') endtoken++;
3882                 switch (endtoken[0]) {
3883                   case '*':
3884                   default:
3885                     endtype = GameUnfinished;
3886                     break;
3887                   case '0':
3888                     endtype = BlackWins;
3889                     break;
3890                   case '1':
3891                     if (endtoken[1] == '/')
3892                       endtype = GameIsDrawn;
3893                     else
3894                       endtype = WhiteWins;
3895                     break;
3896                 }
3897                 GameEnds(endtype, why, GE_ICS);
3898 #if ZIPPY
3899                 if (appData.zippyPlay && first.initDone) {
3900                     ZippyGameEnd(endtype, why);
3901                     if (first.pr == NoProc) {
3902                       /* Start the next process early so that we'll
3903                          be ready for the next challenge */
3904                       StartChessProgram(&first);
3905                     }
3906                     /* Send "new" early, in case this command takes
3907                        a long time to finish, so that we'll be ready
3908                        for the next challenge. */
3909                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3910                     Reset(TRUE, TRUE);
3911                 }
3912 #endif /*ZIPPY*/
3913                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3914                 continue;
3915             }
3916
3917             if (looking_at(buf, &i, "Removing game * from observation") ||
3918                 looking_at(buf, &i, "no longer observing game *") ||
3919                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3920                 if (gameMode == IcsObserving &&
3921                     atoi(star_match[0]) == ics_gamenum)
3922                   {
3923                       /* icsEngineAnalyze */
3924                       if (appData.icsEngineAnalyze) {
3925                             ExitAnalyzeMode();
3926                             ModeHighlight();
3927                       }
3928                       StopClocks();
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "no longer examining game *")) {
3937                 if (gameMode == IcsExamining &&
3938                     atoi(star_match[0]) == ics_gamenum)
3939                   {
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             /* Advance leftover_start past any newlines we find,
3948                so only partial lines can get reparsed */
3949             if (looking_at(buf, &i, "\n")) {
3950                 prevColor = curColor;
3951                 if (curColor != ColorNormal) {
3952                     if (oldi > next_out) {
3953                         SendToPlayer(&buf[next_out], oldi - next_out);
3954                         next_out = oldi;
3955                     }
3956                     Colorize(ColorNormal, FALSE);
3957                     curColor = ColorNormal;
3958                 }
3959                 if (started == STARTED_BOARD) {
3960                     started = STARTED_NONE;
3961                     parse[parse_pos] = NULLCHAR;
3962                     ParseBoard12(parse);
3963                     ics_user_moved = 0;
3964
3965                     /* Send premove here */
3966                     if (appData.premove) {
3967                       char str[MSG_SIZ];
3968                       if (currentMove == 0 &&
3969                           gameMode == IcsPlayingWhite &&
3970                           appData.premoveWhite) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (currentMove == 1 &&
3976                                  gameMode == IcsPlayingBlack &&
3977                                  appData.premoveBlack) {
3978                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3979                         if (appData.debugMode)
3980                           fprintf(debugFP, "Sending premove:\n");
3981                         SendToICS(str);
3982                       } else if (gotPremove) {
3983                         gotPremove = 0;
3984                         ClearPremoveHighlights();
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                           UserMoveEvent(premoveFromX, premoveFromY,
3988                                         premoveToX, premoveToY,
3989                                         premovePromoChar);
3990                       }
3991                     }
3992
3993                     /* Usually suppress following prompt */
3994                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3995                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3996                         if (looking_at(buf, &i, "*% ")) {
3997                             savingComment = FALSE;
3998                             suppressKibitz = 0;
3999                         }
4000                     }
4001                     next_out = i;
4002                 } else if (started == STARTED_HOLDINGS) {
4003                     int gamenum;
4004                     char new_piece[MSG_SIZ];
4005                     started = STARTED_NONE;
4006                     parse[parse_pos] = NULLCHAR;
4007                     if (appData.debugMode)
4008                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4009                                                         parse, currentMove);
4010                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4011                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4012                         if (gameInfo.variant == VariantNormal) {
4013                           /* [HGM] We seem to switch variant during a game!
4014                            * Presumably no holdings were displayed, so we have
4015                            * to move the position two files to the right to
4016                            * create room for them!
4017                            */
4018                           VariantClass newVariant;
4019                           switch(gameInfo.boardWidth) { // base guess on board width
4020                                 case 9:  newVariant = VariantShogi; break;
4021                                 case 10: newVariant = VariantGreat; break;
4022                                 default: newVariant = VariantCrazyhouse; break;
4023                           }
4024                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4025                           /* Get a move list just to see the header, which
4026                              will tell us whether this is really bug or zh */
4027                           if (ics_getting_history == H_FALSE) {
4028                             ics_getting_history = H_REQUESTED;
4029                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4030                             SendToICS(str);
4031                           }
4032                         }
4033                         new_piece[0] = NULLCHAR;
4034                         sscanf(parse, "game %d white [%s black [%s <- %s",
4035                                &gamenum, white_holding, black_holding,
4036                                new_piece);
4037                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4038                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4039                         /* [HGM] copy holdings to board holdings area */
4040                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4041                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4042                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4043 #if ZIPPY
4044                         if (appData.zippyPlay && first.initDone) {
4045                             ZippyHoldings(white_holding, black_holding,
4046                                           new_piece);
4047                         }
4048 #endif /*ZIPPY*/
4049                         if (tinyLayout || smallLayout) {
4050                             char wh[16], bh[16];
4051                             PackHolding(wh, white_holding);
4052                             PackHolding(bh, black_holding);
4053                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4054                                     gameInfo.white, gameInfo.black);
4055                         } else {
4056                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4057                                     gameInfo.white, white_holding, _("vs."),
4058                                     gameInfo.black, black_holding);
4059                         }
4060                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4061                         DrawPosition(FALSE, boards[currentMove]);
4062                         DisplayTitle(str);
4063                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4064                         sscanf(parse, "game %d white [%s black [%s <- %s",
4065                                &gamenum, white_holding, black_holding,
4066                                new_piece);
4067                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4068                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4069                         /* [HGM] copy holdings to partner-board holdings area */
4070                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4071                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4072                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4073                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4074                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4075                       }
4076                     }
4077                     /* Suppress following prompt */
4078                     if (looking_at(buf, &i, "*% ")) {
4079                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4080                         savingComment = FALSE;
4081                         suppressKibitz = 0;
4082                     }
4083                     next_out = i;
4084                 }
4085                 continue;
4086             }
4087
4088             i++;                /* skip unparsed character and loop back */
4089         }
4090
4091         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4092 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4093 //          SendToPlayer(&buf[next_out], i - next_out);
4094             started != STARTED_HOLDINGS && leftover_start > next_out) {
4095             SendToPlayer(&buf[next_out], leftover_start - next_out);
4096             next_out = i;
4097         }
4098
4099         leftover_len = buf_len - leftover_start;
4100         /* if buffer ends with something we couldn't parse,
4101            reparse it after appending the next read */
4102
4103     } else if (count == 0) {
4104         RemoveInputSource(isr);
4105         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4106     } else {
4107         DisplayFatalError(_("Error reading from ICS"), error, 1);
4108     }
4109 }
4110
4111
4112 /* Board style 12 looks like this:
4113
4114    <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
4115
4116  * The "<12> " is stripped before it gets to this routine.  The two
4117  * trailing 0's (flip state and clock ticking) are later addition, and
4118  * some chess servers may not have them, or may have only the first.
4119  * Additional trailing fields may be added in the future.
4120  */
4121
4122 #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"
4123
4124 #define RELATION_OBSERVING_PLAYED    0
4125 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4126 #define RELATION_PLAYING_MYMOVE      1
4127 #define RELATION_PLAYING_NOTMYMOVE  -1
4128 #define RELATION_EXAMINING           2
4129 #define RELATION_ISOLATED_BOARD     -3
4130 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4131
4132 void
4133 ParseBoard12 (char *string)
4134 {
4135     GameMode newGameMode;
4136     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4137     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4138     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4139     char to_play, board_chars[200];
4140     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4141     char black[32], white[32];
4142     Board board;
4143     int prevMove = currentMove;
4144     int ticking = 2;
4145     ChessMove moveType;
4146     int fromX, fromY, toX, toY;
4147     char promoChar;
4148     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4149     char *bookHit = NULL; // [HGM] book
4150     Boolean weird = FALSE, reqFlag = FALSE;
4151
4152     fromX = fromY = toX = toY = -1;
4153
4154     newGame = FALSE;
4155
4156     if (appData.debugMode)
4157       fprintf(debugFP, _("Parsing board: %s\n"), string);
4158
4159     move_str[0] = NULLCHAR;
4160     elapsed_time[0] = NULLCHAR;
4161     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4162         int  i = 0, j;
4163         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4164             if(string[i] == ' ') { ranks++; files = 0; }
4165             else files++;
4166             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4167             i++;
4168         }
4169         for(j = 0; j <i; j++) board_chars[j] = string[j];
4170         board_chars[i] = '\0';
4171         string += i + 1;
4172     }
4173     n = sscanf(string, PATTERN, &to_play, &double_push,
4174                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4175                &gamenum, white, black, &relation, &basetime, &increment,
4176                &white_stren, &black_stren, &white_time, &black_time,
4177                &moveNum, str, elapsed_time, move_str, &ics_flip,
4178                &ticking);
4179
4180     if (n < 21) {
4181         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4182         DisplayError(str, 0);
4183         return;
4184     }
4185
4186     /* Convert the move number to internal form */
4187     moveNum = (moveNum - 1) * 2;
4188     if (to_play == 'B') moveNum++;
4189     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4190       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4191                         0, 1);
4192       return;
4193     }
4194
4195     switch (relation) {
4196       case RELATION_OBSERVING_PLAYED:
4197       case RELATION_OBSERVING_STATIC:
4198         if (gamenum == -1) {
4199             /* Old ICC buglet */
4200             relation = RELATION_OBSERVING_STATIC;
4201         }
4202         newGameMode = IcsObserving;
4203         break;
4204       case RELATION_PLAYING_MYMOVE:
4205       case RELATION_PLAYING_NOTMYMOVE:
4206         newGameMode =
4207           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4208             IcsPlayingWhite : IcsPlayingBlack;
4209         break;
4210       case RELATION_EXAMINING:
4211         newGameMode = IcsExamining;
4212         break;
4213       case RELATION_ISOLATED_BOARD:
4214       default:
4215         /* Just display this board.  If user was doing something else,
4216            we will forget about it until the next board comes. */
4217         newGameMode = IcsIdle;
4218         break;
4219       case RELATION_STARTING_POSITION:
4220         newGameMode = gameMode;
4221         break;
4222     }
4223
4224     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4225          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4226       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4227       char *toSqr;
4228       for (k = 0; k < ranks; k++) {
4229         for (j = 0; j < files; j++)
4230           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4231         if(gameInfo.holdingsWidth > 1) {
4232              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4233              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4234         }
4235       }
4236       CopyBoard(partnerBoard, board);
4237       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4238         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4239         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4240       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4241       if(toSqr = strchr(str, '-')) {
4242         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4243         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4244       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4245       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4246       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4247       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4249       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4250                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4251       DisplayMessage(partnerStatus, "");
4252         partnerBoardValid = TRUE;
4253       return;
4254     }
4255
4256     /* Modify behavior for initial board display on move listing
4257        of wild games.
4258        */
4259     switch (ics_getting_history) {
4260       case H_FALSE:
4261       case H_REQUESTED:
4262         break;
4263       case H_GOT_REQ_HEADER:
4264       case H_GOT_UNREQ_HEADER:
4265         /* This is the initial position of the current game */
4266         gamenum = ics_gamenum;
4267         moveNum = 0;            /* old ICS bug workaround */
4268         if (to_play == 'B') {
4269           startedFromSetupPosition = TRUE;
4270           blackPlaysFirst = TRUE;
4271           moveNum = 1;
4272           if (forwardMostMove == 0) forwardMostMove = 1;
4273           if (backwardMostMove == 0) backwardMostMove = 1;
4274           if (currentMove == 0) currentMove = 1;
4275         }
4276         newGameMode = gameMode;
4277         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4278         break;
4279       case H_GOT_UNWANTED_HEADER:
4280         /* This is an initial board that we don't want */
4281         return;
4282       case H_GETTING_MOVES:
4283         /* Should not happen */
4284         DisplayError(_("Error gathering move list: extra board"), 0);
4285         ics_getting_history = H_FALSE;
4286         return;
4287     }
4288
4289    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4290                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4291      /* [HGM] We seem to have switched variant unexpectedly
4292       * Try to guess new variant from board size
4293       */
4294           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4295           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4296           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4297           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4298           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4299           if(!weird) newVariant = VariantNormal;
4300           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4301           /* Get a move list just to see the header, which
4302              will tell us whether this is really bug or zh */
4303           if (ics_getting_history == H_FALSE) {
4304             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4305             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4306             SendToICS(str);
4307           }
4308     }
4309
4310     /* Take action if this is the first board of a new game, or of a
4311        different game than is currently being displayed.  */
4312     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4313         relation == RELATION_ISOLATED_BOARD) {
4314
4315         /* Forget the old game and get the history (if any) of the new one */
4316         if (gameMode != BeginningOfGame) {
4317           Reset(TRUE, TRUE);
4318         }
4319         newGame = TRUE;
4320         if (appData.autoRaiseBoard) BoardToTop();
4321         prevMove = -3;
4322         if (gamenum == -1) {
4323             newGameMode = IcsIdle;
4324         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4325                    appData.getMoveList && !reqFlag) {
4326             /* Need to get game history */
4327             ics_getting_history = H_REQUESTED;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330         }
4331
4332         /* Initially flip the board to have black on the bottom if playing
4333            black or if the ICS flip flag is set, but let the user change
4334            it with the Flip View button. */
4335         flipView = appData.autoFlipView ?
4336           (newGameMode == IcsPlayingBlack) || ics_flip :
4337           appData.flipView;
4338
4339         /* Done with values from previous mode; copy in new ones */
4340         gameMode = newGameMode;
4341         ModeHighlight();
4342         ics_gamenum = gamenum;
4343         if (gamenum == gs_gamenum) {
4344             int klen = strlen(gs_kind);
4345             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4346             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4347             gameInfo.event = StrSave(str);
4348         } else {
4349             gameInfo.event = StrSave("ICS game");
4350         }
4351         gameInfo.site = StrSave(appData.icsHost);
4352         gameInfo.date = PGNDate();
4353         gameInfo.round = StrSave("-");
4354         gameInfo.white = StrSave(white);
4355         gameInfo.black = StrSave(black);
4356         timeControl = basetime * 60 * 1000;
4357         timeControl_2 = 0;
4358         timeIncrement = increment * 1000;
4359         movesPerSession = 0;
4360         gameInfo.timeControl = TimeControlTagValue();
4361         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4362   if (appData.debugMode) {
4363     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4364     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4365     setbuf(debugFP, NULL);
4366   }
4367
4368         gameInfo.outOfBook = NULL;
4369
4370         /* Do we have the ratings? */
4371         if (strcmp(player1Name, white) == 0 &&
4372             strcmp(player2Name, black) == 0) {
4373             if (appData.debugMode)
4374               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4375                       player1Rating, player2Rating);
4376             gameInfo.whiteRating = player1Rating;
4377             gameInfo.blackRating = player2Rating;
4378         } else if (strcmp(player2Name, white) == 0 &&
4379                    strcmp(player1Name, black) == 0) {
4380             if (appData.debugMode)
4381               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4382                       player2Rating, player1Rating);
4383             gameInfo.whiteRating = player2Rating;
4384             gameInfo.blackRating = player1Rating;
4385         }
4386         player1Name[0] = player2Name[0] = NULLCHAR;
4387
4388         /* Silence shouts if requested */
4389         if (appData.quietPlay &&
4390             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4391             SendToICS(ics_prefix);
4392             SendToICS("set shout 0\n");
4393         }
4394     }
4395
4396     /* Deal with midgame name changes */
4397     if (!newGame) {
4398         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4399             if (gameInfo.white) free(gameInfo.white);
4400             gameInfo.white = StrSave(white);
4401         }
4402         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4403             if (gameInfo.black) free(gameInfo.black);
4404             gameInfo.black = StrSave(black);
4405         }
4406     }
4407
4408     /* Throw away game result if anything actually changes in examine mode */
4409     if (gameMode == IcsExamining && !newGame) {
4410         gameInfo.result = GameUnfinished;
4411         if (gameInfo.resultDetails != NULL) {
4412             free(gameInfo.resultDetails);
4413             gameInfo.resultDetails = NULL;
4414         }
4415     }
4416
4417     /* In pausing && IcsExamining mode, we ignore boards coming
4418        in if they are in a different variation than we are. */
4419     if (pauseExamInvalid) return;
4420     if (pausing && gameMode == IcsExamining) {
4421         if (moveNum <= pauseExamForwardMostMove) {
4422             pauseExamInvalid = TRUE;
4423             forwardMostMove = pauseExamForwardMostMove;
4424             return;
4425         }
4426     }
4427
4428   if (appData.debugMode) {
4429     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4430   }
4431     /* Parse the board */
4432     for (k = 0; k < ranks; k++) {
4433       for (j = 0; j < files; j++)
4434         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435       if(gameInfo.holdingsWidth > 1) {
4436            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438       }
4439     }
4440     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4441       board[5][BOARD_RGHT+1] = WhiteAngel;
4442       board[6][BOARD_RGHT+1] = WhiteMarshall;
4443       board[1][0] = BlackMarshall;
4444       board[2][0] = BlackAngel;
4445       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4446     }
4447     CopyBoard(boards[moveNum], board);
4448     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4449     if (moveNum == 0) {
4450         startedFromSetupPosition =
4451           !CompareBoards(board, initialPosition);
4452         if(startedFromSetupPosition)
4453             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4454     }
4455
4456     /* [HGM] Set castling rights. Take the outermost Rooks,
4457        to make it also work for FRC opening positions. Note that board12
4458        is really defective for later FRC positions, as it has no way to
4459        indicate which Rook can castle if they are on the same side of King.
4460        For the initial position we grant rights to the outermost Rooks,
4461        and remember thos rights, and we then copy them on positions
4462        later in an FRC game. This means WB might not recognize castlings with
4463        Rooks that have moved back to their original position as illegal,
4464        but in ICS mode that is not its job anyway.
4465     */
4466     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4467     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4468
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[0][i] == WhiteRook) j = i;
4471         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[0][i] == WhiteRook) j = i;
4474         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4476             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4477         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4478         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4479             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4480         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4481
4482         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4483         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4484         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4485             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4486         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4487             if(board[BOARD_HEIGHT-1][k] == bKing)
4488                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4489         if(gameInfo.variant == VariantTwoKings) {
4490             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4491             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4492             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4493         }
4494     } else { int r;
4495         r = boards[moveNum][CASTLING][0] = initialRights[0];
4496         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4497         r = boards[moveNum][CASTLING][1] = initialRights[1];
4498         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4499         r = boards[moveNum][CASTLING][3] = initialRights[3];
4500         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4501         r = boards[moveNum][CASTLING][4] = initialRights[4];
4502         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4503         /* wildcastle kludge: always assume King has rights */
4504         r = boards[moveNum][CASTLING][2] = initialRights[2];
4505         r = boards[moveNum][CASTLING][5] = initialRights[5];
4506     }
4507     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4508     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4509
4510
4511     if (ics_getting_history == H_GOT_REQ_HEADER ||
4512         ics_getting_history == H_GOT_UNREQ_HEADER) {
4513         /* This was an initial position from a move list, not
4514            the current position */
4515         return;
4516     }
4517
4518     /* Update currentMove and known move number limits */
4519     newMove = newGame || moveNum > forwardMostMove;
4520
4521     if (newGame) {
4522         forwardMostMove = backwardMostMove = currentMove = moveNum;
4523         if (gameMode == IcsExamining && moveNum == 0) {
4524           /* Workaround for ICS limitation: we are not told the wild
4525              type when starting to examine a game.  But if we ask for
4526              the move list, the move list header will tell us */
4527             ics_getting_history = H_REQUESTED;
4528             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4529             SendToICS(str);
4530         }
4531     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4532                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4533 #if ZIPPY
4534         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4535         /* [HGM] applied this also to an engine that is silently watching        */
4536         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4537             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4538             gameInfo.variant == currentlyInitializedVariant) {
4539           takeback = forwardMostMove - moveNum;
4540           for (i = 0; i < takeback; i++) {
4541             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4542             SendToProgram("undo\n", &first);
4543           }
4544         }
4545 #endif
4546
4547         forwardMostMove = moveNum;
4548         if (!pausing || currentMove > forwardMostMove)
4549           currentMove = forwardMostMove;
4550     } else {
4551         /* New part of history that is not contiguous with old part */
4552         if (pausing && gameMode == IcsExamining) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4558 #if ZIPPY
4559             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4560                 // [HGM] when we will receive the move list we now request, it will be
4561                 // fed to the engine from the first move on. So if the engine is not
4562                 // in the initial position now, bring it there.
4563                 InitChessProgram(&first, 0);
4564             }
4565 #endif
4566             ics_getting_history = H_REQUESTED;
4567             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4568             SendToICS(str);
4569         }
4570         forwardMostMove = backwardMostMove = currentMove = moveNum;
4571     }
4572
4573     /* Update the clocks */
4574     if (strchr(elapsed_time, '.')) {
4575       /* Time is in ms */
4576       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4577       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4578     } else {
4579       /* Time is in seconds */
4580       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4581       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4582     }
4583
4584
4585 #if ZIPPY
4586     if (appData.zippyPlay && newGame &&
4587         gameMode != IcsObserving && gameMode != IcsIdle &&
4588         gameMode != IcsExamining)
4589       ZippyFirstBoard(moveNum, basetime, increment);
4590 #endif
4591
4592     /* Put the move on the move list, first converting
4593        to canonical algebraic form. */
4594     if (moveNum > 0) {
4595   if (appData.debugMode) {
4596     if (appData.debugMode) { int f = forwardMostMove;
4597         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4598                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4599                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4600     }
4601     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4602     fprintf(debugFP, "moveNum = %d\n", moveNum);
4603     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4604     setbuf(debugFP, NULL);
4605   }
4606         if (moveNum <= backwardMostMove) {
4607             /* We don't know what the board looked like before
4608                this move.  Punt. */
4609           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4610             strcat(parseList[moveNum - 1], " ");
4611             strcat(parseList[moveNum - 1], elapsed_time);
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613         } else if (strcmp(move_str, "none") == 0) {
4614             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4615             /* Again, we don't know what the board looked like;
4616                this is really the start of the game. */
4617             parseList[moveNum - 1][0] = NULLCHAR;
4618             moveList[moveNum - 1][0] = NULLCHAR;
4619             backwardMostMove = moveNum;
4620             startedFromSetupPosition = TRUE;
4621             fromX = fromY = toX = toY = -1;
4622         } else {
4623           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4624           //                 So we parse the long-algebraic move string in stead of the SAN move
4625           int valid; char buf[MSG_SIZ], *prom;
4626
4627           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4628                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4629           // str looks something like "Q/a1-a2"; kill the slash
4630           if(str[1] == '/')
4631             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4632           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4633           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4634                 strcat(buf, prom); // long move lacks promo specification!
4635           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4636                 if(appData.debugMode)
4637                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4638                 safeStrCpy(move_str, buf, MSG_SIZ);
4639           }
4640           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4641                                 &fromX, &fromY, &toX, &toY, &promoChar)
4642                || ParseOneMove(buf, moveNum - 1, &moveType,
4643                                 &fromX, &fromY, &toX, &toY, &promoChar);
4644           // end of long SAN patch
4645           if (valid) {
4646             (void) CoordsToAlgebraic(boards[moveNum - 1],
4647                                      PosFlags(moveNum - 1),
4648                                      fromY, fromX, toY, toX, promoChar,
4649                                      parseList[moveNum-1]);
4650             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4651               case MT_NONE:
4652               case MT_STALEMATE:
4653               default:
4654                 break;
4655               case MT_CHECK:
4656                 if(gameInfo.variant != VariantShogi)
4657                     strcat(parseList[moveNum - 1], "+");
4658                 break;
4659               case MT_CHECKMATE:
4660               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4661                 strcat(parseList[moveNum - 1], "#");
4662                 break;
4663             }
4664             strcat(parseList[moveNum - 1], " ");
4665             strcat(parseList[moveNum - 1], elapsed_time);
4666             /* currentMoveString is set as a side-effect of ParseOneMove */
4667             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4668             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4669             strcat(moveList[moveNum - 1], "\n");
4670
4671             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4672                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4673               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4674                 ChessSquare old, new = boards[moveNum][k][j];
4675                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4676                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4677                   if(old == new) continue;
4678                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4679                   else if(new == WhiteWazir || new == BlackWazir) {
4680                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4681                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4682                       else boards[moveNum][k][j] = old; // preserve type of Gold
4683                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4684                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4685               }
4686           } else {
4687             /* Move from ICS was illegal!?  Punt. */
4688             if (appData.debugMode) {
4689               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4690               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4691             }
4692             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4693             strcat(parseList[moveNum - 1], " ");
4694             strcat(parseList[moveNum - 1], elapsed_time);
4695             moveList[moveNum - 1][0] = NULLCHAR;
4696             fromX = fromY = toX = toY = -1;
4697           }
4698         }
4699   if (appData.debugMode) {
4700     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4701     setbuf(debugFP, NULL);
4702   }
4703
4704 #if ZIPPY
4705         /* Send move to chess program (BEFORE animating it). */
4706         if (appData.zippyPlay && !newGame && newMove &&
4707            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4708
4709             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4710                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4711                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4712                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4713                             move_str);
4714                     DisplayError(str, 0);
4715                 } else {
4716                     if (first.sendTime) {
4717                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4718                     }
4719                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4720                     if (firstMove && !bookHit) {
4721                         firstMove = FALSE;
4722                         if (first.useColors) {
4723                           SendToProgram(gameMode == IcsPlayingWhite ?
4724                                         "white\ngo\n" :
4725                                         "black\ngo\n", &first);
4726                         } else {
4727                           SendToProgram("go\n", &first);
4728                         }
4729                         first.maybeThinking = TRUE;
4730                     }
4731                 }
4732             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4733               if (moveList[moveNum - 1][0] == NULLCHAR) {
4734                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4735                 DisplayError(str, 0);
4736               } else {
4737                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4738                 SendMoveToProgram(moveNum - 1, &first);
4739               }
4740             }
4741         }
4742 #endif
4743     }
4744
4745     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4746         /* If move comes from a remote source, animate it.  If it
4747            isn't remote, it will have already been animated. */
4748         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4749             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4750         }
4751         if (!pausing && appData.highlightLastMove) {
4752             SetHighlights(fromX, fromY, toX, toY);
4753         }
4754     }
4755
4756     /* Start the clocks */
4757     whiteFlag = blackFlag = FALSE;
4758     appData.clockMode = !(basetime == 0 && increment == 0);
4759     if (ticking == 0) {
4760       ics_clock_paused = TRUE;
4761       StopClocks();
4762     } else if (ticking == 1) {
4763       ics_clock_paused = FALSE;
4764     }
4765     if (gameMode == IcsIdle ||
4766         relation == RELATION_OBSERVING_STATIC ||
4767         relation == RELATION_EXAMINING ||
4768         ics_clock_paused)
4769       DisplayBothClocks();
4770     else
4771       StartClocks();
4772
4773     /* Display opponents and material strengths */
4774     if (gameInfo.variant != VariantBughouse &&
4775         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4776         if (tinyLayout || smallLayout) {
4777             if(gameInfo.variant == VariantNormal)
4778               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment);
4781             else
4782               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4783                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4784                     basetime, increment, (int) gameInfo.variant);
4785         } else {
4786             if(gameInfo.variant == VariantNormal)
4787               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4788                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4789                     basetime, increment);
4790             else
4791               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4792                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4793                     basetime, increment, VariantName(gameInfo.variant));
4794         }
4795         DisplayTitle(str);
4796   if (appData.debugMode) {
4797     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4798   }
4799     }
4800
4801
4802     /* Display the board */
4803     if (!pausing && !appData.noGUI) {
4804
4805       if (appData.premove)
4806           if (!gotPremove ||
4807              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4808              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4809               ClearPremoveHighlights();
4810
4811       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4812         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4813       DrawPosition(j, boards[currentMove]);
4814
4815       DisplayMove(moveNum - 1);
4816       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4817             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4818               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4819         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4820       }
4821     }
4822
4823     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4824 #if ZIPPY
4825     if(bookHit) { // [HGM] book: simulate book reply
4826         static char bookMove[MSG_SIZ]; // a bit generous?
4827
4828         programStats.nodes = programStats.depth = programStats.time =
4829         programStats.score = programStats.got_only_move = 0;
4830         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4831
4832         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4833         strcat(bookMove, bookHit);
4834         HandleMachineMove(bookMove, &first);
4835     }
4836 #endif
4837 }
4838
4839 void
4840 GetMoveListEvent ()
4841 {
4842     char buf[MSG_SIZ];
4843     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4844         ics_getting_history = H_REQUESTED;
4845         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4846         SendToICS(buf);
4847     }
4848 }
4849
4850 void
4851 AnalysisPeriodicEvent (int force)
4852 {
4853     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4854          && !force) || !appData.periodicUpdates)
4855       return;
4856
4857     /* Send . command to Crafty to collect stats */
4858     SendToProgram(".\n", &first);
4859
4860     /* Don't send another until we get a response (this makes
4861        us stop sending to old Crafty's which don't understand
4862        the "." command (sending illegal cmds resets node count & time,
4863        which looks bad)) */
4864     programStats.ok_to_send = 0;
4865 }
4866
4867 void
4868 ics_update_width (int new_width)
4869 {
4870         ics_printf("set width %d\n", new_width);
4871 }
4872
4873 void
4874 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4875 {
4876     char buf[MSG_SIZ];
4877
4878     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4879         // null move in variant where engine does not understand it (for analysis purposes)
4880         SendBoard(cps, moveNum + 1); // send position after move in stead.
4881         return;
4882     }
4883     if (cps->useUsermove) {
4884       SendToProgram("usermove ", cps);
4885     }
4886     if (cps->useSAN) {
4887       char *space;
4888       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4889         int len = space - parseList[moveNum];
4890         memcpy(buf, parseList[moveNum], len);
4891         buf[len++] = '\n';
4892         buf[len] = NULLCHAR;
4893       } else {
4894         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4895       }
4896       SendToProgram(buf, cps);
4897     } else {
4898       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4899         AlphaRank(moveList[moveNum], 4);
4900         SendToProgram(moveList[moveNum], cps);
4901         AlphaRank(moveList[moveNum], 4); // and back
4902       } else
4903       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4904        * the engine. It would be nice to have a better way to identify castle
4905        * moves here. */
4906       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4907                                                                          && cps->useOOCastle) {
4908         int fromX = moveList[moveNum][0] - AAA;
4909         int fromY = moveList[moveNum][1] - ONE;
4910         int toX = moveList[moveNum][2] - AAA;
4911         int toY = moveList[moveNum][3] - ONE;
4912         if((boards[moveNum][fromY][fromX] == WhiteKing
4913             && boards[moveNum][toY][toX] == WhiteRook)
4914            || (boards[moveNum][fromY][fromX] == BlackKing
4915                && boards[moveNum][toY][toX] == BlackRook)) {
4916           if(toX > fromX) SendToProgram("O-O\n", cps);
4917           else SendToProgram("O-O-O\n", cps);
4918         }
4919         else SendToProgram(moveList[moveNum], cps);
4920       } else
4921       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4922         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4923           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4924           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4925                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         } else
4927           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4928                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4929         SendToProgram(buf, cps);
4930       }
4931       else SendToProgram(moveList[moveNum], cps);
4932       /* End of additions by Tord */
4933     }
4934
4935     /* [HGM] setting up the opening has brought engine in force mode! */
4936     /*       Send 'go' if we are in a mode where machine should play. */
4937     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4938         (gameMode == TwoMachinesPlay   ||
4939 #if ZIPPY
4940          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4941 #endif
4942          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4943         SendToProgram("go\n", cps);
4944   if (appData.debugMode) {
4945     fprintf(debugFP, "(extra)\n");
4946   }
4947     }
4948     setboardSpoiledMachineBlack = 0;
4949 }
4950
4951 void
4952 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4953 {
4954     char user_move[MSG_SIZ];
4955     char suffix[4];
4956
4957     if(gameInfo.variant == VariantSChess && promoChar) {
4958         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4959         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4960     } else suffix[0] = NULLCHAR;
4961
4962     switch (moveType) {
4963       default:
4964         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4965                 (int)moveType, fromX, fromY, toX, toY);
4966         DisplayError(user_move + strlen("say "), 0);
4967         break;
4968       case WhiteKingSideCastle:
4969       case BlackKingSideCastle:
4970       case WhiteQueenSideCastleWild:
4971       case BlackQueenSideCastleWild:
4972       /* PUSH Fabien */
4973       case WhiteHSideCastleFR:
4974       case BlackHSideCastleFR:
4975       /* POP Fabien */
4976         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4977         break;
4978       case WhiteQueenSideCastle:
4979       case BlackQueenSideCastle:
4980       case WhiteKingSideCastleWild:
4981       case BlackKingSideCastleWild:
4982       /* PUSH Fabien */
4983       case WhiteASideCastleFR:
4984       case BlackASideCastleFR:
4985       /* POP Fabien */
4986         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4987         break;
4988       case WhiteNonPromotion:
4989       case BlackNonPromotion:
4990         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4991         break;
4992       case WhitePromotion:
4993       case BlackPromotion:
4994         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 PieceToChar(WhiteFerz));
4998         else if(gameInfo.variant == VariantGreat)
4999           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5000                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001                 PieceToChar(WhiteMan));
5002         else
5003           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005                 promoChar);
5006         break;
5007       case WhiteDrop:
5008       case BlackDrop:
5009       drop:
5010         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5011                  ToUpper(PieceToChar((ChessSquare) fromX)),
5012                  AAA + toX, ONE + toY);
5013         break;
5014       case IllegalMove:  /* could be a variant we don't quite understand */
5015         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5016       case NormalMove:
5017       case WhiteCapturesEnPassant:
5018       case BlackCapturesEnPassant:
5019         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5021         break;
5022     }
5023     SendToICS(user_move);
5024     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5025         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5026 }
5027
5028 void
5029 UploadGameEvent ()
5030 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5031     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5032     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5033     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5034       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5035       return;
5036     }
5037     if(gameMode != IcsExamining) { // is this ever not the case?
5038         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5039
5040         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5041           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5042         } else { // on FICS we must first go to general examine mode
5043           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5044         }
5045         if(gameInfo.variant != VariantNormal) {
5046             // try figure out wild number, as xboard names are not always valid on ICS
5047             for(i=1; i<=36; i++) {
5048               snprintf(buf, MSG_SIZ, "wild/%d", i);
5049                 if(StringToVariant(buf) == gameInfo.variant) break;
5050             }
5051             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5052             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5053             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5054         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5055         SendToICS(ics_prefix);
5056         SendToICS(buf);
5057         if(startedFromSetupPosition || backwardMostMove != 0) {
5058           fen = PositionToFEN(backwardMostMove, NULL);
5059           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5060             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5061             SendToICS(buf);
5062           } else { // FICS: everything has to set by separate bsetup commands
5063             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5064             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5065             SendToICS(buf);
5066             if(!WhiteOnMove(backwardMostMove)) {
5067                 SendToICS("bsetup tomove black\n");
5068             }
5069             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5070             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5071             SendToICS(buf);
5072             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5073             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5074             SendToICS(buf);
5075             i = boards[backwardMostMove][EP_STATUS];
5076             if(i >= 0) { // set e.p.
5077               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5078                 SendToICS(buf);
5079             }
5080             bsetup++;
5081           }
5082         }
5083       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5084             SendToICS("bsetup done\n"); // switch to normal examining.
5085     }
5086     for(i = backwardMostMove; i<last; i++) {
5087         char buf[20];
5088         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5089         SendToICS(buf);
5090     }
5091     SendToICS(ics_prefix);
5092     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5093 }
5094
5095 void
5096 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5097 {
5098     if (rf == DROP_RANK) {
5099       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5100       sprintf(move, "%c@%c%c\n",
5101                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5102     } else {
5103         if (promoChar == 'x' || promoChar == NULLCHAR) {
5104           sprintf(move, "%c%c%c%c\n",
5105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5106         } else {
5107             sprintf(move, "%c%c%c%c%c\n",
5108                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5109         }
5110     }
5111 }
5112
5113 void
5114 ProcessICSInitScript (FILE *f)
5115 {
5116     char buf[MSG_SIZ];
5117
5118     while (fgets(buf, MSG_SIZ, f)) {
5119         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5120     }
5121
5122     fclose(f);
5123 }
5124
5125
5126 static int lastX, lastY, selectFlag, dragging;
5127
5128 void
5129 Sweep (int step)
5130 {
5131     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5132     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5133     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5134     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5135     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5136     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5137     do {
5138         promoSweep -= step;
5139         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5140         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5141         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5142         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5143         if(!step) step = -1;
5144     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5145             appData.testLegality && (promoSweep == king ||
5146             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5147     ChangeDragPiece(promoSweep);
5148 }
5149
5150 int
5151 PromoScroll (int x, int y)
5152 {
5153   int step = 0;
5154
5155   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5156   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5157   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5158   if(!step) return FALSE;
5159   lastX = x; lastY = y;
5160   if((promoSweep < BlackPawn) == flipView) step = -step;
5161   if(step > 0) selectFlag = 1;
5162   if(!selectFlag) Sweep(step);
5163   return FALSE;
5164 }
5165
5166 void
5167 NextPiece (int step)
5168 {
5169     ChessSquare piece = boards[currentMove][toY][toX];
5170     do {
5171         pieceSweep -= step;
5172         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5173         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5174         if(!step) step = -1;
5175     } while(PieceToChar(pieceSweep) == '.');
5176     boards[currentMove][toY][toX] = pieceSweep;
5177     DrawPosition(FALSE, boards[currentMove]);
5178     boards[currentMove][toY][toX] = piece;
5179 }
5180 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5181 void
5182 AlphaRank (char *move, int n)
5183 {
5184 //    char *p = move, c; int x, y;
5185
5186     if (appData.debugMode) {
5187         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5188     }
5189
5190     if(move[1]=='*' &&
5191        move[2]>='0' && move[2]<='9' &&
5192        move[3]>='a' && move[3]<='x'    ) {
5193         move[1] = '@';
5194         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5195         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5196     } else
5197     if(move[0]>='0' && move[0]<='9' &&
5198        move[1]>='a' && move[1]<='x' &&
5199        move[2]>='0' && move[2]<='9' &&
5200        move[3]>='a' && move[3]<='x'    ) {
5201         /* input move, Shogi -> normal */
5202         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5203         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5204         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5205         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5206     } else
5207     if(move[1]=='@' &&
5208        move[3]>='0' && move[3]<='9' &&
5209        move[2]>='a' && move[2]<='x'    ) {
5210         move[1] = '*';
5211         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5212         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5213     } else
5214     if(
5215        move[0]>='a' && move[0]<='x' &&
5216        move[3]>='0' && move[3]<='9' &&
5217        move[2]>='a' && move[2]<='x'    ) {
5218          /* output move, normal -> Shogi */
5219         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5220         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5221         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5222         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5223         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5224     }
5225     if (appData.debugMode) {
5226         fprintf(debugFP, "   out = '%s'\n", move);
5227     }
5228 }
5229
5230 char yy_textstr[8000];
5231
5232 /* Parser for moves from gnuchess, ICS, or user typein box */
5233 Boolean
5234 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV (ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN (char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV (int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV ()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV (int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void
5513 GetPositionNumber ()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int
5526 put (Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void
5545 AddOnePiece (Board board, int pieceType, int rank, int shade)
5546 // calculate where the next piece goes, (any empty square), and put it there
5547 {
5548         int i;
5549
5550         i = seed % squaresLeft[shade];
5551         nrOfShuffles *= squaresLeft[shade];
5552         seed /= squaresLeft[shade];
5553         put(board, pieceType, rank, i, shade);
5554 }
5555
5556 void
5557 AddTwoPieces (Board board, int pieceType, int rank)
5558 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5559 {
5560         int i, n=squaresLeft[ANY], j=n-1, k;
5561
5562         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5563         i = seed % k;  // pick one
5564         nrOfShuffles *= k;
5565         seed /= k;
5566         while(i >= j) i -= j--;
5567         j = n - 1 - j; i += j;
5568         put(board, pieceType, rank, j, ANY);
5569         put(board, pieceType, rank, i, ANY);
5570 }
5571
5572 void
5573 SetUpShuffle (Board board, int number)
5574 {
5575         int i, p, first=1;
5576
5577         GetPositionNumber(); nrOfShuffles = 1;
5578
5579         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5580         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5581         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5582
5583         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5584
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5586             p = (int) board[0][i];
5587             if(p < (int) BlackPawn) piecesLeft[p] ++;
5588             board[0][i] = EmptySquare;
5589         }
5590
5591         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5592             // shuffles restricted to allow normal castling put KRR first
5593             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5594                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5596                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5598                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5600                 put(board, WhiteRook, 0, 0, ANY);
5601             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5602         }
5603
5604         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5605             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5606             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5607                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5608                 while(piecesLeft[p] >= 2) {
5609                     AddOnePiece(board, p, 0, LITE);
5610                     AddOnePiece(board, p, 0, DARK);
5611                 }
5612                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5613             }
5614
5615         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5616             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5617             // but we leave King and Rooks for last, to possibly obey FRC restriction
5618             if(p == (int)WhiteRook) continue;
5619             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5620             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5621         }
5622
5623         // now everything is placed, except perhaps King (Unicorn) and Rooks
5624
5625         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5626             // Last King gets castling rights
5627             while(piecesLeft[(int)WhiteUnicorn]) {
5628                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632             while(piecesLeft[(int)WhiteKing]) {
5633                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637
5638         } else {
5639             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5640             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5641         }
5642
5643         // Only Rooks can be left; simply place them all
5644         while(piecesLeft[(int)WhiteRook]) {
5645                 i = put(board, WhiteRook, 0, 0, ANY);
5646                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5647                         if(first) {
5648                                 first=0;
5649                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5650                         }
5651                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5652                 }
5653         }
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5655             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5656         }
5657
5658         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5659 }
5660
5661 int
5662 SetCharTable (char *table, const char * map)
5663 /* [HGM] moved here from winboard.c because of its general usefulness */
5664 /*       Basically a safe strcpy that uses the last character as King */
5665 {
5666     int result = FALSE; int NrPieces;
5667
5668     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5669                     && NrPieces >= 12 && !(NrPieces&1)) {
5670         int i; /* [HGM] Accept even length from 12 to 34 */
5671
5672         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5673         for( i=0; i<NrPieces/2-1; i++ ) {
5674             table[i] = map[i];
5675             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5676         }
5677         table[(int) WhiteKing]  = map[NrPieces/2-1];
5678         table[(int) BlackKing]  = map[NrPieces-1];
5679
5680         result = TRUE;
5681     }
5682
5683     return result;
5684 }
5685
5686 void
5687 Prelude (Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition (int redraw)
5725 {
5726     ChessSquare (* pieces)[BOARD_FILES];
5727     int i, j, pawnRow, overrule,
5728     oldx = gameInfo.boardWidth,
5729     oldy = gameInfo.boardHeight,
5730     oldh = gameInfo.holdingsWidth;
5731     static int oldv;
5732
5733     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5734
5735     /* [AS] Initialize pv info list [HGM] and game status */
5736     {
5737         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5738             pvInfoList[i].depth = 0;
5739             boards[i][EP_STATUS] = EP_NONE;
5740             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5741         }
5742
5743         initialRulePlies = 0; /* 50-move counter start */
5744
5745         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5746         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5747     }
5748
5749
5750     /* [HGM] logic here is completely changed. In stead of full positions */
5751     /* the initialized data only consist of the two backranks. The switch */
5752     /* selects which one we will use, which is than copied to the Board   */
5753     /* initialPosition, which for the rest is initialized by Pawns and    */
5754     /* empty squares. This initial position is then copied to boards[0],  */
5755     /* possibly after shuffling, so that it remains available.            */
5756
5757     gameInfo.holdingsWidth = 0; /* default board sizes */
5758     gameInfo.boardWidth    = 8;
5759     gameInfo.boardHeight   = 8;
5760     gameInfo.holdingsSize  = 0;
5761     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5762     for(i=0; i<BOARD_FILES-2; i++)
5763       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5764     initialPosition[EP_STATUS] = EP_NONE;
5765     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5766     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5767          SetCharTable(pieceNickName, appData.pieceNickNames);
5768     else SetCharTable(pieceNickName, "............");
5769     pieces = FIDEArray;
5770
5771     switch (gameInfo.variant) {
5772     case VariantFischeRandom:
5773       shuffleOpenings = TRUE;
5774     default:
5775       break;
5776     case VariantShatranj:
5777       pieces = ShatranjArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5780       break;
5781     case VariantMakruk:
5782       pieces = makrukArray;
5783       nrCastlingRights = 0;
5784       startedFromSetupPosition = TRUE;
5785       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5786       break;
5787     case VariantTwoKings:
5788       pieces = twoKingsArray;
5789       break;
5790     case VariantGrand:
5791       pieces = GrandArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       gameInfo.boardWidth = 10;
5795       gameInfo.boardHeight = 10;
5796       gameInfo.holdingsSize = 7;
5797       break;
5798     case VariantCapaRandom:
5799       shuffleOpenings = TRUE;
5800     case VariantCapablanca:
5801       pieces = CapablancaArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantGothic:
5806       pieces = GothicArray;
5807       gameInfo.boardWidth = 10;
5808       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5809       break;
5810     case VariantSChess:
5811       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5812       gameInfo.holdingsSize = 7;
5813       break;
5814     case VariantJanus:
5815       pieces = JanusArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5818       nrCastlingRights = 6;
5819         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5822         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5825       break;
5826     case VariantFalcon:
5827       pieces = FalconArray;
5828       gameInfo.boardWidth = 10;
5829       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5830       break;
5831     case VariantXiangqi:
5832       pieces = XiangqiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 10;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5837       break;
5838     case VariantShogi:
5839       pieces = ShogiArray;
5840       gameInfo.boardWidth  = 9;
5841       gameInfo.boardHeight = 9;
5842       gameInfo.holdingsSize = 7;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5845       break;
5846     case VariantCourier:
5847       pieces = CourierArray;
5848       gameInfo.boardWidth  = 12;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5851       break;
5852     case VariantKnightmate:
5853       pieces = KnightmateArray;
5854       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5855       break;
5856     case VariantSpartan:
5857       pieces = SpartanArray;
5858       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5859       break;
5860     case VariantFairy:
5861       pieces = fairyArray;
5862       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5863       break;
5864     case VariantGreat:
5865       pieces = GreatArray;
5866       gameInfo.boardWidth = 10;
5867       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5868       gameInfo.holdingsSize = 8;
5869       break;
5870     case VariantSuper:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5873       gameInfo.holdingsSize = 8;
5874       startedFromSetupPosition = TRUE;
5875       break;
5876     case VariantCrazyhouse:
5877     case VariantBughouse:
5878       pieces = FIDEArray;
5879       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5880       gameInfo.holdingsSize = 5;
5881       break;
5882     case VariantWildCastle:
5883       pieces = FIDEArray;
5884       /* !!?shuffle with kings guaranteed to be on d or e file */
5885       shuffleOpenings = 1;
5886       break;
5887     case VariantNoCastle:
5888       pieces = FIDEArray;
5889       nrCastlingRights = 0;
5890       /* !!?unconstrained back-rank shuffle */
5891       shuffleOpenings = 1;
5892       break;
5893     }
5894
5895     overrule = 0;
5896     if(appData.NrFiles >= 0) {
5897         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5898         gameInfo.boardWidth = appData.NrFiles;
5899     }
5900     if(appData.NrRanks >= 0) {
5901         gameInfo.boardHeight = appData.NrRanks;
5902     }
5903     if(appData.holdingsSize >= 0) {
5904         i = appData.holdingsSize;
5905         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5906         gameInfo.holdingsSize = i;
5907     }
5908     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5909     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5910         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5911
5912     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5913     if(pawnRow < 1) pawnRow = 1;
5914     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5915
5916     /* User pieceToChar list overrules defaults */
5917     if(appData.pieceToCharTable != NULL)
5918         SetCharTable(pieceToChar, appData.pieceToCharTable);
5919
5920     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5921
5922         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5923             s = (ChessSquare) 0; /* account holding counts in guard band */
5924         for( i=0; i<BOARD_HEIGHT; i++ )
5925             initialPosition[i][j] = s;
5926
5927         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5928         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5929         initialPosition[pawnRow][j] = WhitePawn;
5930         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5931         if(gameInfo.variant == VariantXiangqi) {
5932             if(j&1) {
5933                 initialPosition[pawnRow][j] =
5934                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5935                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5936                    initialPosition[2][j] = WhiteCannon;
5937                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5938                 }
5939             }
5940         }
5941         if(gameInfo.variant == VariantGrand) {
5942             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5943                initialPosition[0][j] = WhiteRook;
5944                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5945             }
5946         }
5947         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5948     }
5949     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5950
5951             j=BOARD_LEFT+1;
5952             initialPosition[1][j] = WhiteBishop;
5953             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5954             j=BOARD_RGHT-2;
5955             initialPosition[1][j] = WhiteRook;
5956             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5957     }
5958
5959     if( nrCastlingRights == -1) {
5960         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5961         /*       This sets default castling rights from none to normal corners   */
5962         /* Variants with other castling rights must set them themselves above    */
5963         nrCastlingRights = 6;
5964
5965         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5966         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5967         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5968         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5969         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5970         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5971      }
5972
5973      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5974      if(gameInfo.variant == VariantGreat) { // promotion commoners
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5979      }
5980      if( gameInfo.variant == VariantSChess ) {
5981       initialPosition[1][0] = BlackMarshall;
5982       initialPosition[2][0] = BlackAngel;
5983       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5984       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5985       initialPosition[1][1] = initialPosition[2][1] = 
5986       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5987      }
5988   if (appData.debugMode) {
5989     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5990   }
5991     if(shuffleOpenings) {
5992         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5993         startedFromSetupPosition = TRUE;
5994     }
5995     if(startedFromPositionFile) {
5996       /* [HGM] loadPos: use PositionFile for every new game */
5997       CopyBoard(initialPosition, filePosition);
5998       for(i=0; i<nrCastlingRights; i++)
5999           initialRights[i] = filePosition[CASTLING][i];
6000       startedFromSetupPosition = TRUE;
6001     }
6002
6003     CopyBoard(boards[0], initialPosition);
6004
6005     if(oldx != gameInfo.boardWidth ||
6006        oldy != gameInfo.boardHeight ||
6007        oldv != gameInfo.variant ||
6008        oldh != gameInfo.holdingsWidth
6009                                          )
6010             InitDrawingSizes(-2 ,0);
6011
6012     oldv = gameInfo.variant;
6013     if (redraw)
6014       DrawPosition(TRUE, boards[currentMove]);
6015 }
6016
6017 void
6018 SendBoard (ChessProgramState *cps, int moveNum)
6019 {
6020     char message[MSG_SIZ];
6021
6022     if (cps->useSetboard) {
6023       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6024       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6025       SendToProgram(message, cps);
6026       free(fen);
6027
6028     } else {
6029       ChessSquare *bp;
6030       int i, j, left=0, right=BOARD_WIDTH;
6031       /* Kludge to set black to move, avoiding the troublesome and now
6032        * deprecated "black" command.
6033        */
6034       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6035         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6036
6037       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6038
6039       SendToProgram("edit\n", cps);
6040       SendToProgram("#\n", cps);
6041       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6042         bp = &boards[moveNum][i][left];
6043         for (j = left; j < right; j++, bp++) {
6044           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6045           if ((int) *bp < (int) BlackPawn) {
6046             if(j == BOARD_RGHT+1)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6048             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6049             if(message[0] == '+' || message[0] == '~') {
6050               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6052                         AAA + j, ONE + i);
6053             }
6054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6055                 message[1] = BOARD_RGHT   - 1 - j + '1';
6056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6057             }
6058             SendToProgram(message, cps);
6059           }
6060         }
6061       }
6062
6063       SendToProgram("c\n", cps);
6064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6065         bp = &boards[moveNum][i][left];
6066         for (j = left; j < right; j++, bp++) {
6067           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6068           if (((int) *bp != (int) EmptySquare)
6069               && ((int) *bp >= (int) BlackPawn)) {
6070             if(j == BOARD_LEFT-2)
6071                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6072             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6073                     AAA + j, ONE + i);
6074             if(message[0] == '+' || message[0] == '~') {
6075               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6076                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6077                         AAA + j, ONE + i);
6078             }
6079             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6080                 message[1] = BOARD_RGHT   - 1 - j + '1';
6081                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082             }
6083             SendToProgram(message, cps);
6084           }
6085         }
6086       }
6087
6088       SendToProgram(".\n", cps);
6089     }
6090     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6091 }
6092
6093 ChessSquare
6094 DefaultPromoChoice (int white)
6095 {
6096     ChessSquare result;
6097     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6098         result = WhiteFerz; // no choice
6099     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6100         result= WhiteKing; // in Suicide Q is the last thing we want
6101     else if(gameInfo.variant == VariantSpartan)
6102         result = white ? WhiteQueen : WhiteAngel;
6103     else result = WhiteQueen;
6104     if(!white) result = WHITE_TO_BLACK result;
6105     return result;
6106 }
6107
6108 static int autoQueen; // [HGM] oneclick
6109
6110 int
6111 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6112 {
6113     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6114     /* [HGM] add Shogi promotions */
6115     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6116     ChessSquare piece;
6117     ChessMove moveType;
6118     Boolean premove;
6119
6120     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6121     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6122
6123     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6124       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6125         return FALSE;
6126
6127     piece = boards[currentMove][fromY][fromX];
6128     if(gameInfo.variant == VariantShogi) {
6129         promotionZoneSize = BOARD_HEIGHT/3;
6130         highestPromotingPiece = (int)WhiteFerz;
6131     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6132         promotionZoneSize = 3;
6133     }
6134
6135     // Treat Lance as Pawn when it is not representing Amazon
6136     if(gameInfo.variant != VariantSuper) {
6137         if(piece == WhiteLance) piece = WhitePawn; else
6138         if(piece == BlackLance) piece = BlackPawn;
6139     }
6140
6141     // next weed out all moves that do not touch the promotion zone at all
6142     if((int)piece >= BlackPawn) {
6143         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6144              return FALSE;
6145         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6146     } else {
6147         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6148            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6149     }
6150
6151     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6152
6153     // weed out mandatory Shogi promotions
6154     if(gameInfo.variant == VariantShogi) {
6155         if(piece >= BlackPawn) {
6156             if(toY == 0 && piece == BlackPawn ||
6157                toY == 0 && piece == BlackQueen ||
6158                toY <= 1 && piece == BlackKnight) {
6159                 *promoChoice = '+';
6160                 return FALSE;
6161             }
6162         } else {
6163             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6164                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6165                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6166                 *promoChoice = '+';
6167                 return FALSE;
6168             }
6169         }
6170     }
6171
6172     // weed out obviously illegal Pawn moves
6173     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6174         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6175         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6176         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6177         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6178         // note we are not allowed to test for valid (non-)capture, due to premove
6179     }
6180
6181     // we either have a choice what to promote to, or (in Shogi) whether to promote
6182     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6183         *promoChoice = PieceToChar(BlackFerz);  // no choice
6184         return FALSE;
6185     }
6186     // no sense asking what we must promote to if it is going to explode...
6187     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6188         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6189         return FALSE;
6190     }
6191     // give caller the default choice even if we will not make it
6192     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6193     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6194     if(        sweepSelect && gameInfo.variant != VariantGreat
6195                            && gameInfo.variant != VariantGrand
6196                            && gameInfo.variant != VariantSuper) return FALSE;
6197     if(autoQueen) return FALSE; // predetermined
6198
6199     // suppress promotion popup on illegal moves that are not premoves
6200     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6201               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6202     if(appData.testLegality && !premove) {
6203         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6204                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6205         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6206             return FALSE;
6207     }
6208
6209     return TRUE;
6210 }
6211
6212 int
6213 InPalace (int row, int column)
6214 {   /* [HGM] for Xiangqi */
6215     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6216          column < (BOARD_WIDTH + 4)/2 &&
6217          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6218     return FALSE;
6219 }
6220
6221 int
6222 PieceForSquare (int x, int y)
6223 {
6224   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6225      return -1;
6226   else
6227      return boards[currentMove][y][x];
6228 }
6229
6230 int
6231 OKToStartUserMove (int x, int y)
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove (int *x, int *y, Boolean captures) 
6332 {
6333     DisambiguateClosure cl;
6334     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6335     switch(gameMode) {
6336       case MachinePlaysBlack:
6337       case IcsPlayingWhite:
6338       case BeginningOfGame:
6339         if(!WhiteOnMove(currentMove)) return FALSE;
6340         break;
6341       case MachinePlaysWhite:
6342       case IcsPlayingBlack:
6343         if(WhiteOnMove(currentMove)) return FALSE;
6344         break;
6345       case EditGame:
6346         break;
6347       default:
6348         return FALSE;
6349     }
6350     cl.pieceIn = EmptySquare;
6351     cl.rfIn = *y;
6352     cl.ffIn = *x;
6353     cl.rtIn = -1;
6354     cl.ftIn = -1;
6355     cl.promoCharIn = NULLCHAR;
6356     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6357     if( cl.kind == NormalMove ||
6358         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6359         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6360         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6361       fromX = cl.ff;
6362       fromY = cl.rf;
6363       *x = cl.ft;
6364       *y = cl.rt;
6365       return TRUE;
6366     }
6367     if(cl.kind != ImpossibleMove) return FALSE;
6368     cl.pieceIn = EmptySquare;
6369     cl.rfIn = -1;
6370     cl.ffIn = -1;
6371     cl.rtIn = *y;
6372     cl.ftIn = *x;
6373     cl.promoCharIn = NULLCHAR;
6374     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6375     if( cl.kind == NormalMove ||
6376         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6377         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6378         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6379       fromX = cl.ff;
6380       fromY = cl.rf;
6381       *x = cl.ft;
6382       *y = cl.rt;
6383       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384       return TRUE;
6385     }
6386     return FALSE;
6387 }
6388
6389 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6390 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6391 int lastLoadGameUseList = FALSE;
6392 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6393 ChessMove lastLoadGameStart = EndOfFile;
6394
6395 void
6396 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6397 {
6398     ChessMove moveType;
6399     ChessSquare pdown, pup;
6400
6401     /* Check if the user is playing in turn.  This is complicated because we
6402        let the user "pick up" a piece before it is his turn.  So the piece he
6403        tried to pick up may have been captured by the time he puts it down!
6404        Therefore we use the color the user is supposed to be playing in this
6405        test, not the color of the piece that is currently on the starting
6406        square---except in EditGame mode, where the user is playing both
6407        sides; fortunately there the capture race can't happen.  (It can
6408        now happen in IcsExamining mode, but that's just too bad.  The user
6409        will get a somewhat confusing message in that case.)
6410        */
6411
6412     switch (gameMode) {
6413       case AnalyzeFile:
6414       case TwoMachinesPlay:
6415       case EndOfGame:
6416       case IcsObserving:
6417       case IcsIdle:
6418         /* We switched into a game mode where moves are not accepted,
6419            perhaps while the mouse button was down. */
6420         return;
6421
6422       case MachinePlaysWhite:
6423         /* User is moving for Black */
6424         if (WhiteOnMove(currentMove)) {
6425             DisplayMoveError(_("It is White's turn"));
6426             return;
6427         }
6428         break;
6429
6430       case MachinePlaysBlack:
6431         /* User is moving for White */
6432         if (!WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is Black's turn"));
6434             return;
6435         }
6436         break;
6437
6438       case PlayFromGameFile:
6439             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6440       case EditGame:
6441       case IcsExamining:
6442       case BeginningOfGame:
6443       case AnalyzeMode:
6444       case Training:
6445         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6446         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6447             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6448             /* User is moving for Black */
6449             if (WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is White's turn"));
6451                 return;
6452             }
6453         } else {
6454             /* User is moving for White */
6455             if (!WhiteOnMove(currentMove)) {
6456                 DisplayMoveError(_("It is Black's turn"));
6457                 return;
6458             }
6459         }
6460         break;
6461
6462       case IcsPlayingBlack:
6463         /* User is moving for Black */
6464         if (WhiteOnMove(currentMove)) {
6465             if (!appData.premove) {
6466                 DisplayMoveError(_("It is White's turn"));
6467             } else if (toX >= 0 && toY >= 0) {
6468                 premoveToX = toX;
6469                 premoveToY = toY;
6470                 premoveFromX = fromX;
6471                 premoveFromY = fromY;
6472                 premovePromoChar = promoChar;
6473                 gotPremove = 1;
6474                 if (appData.debugMode)
6475                     fprintf(debugFP, "Got premove: fromX %d,"
6476                             "fromY %d, toX %d, toY %d\n",
6477                             fromX, fromY, toX, toY);
6478             }
6479             return;
6480         }
6481         break;
6482
6483       case IcsPlayingWhite:
6484         /* User is moving for White */
6485         if (!WhiteOnMove(currentMove)) {
6486             if (!appData.premove) {
6487                 DisplayMoveError(_("It is Black's turn"));
6488             } else if (toX >= 0 && toY >= 0) {
6489                 premoveToX = toX;
6490                 premoveToY = toY;
6491                 premoveFromX = fromX;
6492                 premoveFromY = fromY;
6493                 premovePromoChar = promoChar;
6494                 gotPremove = 1;
6495                 if (appData.debugMode)
6496                     fprintf(debugFP, "Got premove: fromX %d,"
6497                             "fromY %d, toX %d, toY %d\n",
6498                             fromX, fromY, toX, toY);
6499             }
6500             return;
6501         }
6502         break;
6503
6504       default:
6505         break;
6506
6507       case EditPosition:
6508         /* EditPosition, empty square, or different color piece;
6509            click-click move is possible */
6510         if (toX == -2 || toY == -2) {
6511             boards[0][fromY][fromX] = EmptySquare;
6512             DrawPosition(FALSE, boards[currentMove]);
6513             return;
6514         } else if (toX >= 0 && toY >= 0) {
6515             boards[0][toY][toX] = boards[0][fromY][fromX];
6516             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6517                 if(boards[0][fromY][0] != EmptySquare) {
6518                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6519                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6520                 }
6521             } else
6522             if(fromX == BOARD_RGHT+1) {
6523                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6524                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6525                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6526                 }
6527             } else
6528             boards[0][fromY][fromX] = EmptySquare;
6529             DrawPosition(FALSE, boards[currentMove]);
6530             return;
6531         }
6532         return;
6533     }
6534
6535     if(toX < 0 || toY < 0) return;
6536     pdown = boards[currentMove][fromY][fromX];
6537     pup = boards[currentMove][toY][toX];
6538
6539     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6540     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6541          if( pup != EmptySquare ) return;
6542          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6543            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6544                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6545            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6546            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6547            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6548            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6549          fromY = DROP_RANK;
6550     }
6551
6552     /* [HGM] always test for legality, to get promotion info */
6553     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6554                                          fromY, fromX, toY, toX, promoChar);
6555
6556     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6557
6558     /* [HGM] but possibly ignore an IllegalMove result */
6559     if (appData.testLegality) {
6560         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6561             DisplayMoveError(_("Illegal move"));
6562             return;
6563         }
6564     }
6565
6566     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6567 }
6568
6569 /* Common tail of UserMoveEvent and DropMenuEvent */
6570 int
6571 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6572 {
6573     char *bookHit = 0;
6574
6575     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6576         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6577         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6578         if(WhiteOnMove(currentMove)) {
6579             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6580         } else {
6581             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6582         }
6583     }
6584
6585     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6586        move type in caller when we know the move is a legal promotion */
6587     if(moveType == NormalMove && promoChar)
6588         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6589
6590     /* [HGM] <popupFix> The following if has been moved here from
6591        UserMoveEvent(). Because it seemed to belong here (why not allow
6592        piece drops in training games?), and because it can only be
6593        performed after it is known to what we promote. */
6594     if (gameMode == Training) {
6595       /* compare the move played on the board to the next move in the
6596        * game. If they match, display the move and the opponent's response.
6597        * If they don't match, display an error message.
6598        */
6599       int saveAnimate;
6600       Board testBoard;
6601       CopyBoard(testBoard, boards[currentMove]);
6602       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6603
6604       if (CompareBoards(testBoard, boards[currentMove+1])) {
6605         ForwardInner(currentMove+1);
6606
6607         /* Autoplay the opponent's response.
6608          * if appData.animate was TRUE when Training mode was entered,
6609          * the response will be animated.
6610          */
6611         saveAnimate = appData.animate;
6612         appData.animate = animateTraining;
6613         ForwardInner(currentMove+1);
6614         appData.animate = saveAnimate;
6615
6616         /* check for the end of the game */
6617         if (currentMove >= forwardMostMove) {
6618           gameMode = PlayFromGameFile;
6619           ModeHighlight();
6620           SetTrainingModeOff();
6621           DisplayInformation(_("End of game"));
6622         }
6623       } else {
6624         DisplayError(_("Incorrect move"), 0);
6625       }
6626       return 1;
6627     }
6628
6629   /* Ok, now we know that the move is good, so we can kill
6630      the previous line in Analysis Mode */
6631   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6632                                 && currentMove < forwardMostMove) {
6633     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6634     else forwardMostMove = currentMove;
6635   }
6636
6637   /* If we need the chess program but it's dead, restart it */
6638   ResurrectChessProgram();
6639
6640   /* A user move restarts a paused game*/
6641   if (pausing)
6642     PauseEvent();
6643
6644   thinkOutput[0] = NULLCHAR;
6645
6646   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6647
6648   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6649     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650     return 1;
6651   }
6652
6653   if (gameMode == BeginningOfGame) {
6654     if (appData.noChessProgram) {
6655       gameMode = EditGame;
6656       SetGameInfo();
6657     } else {
6658       char buf[MSG_SIZ];
6659       gameMode = MachinePlaysBlack;
6660       StartClocks();
6661       SetGameInfo();
6662       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6663       DisplayTitle(buf);
6664       if (first.sendName) {
6665         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6666         SendToProgram(buf, &first);
6667       }
6668       StartClocks();
6669     }
6670     ModeHighlight();
6671   }
6672
6673   /* Relay move to ICS or chess engine */
6674   if (appData.icsActive) {
6675     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6676         gameMode == IcsExamining) {
6677       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6678         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6679         SendToICS("draw ");
6680         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       }
6682       // also send plain move, in case ICS does not understand atomic claims
6683       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6684       ics_user_moved = 1;
6685     }
6686   } else {
6687     if (first.sendTime && (gameMode == BeginningOfGame ||
6688                            gameMode == MachinePlaysWhite ||
6689                            gameMode == MachinePlaysBlack)) {
6690       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6691     }
6692     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6693          // [HGM] book: if program might be playing, let it use book
6694         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6695         first.maybeThinking = TRUE;
6696     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6697         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6698         SendBoard(&first, currentMove+1);
6699     } else SendMoveToProgram(forwardMostMove-1, &first);
6700     if (currentMove == cmailOldMove + 1) {
6701       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6702     }
6703   }
6704
6705   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706
6707   switch (gameMode) {
6708   case EditGame:
6709     if(appData.testLegality)
6710     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6711     case MT_NONE:
6712     case MT_CHECK:
6713       break;
6714     case MT_CHECKMATE:
6715     case MT_STAINMATE:
6716       if (WhiteOnMove(currentMove)) {
6717         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6718       } else {
6719         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6720       }
6721       break;
6722     case MT_STALEMATE:
6723       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6724       break;
6725     }
6726     break;
6727
6728   case MachinePlaysBlack:
6729   case MachinePlaysWhite:
6730     /* disable certain menu options while machine is thinking */
6731     SetMachineThinkingEnables();
6732     break;
6733
6734   default:
6735     break;
6736   }
6737
6738   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6739   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6740
6741   if(bookHit) { // [HGM] book: simulate book reply
6742         static char bookMove[MSG_SIZ]; // a bit generous?
6743
6744         programStats.nodes = programStats.depth = programStats.time =
6745         programStats.score = programStats.got_only_move = 0;
6746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6747
6748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6749         strcat(bookMove, bookHit);
6750         HandleMachineMove(bookMove, &first);
6751   }
6752   return 1;
6753 }
6754
6755 void
6756 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6757 {
6758     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6759     Markers *m = (Markers *) closure;
6760     if(rf == fromY && ff == fromX)
6761         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6762                          || kind == WhiteCapturesEnPassant
6763                          || kind == BlackCapturesEnPassant);
6764     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6765 }
6766
6767 void
6768 MarkTargetSquares (int clear)
6769 {
6770   int x, y;
6771   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6772      !appData.testLegality || gameMode == EditPosition) return;
6773   if(clear) {
6774     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6775   } else {
6776     int capt = 0;
6777     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6778     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6780       if(capt)
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6782     }
6783   }
6784   DrawPosition(TRUE, NULL);
6785 }
6786
6787 int
6788 Explode (Board board, int fromX, int fromY, int toX, int toY)
6789 {
6790     if(gameInfo.variant == VariantAtomic &&
6791        (board[toY][toX] != EmptySquare ||                     // capture?
6792         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6793                          board[fromY][fromX] == BlackPawn   )
6794       )) {
6795         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6796         return TRUE;
6797     }
6798     return FALSE;
6799 }
6800
6801 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6802
6803 int
6804 CanPromote (ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void
6819 LeftClick (ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int
7118 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void
7209 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     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
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane (int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings (int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 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
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights (Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate (ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566         if (appData.debugMode) { int i;
7567             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7568                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7569                     appData.drawRepeats);
7570             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7571               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7572
7573         }
7574
7575         // Repetition draws and 50-move rule can be applied independently of legality testing
7576
7577                 /* Check for rep-draws */
7578                 count = 0;
7579                 for(k = forwardMostMove-2;
7580                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7581                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7582                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7583                     k-=2)
7584                 {   int rights=0;
7585                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7586                         /* compare castling rights */
7587                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7588                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7589                                 rights++; /* King lost rights, while rook still had them */
7590                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7591                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7592                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7593                                    rights++; /* but at least one rook lost them */
7594                         }
7595                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7596                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7597                                 rights++;
7598                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7599                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7600                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7601                                    rights++;
7602                         }
7603                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7604                             && appData.drawRepeats > 1) {
7605                              /* adjudicate after user-specified nr of repeats */
7606                              int result = GameIsDrawn;
7607                              char *details = "XBoard adjudication: repetition draw";
7608                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7609                                 // [HGM] xiangqi: check for forbidden perpetuals
7610                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7611                                 for(m=forwardMostMove; m>k; m-=2) {
7612                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7613                                         ourPerpetual = 0; // the current mover did not always check
7614                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7615                                         hisPerpetual = 0; // the opponent did not always check
7616                                 }
7617                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7618                                                                         ourPerpetual, hisPerpetual);
7619                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7620                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                     details = "Xboard adjudication: perpetual checking";
7622                                 } else
7623                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7624                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7625                                 } else
7626                                 // Now check for perpetual chases
7627                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7628                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7629                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7630                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7631                                         static char resdet[MSG_SIZ];
7632                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7633                                         details = resdet;
7634                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7635                                     } else
7636                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7637                                         break; // Abort repetition-checking loop.
7638                                 }
7639                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7640                              }
7641                              if(engineOpponent) {
7642                                SendToProgram("force\n", engineOpponent); // suppress reply
7643                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7644                              }
7645                              GameEnds( result, details, GE_XBOARD );
7646                              return 1;
7647                         }
7648                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7649                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7650                     }
7651                 }
7652
7653                 /* Now we test for 50-move draws. Determine ply count */
7654                 count = forwardMostMove;
7655                 /* look for last irreversble move */
7656                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7657                     count--;
7658                 /* if we hit starting position, add initial plies */
7659                 if( count == backwardMostMove )
7660                     count -= initialRulePlies;
7661                 count = forwardMostMove - count;
7662                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7663                         // adjust reversible move counter for checks in Xiangqi
7664                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7665                         if(i < backwardMostMove) i = backwardMostMove;
7666                         while(i <= forwardMostMove) {
7667                                 lastCheck = inCheck; // check evasion does not count
7668                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7669                                 if(inCheck || lastCheck) count--; // check does not count
7670                                 i++;
7671                         }
7672                 }
7673                 if( count >= 100)
7674                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7675                          /* this is used to judge if draw claims are legal */
7676                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7677                          if(engineOpponent) {
7678                            SendToProgram("force\n", engineOpponent); // suppress reply
7679                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680                          }
7681                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7682                          return 1;
7683                 }
7684
7685                 /* if draw offer is pending, treat it as a draw claim
7686                  * when draw condition present, to allow engines a way to
7687                  * claim draws before making their move to avoid a race
7688                  * condition occurring after their move
7689                  */
7690                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7691                          char *p = NULL;
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7693                              p = "Draw claim: 50-move rule";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7695                              p = "Draw claim: 3-fold repetition";
7696                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7697                              p = "Draw claim: insufficient mating material";
7698                          if( p != NULL && canAdjudicate) {
7699                              if(engineOpponent) {
7700                                SendToProgram("force\n", engineOpponent); // suppress reply
7701                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702                              }
7703                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7704                              return 1;
7705                          }
7706                 }
7707
7708                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7709                     if(engineOpponent) {
7710                       SendToProgram("force\n", engineOpponent); // suppress reply
7711                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7712                     }
7713                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7714                     return 1;
7715                 }
7716         return 0;
7717 }
7718
7719 char *
7720 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7721 {   // [HGM] book: this routine intercepts moves to simulate book replies
7722     char *bookHit = NULL;
7723
7724     //first determine if the incoming move brings opponent into his book
7725     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7726         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7727     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7728     if(bookHit != NULL && !cps->bookSuspend) {
7729         // make sure opponent is not going to reply after receiving move to book position
7730         SendToProgram("force\n", cps);
7731         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7732     }
7733     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7734     // now arrange restart after book miss
7735     if(bookHit) {
7736         // after a book hit we never send 'go', and the code after the call to this routine
7737         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7738         char buf[MSG_SIZ], *move = bookHit;
7739         if(cps->useSAN) {
7740             int fromX, fromY, toX, toY;
7741             char promoChar;
7742             ChessMove moveType;
7743             move = buf + 30;
7744             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7745                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7746                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7747                                     PosFlags(forwardMostMove),
7748                                     fromY, fromX, toY, toX, promoChar, move);
7749             } else {
7750                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7751                 bookHit = NULL;
7752             }
7753         }
7754         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7755         SendToProgram(buf, cps);
7756         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7757     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7758         SendToProgram("go\n", cps);
7759         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7760     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7761         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7762             SendToProgram("go\n", cps);
7763         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7764     }
7765     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7766 }
7767
7768 char *savedMessage;
7769 ChessProgramState *savedState;
7770 void
7771 DeferredBookMove (void)
7772 {
7773         if(savedState->lastPing != savedState->lastPong)
7774                     ScheduleDelayedEvent(DeferredBookMove, 10);
7775         else
7776         HandleMachineMove(savedMessage, savedState);
7777 }
7778
7779 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7780
7781 void
7782 HandleMachineMove (char *message, ChessProgramState *cps)
7783 {
7784     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7785     char realname[MSG_SIZ];
7786     int fromX, fromY, toX, toY;
7787     ChessMove moveType;
7788     char promoChar;
7789     char *p, *pv=buf1;
7790     int machineWhite;
7791     char *bookHit;
7792
7793     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7794         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7795         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7796             DisplayError(_("Invalid pairing from pairing engine"), 0);
7797             return;
7798         }
7799         pairingReceived = 1;
7800         NextMatchGame();
7801         return; // Skim the pairing messages here.
7802     }
7803
7804     cps->userError = 0;
7805
7806 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7807     /*
7808      * Kludge to ignore BEL characters
7809      */
7810     while (*message == '\007') message++;
7811
7812     /*
7813      * [HGM] engine debug message: ignore lines starting with '#' character
7814      */
7815     if(cps->debug && *message == '#') return;
7816
7817     /*
7818      * Look for book output
7819      */
7820     if (cps == &first && bookRequested) {
7821         if (message[0] == '\t' || message[0] == ' ') {
7822             /* Part of the book output is here; append it */
7823             strcat(bookOutput, message);
7824             strcat(bookOutput, "  \n");
7825             return;
7826         } else if (bookOutput[0] != NULLCHAR) {
7827             /* All of book output has arrived; display it */
7828             char *p = bookOutput;
7829             while (*p != NULLCHAR) {
7830                 if (*p == '\t') *p = ' ';
7831                 p++;
7832             }
7833             DisplayInformation(bookOutput);
7834             bookRequested = FALSE;
7835             /* Fall through to parse the current output */
7836         }
7837     }
7838
7839     /*
7840      * Look for machine move.
7841      */
7842     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7843         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7844     {
7845         /* This method is only useful on engines that support ping */
7846         if (cps->lastPing != cps->lastPong) {
7847           if (gameMode == BeginningOfGame) {
7848             /* Extra move from before last new; ignore */
7849             if (appData.debugMode) {
7850                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7851             }
7852           } else {
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7855                         cps->which, gameMode);
7856             }
7857
7858             SendToProgram("undo\n", cps);
7859           }
7860           return;
7861         }
7862
7863         switch (gameMode) {
7864           case BeginningOfGame:
7865             /* Extra move from before last reset; ignore */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7868             }
7869             return;
7870
7871           case EndOfGame:
7872           case IcsIdle:
7873           default:
7874             /* Extra move after we tried to stop.  The mode test is
7875                not a reliable way of detecting this problem, but it's
7876                the best we can do on engines that don't support ping.
7877             */
7878             if (appData.debugMode) {
7879                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7880                         cps->which, gameMode);
7881             }
7882             SendToProgram("undo\n", cps);
7883             return;
7884
7885           case MachinePlaysWhite:
7886           case IcsPlayingWhite:
7887             machineWhite = TRUE;
7888             break;
7889
7890           case MachinePlaysBlack:
7891           case IcsPlayingBlack:
7892             machineWhite = FALSE;
7893             break;
7894
7895           case TwoMachinesPlay:
7896             machineWhite = (cps->twoMachinesColor[0] == 'w');
7897             break;
7898         }
7899         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7900             if (appData.debugMode) {
7901                 fprintf(debugFP,
7902                         "Ignoring move out of turn by %s, gameMode %d"
7903                         ", forwardMost %d\n",
7904                         cps->which, gameMode, forwardMostMove);
7905             }
7906             return;
7907         }
7908
7909     if (appData.debugMode) { int f = forwardMostMove;
7910         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7911                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7912                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7913     }
7914         if(cps->alphaRank) AlphaRank(machineMove, 4);
7915         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7916                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7917             /* Machine move could not be parsed; ignore it. */
7918           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7919                     machineMove, _(cps->which));
7920             DisplayError(buf1, 0);
7921             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7922                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7923             if (gameMode == TwoMachinesPlay) {
7924               GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                        buf1, GE_XBOARD);
7926             }
7927             return;
7928         }
7929
7930         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7931         /* So we have to redo legality test with true e.p. status here,  */
7932         /* to make sure an illegal e.p. capture does not slip through,   */
7933         /* to cause a forfeit on a justified illegal-move complaint      */
7934         /* of the opponent.                                              */
7935         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7936            ChessMove moveType;
7937            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7938                              fromY, fromX, toY, toX, promoChar);
7939             if (appData.debugMode) {
7940                 int i;
7941                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7942                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7943                 fprintf(debugFP, "castling rights\n");
7944             }
7945             if(moveType == IllegalMove) {
7946               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7947                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7948                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7949                            buf1, GE_XBOARD);
7950                 return;
7951            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7952            /* [HGM] Kludge to handle engines that send FRC-style castling
7953               when they shouldn't (like TSCP-Gothic) */
7954            switch(moveType) {
7955              case WhiteASideCastleFR:
7956              case BlackASideCastleFR:
7957                toX+=2;
7958                currentMoveString[2]++;
7959                break;
7960              case WhiteHSideCastleFR:
7961              case BlackHSideCastleFR:
7962                toX--;
7963                currentMoveString[2]--;
7964                break;
7965              default: ; // nothing to do, but suppresses warning of pedantic compilers
7966            }
7967         }
7968         hintRequested = FALSE;
7969         lastHint[0] = NULLCHAR;
7970         bookRequested = FALSE;
7971         /* Program may be pondering now */
7972         cps->maybeThinking = TRUE;
7973         if (cps->sendTime == 2) cps->sendTime = 1;
7974         if (cps->offeredDraw) cps->offeredDraw--;
7975
7976         /* [AS] Save move info*/
7977         pvInfoList[ forwardMostMove ].score = programStats.score;
7978         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7979         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7980
7981         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7982
7983         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7984         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7985             int count = 0;
7986
7987             while( count < adjudicateLossPlies ) {
7988                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7989
7990                 if( count & 1 ) {
7991                     score = -score; /* Flip score for winning side */
7992                 }
7993
7994                 if( score > adjudicateLossThreshold ) {
7995                     break;
7996                 }
7997
7998                 count++;
7999             }
8000
8001             if( count >= adjudicateLossPlies ) {
8002                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8003
8004                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8005                     "Xboard adjudication",
8006                     GE_XBOARD );
8007
8008                 return;
8009             }
8010         }
8011
8012         if(Adjudicate(cps)) {
8013             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8014             return; // [HGM] adjudicate: for all automatic game ends
8015         }
8016
8017 #if ZIPPY
8018         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8019             first.initDone) {
8020           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8021                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8022                 SendToICS("draw ");
8023                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           }
8025           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8026           ics_user_moved = 1;
8027           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8028                 char buf[3*MSG_SIZ];
8029
8030                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8031                         programStats.score / 100.,
8032                         programStats.depth,
8033                         programStats.time / 100.,
8034                         (unsigned int)programStats.nodes,
8035                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8036                         programStats.movelist);
8037                 SendToICS(buf);
8038 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8039           }
8040         }
8041 #endif
8042
8043         /* [AS] Clear stats for next move */
8044         ClearProgramStats();
8045         thinkOutput[0] = NULLCHAR;
8046         hiddenThinkOutputState = 0;
8047
8048         bookHit = NULL;
8049         if (gameMode == TwoMachinesPlay) {
8050             /* [HGM] relaying draw offers moved to after reception of move */
8051             /* and interpreting offer as claim if it brings draw condition */
8052             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8053                 SendToProgram("draw\n", cps->other);
8054             }
8055             if (cps->other->sendTime) {
8056                 SendTimeRemaining(cps->other,
8057                                   cps->other->twoMachinesColor[0] == 'w');
8058             }
8059             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8060             if (firstMove && !bookHit) {
8061                 firstMove = FALSE;
8062                 if (cps->other->useColors) {
8063                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8064                 }
8065                 SendToProgram("go\n", cps->other);
8066             }
8067             cps->other->maybeThinking = TRUE;
8068         }
8069
8070         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8071
8072         if (!pausing && appData.ringBellAfterMoves) {
8073             RingBell();
8074         }
8075
8076         /*
8077          * Reenable menu items that were disabled while
8078          * machine was thinking
8079          */
8080         if (gameMode != TwoMachinesPlay)
8081             SetUserThinkingEnables();
8082
8083         // [HGM] book: after book hit opponent has received move and is now in force mode
8084         // force the book reply into it, and then fake that it outputted this move by jumping
8085         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8086         if(bookHit) {
8087                 static char bookMove[MSG_SIZ]; // a bit generous?
8088
8089                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8090                 strcat(bookMove, bookHit);
8091                 message = bookMove;
8092                 cps = cps->other;
8093                 programStats.nodes = programStats.depth = programStats.time =
8094                 programStats.score = programStats.got_only_move = 0;
8095                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8096
8097                 if(cps->lastPing != cps->lastPong) {
8098                     savedMessage = message; // args for deferred call
8099                     savedState = cps;
8100                     ScheduleDelayedEvent(DeferredBookMove, 10);
8101                     return;
8102                 }
8103                 goto FakeBookMove;
8104         }
8105
8106         return;
8107     }
8108
8109     /* Set special modes for chess engines.  Later something general
8110      *  could be added here; for now there is just one kludge feature,
8111      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8112      *  when "xboard" is given as an interactive command.
8113      */
8114     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8115         cps->useSigint = FALSE;
8116         cps->useSigterm = FALSE;
8117     }
8118     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8119       ParseFeatures(message+8, cps);
8120       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8121     }
8122
8123     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8124                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8125       int dummy, s=6; char buf[MSG_SIZ];
8126       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8127       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8128       if(startedFromSetupPosition) return;
8129       ParseFEN(boards[0], &dummy, message+s);
8130       DrawPosition(TRUE, boards[0]);
8131       startedFromSetupPosition = TRUE;
8132       return;
8133     }
8134     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8135      * want this, I was asked to put it in, and obliged.
8136      */
8137     if (!strncmp(message, "setboard ", 9)) {
8138         Board initial_position;
8139
8140         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8141
8142         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8143             DisplayError(_("Bad FEN received from engine"), 0);
8144             return ;
8145         } else {
8146            Reset(TRUE, FALSE);
8147            CopyBoard(boards[0], initial_position);
8148            initialRulePlies = FENrulePlies;
8149            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8150            else gameMode = MachinePlaysBlack;
8151            DrawPosition(FALSE, boards[currentMove]);
8152         }
8153         return;
8154     }
8155
8156     /*
8157      * Look for communication commands
8158      */
8159     if (!strncmp(message, "telluser ", 9)) {
8160         if(message[9] == '\\' && message[10] == '\\')
8161             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8162         PlayTellSound();
8163         DisplayNote(message + 9);
8164         return;
8165     }
8166     if (!strncmp(message, "tellusererror ", 14)) {
8167         cps->userError = 1;
8168         if(message[14] == '\\' && message[15] == '\\')
8169             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8170         PlayTellSound();
8171         DisplayError(message + 14, 0);
8172         return;
8173     }
8174     if (!strncmp(message, "tellopponent ", 13)) {
8175       if (appData.icsActive) {
8176         if (loggedOn) {
8177           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8178           SendToICS(buf1);
8179         }
8180       } else {
8181         DisplayNote(message + 13);
8182       }
8183       return;
8184     }
8185     if (!strncmp(message, "tellothers ", 11)) {
8186       if (appData.icsActive) {
8187         if (loggedOn) {
8188           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8189           SendToICS(buf1);
8190         }
8191       }
8192       return;
8193     }
8194     if (!strncmp(message, "tellall ", 8)) {
8195       if (appData.icsActive) {
8196         if (loggedOn) {
8197           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8198           SendToICS(buf1);
8199         }
8200       } else {
8201         DisplayNote(message + 8);
8202       }
8203       return;
8204     }
8205     if (strncmp(message, "warning", 7) == 0) {
8206         /* Undocumented feature, use tellusererror in new code */
8207         DisplayError(message, 0);
8208         return;
8209     }
8210     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8211         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8212         strcat(realname, " query");
8213         AskQuestion(realname, buf2, buf1, cps->pr);
8214         return;
8215     }
8216     /* Commands from the engine directly to ICS.  We don't allow these to be
8217      *  sent until we are logged on. Crafty kibitzes have been known to
8218      *  interfere with the login process.
8219      */
8220     if (loggedOn) {
8221         if (!strncmp(message, "tellics ", 8)) {
8222             SendToICS(message + 8);
8223             SendToICS("\n");
8224             return;
8225         }
8226         if (!strncmp(message, "tellicsnoalias ", 15)) {
8227             SendToICS(ics_prefix);
8228             SendToICS(message + 15);
8229             SendToICS("\n");
8230             return;
8231         }
8232         /* The following are for backward compatibility only */
8233         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8234             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8235             SendToICS(ics_prefix);
8236             SendToICS(message);
8237             SendToICS("\n");
8238             return;
8239         }
8240     }
8241     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8242         return;
8243     }
8244     /*
8245      * If the move is illegal, cancel it and redraw the board.
8246      * Also deal with other error cases.  Matching is rather loose
8247      * here to accommodate engines written before the spec.
8248      */
8249     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8250         strncmp(message, "Error", 5) == 0) {
8251         if (StrStr(message, "name") ||
8252             StrStr(message, "rating") || StrStr(message, "?") ||
8253             StrStr(message, "result") || StrStr(message, "board") ||
8254             StrStr(message, "bk") || StrStr(message, "computer") ||
8255             StrStr(message, "variant") || StrStr(message, "hint") ||
8256             StrStr(message, "random") || StrStr(message, "depth") ||
8257             StrStr(message, "accepted")) {
8258             return;
8259         }
8260         if (StrStr(message, "protover")) {
8261           /* Program is responding to input, so it's apparently done
8262              initializing, and this error message indicates it is
8263              protocol version 1.  So we don't need to wait any longer
8264              for it to initialize and send feature commands. */
8265           FeatureDone(cps, 1);
8266           cps->protocolVersion = 1;
8267           return;
8268         }
8269         cps->maybeThinking = FALSE;
8270
8271         if (StrStr(message, "draw")) {
8272             /* Program doesn't have "draw" command */
8273             cps->sendDrawOffers = 0;
8274             return;
8275         }
8276         if (cps->sendTime != 1 &&
8277             (StrStr(message, "time") || StrStr(message, "otim"))) {
8278           /* Program apparently doesn't have "time" or "otim" command */
8279           cps->sendTime = 0;
8280           return;
8281         }
8282         if (StrStr(message, "analyze")) {
8283             cps->analysisSupport = FALSE;
8284             cps->analyzing = FALSE;
8285 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8286             EditGameEvent(); // [HGM] try to preserve loaded game
8287             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8288             DisplayError(buf2, 0);
8289             return;
8290         }
8291         if (StrStr(message, "(no matching move)st")) {
8292           /* Special kludge for GNU Chess 4 only */
8293           cps->stKludge = TRUE;
8294           SendTimeControl(cps, movesPerSession, timeControl,
8295                           timeIncrement, appData.searchDepth,
8296                           searchTime);
8297           return;
8298         }
8299         if (StrStr(message, "(no matching move)sd")) {
8300           /* Special kludge for GNU Chess 4 only */
8301           cps->sdKludge = TRUE;
8302           SendTimeControl(cps, movesPerSession, timeControl,
8303                           timeIncrement, appData.searchDepth,
8304                           searchTime);
8305           return;
8306         }
8307         if (!StrStr(message, "llegal")) {
8308             return;
8309         }
8310         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8311             gameMode == IcsIdle) return;
8312         if (forwardMostMove <= backwardMostMove) return;
8313         if (pausing) PauseEvent();
8314       if(appData.forceIllegal) {
8315             // [HGM] illegal: machine refused move; force position after move into it
8316           SendToProgram("force\n", cps);
8317           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8318                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8319                 // when black is to move, while there might be nothing on a2 or black
8320                 // might already have the move. So send the board as if white has the move.
8321                 // But first we must change the stm of the engine, as it refused the last move
8322                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8323                 if(WhiteOnMove(forwardMostMove)) {
8324                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8325                     SendBoard(cps, forwardMostMove); // kludgeless board
8326                 } else {
8327                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8328                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8329                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8330                 }
8331           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8332             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8333                  gameMode == TwoMachinesPlay)
8334               SendToProgram("go\n", cps);
8335             return;
8336       } else
8337         if (gameMode == PlayFromGameFile) {
8338             /* Stop reading this game file */
8339             gameMode = EditGame;
8340             ModeHighlight();
8341         }
8342         /* [HGM] illegal-move claim should forfeit game when Xboard */
8343         /* only passes fully legal moves                            */
8344         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8345             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8346                                 "False illegal-move claim", GE_XBOARD );
8347             return; // do not take back move we tested as valid
8348         }
8349         currentMove = forwardMostMove-1;
8350         DisplayMove(currentMove-1); /* before DisplayMoveError */
8351         SwitchClocks(forwardMostMove-1); // [HGM] race
8352         DisplayBothClocks();
8353         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8354                 parseList[currentMove], _(cps->which));
8355         DisplayMoveError(buf1);
8356         DrawPosition(FALSE, boards[currentMove]);
8357
8358         SetUserThinkingEnables();
8359         return;
8360     }
8361     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8362         /* Program has a broken "time" command that
8363            outputs a string not ending in newline.
8364            Don't use it. */
8365         cps->sendTime = 0;
8366     }
8367
8368     /*
8369      * If chess program startup fails, exit with an error message.
8370      * Attempts to recover here are futile.
8371      */
8372     if ((StrStr(message, "unknown host") != NULL)
8373         || (StrStr(message, "No remote directory") != NULL)
8374         || (StrStr(message, "not found") != NULL)
8375         || (StrStr(message, "No such file") != NULL)
8376         || (StrStr(message, "can't alloc") != NULL)
8377         || (StrStr(message, "Permission denied") != NULL)) {
8378
8379         cps->maybeThinking = FALSE;
8380         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8381                 _(cps->which), cps->program, cps->host, message);
8382         RemoveInputSource(cps->isr);
8383         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8384             if(cps == &first) appData.noChessProgram = TRUE;
8385             DisplayError(buf1, 0);
8386         }
8387         return;
8388     }
8389
8390     /*
8391      * Look for hint output
8392      */
8393     if (sscanf(message, "Hint: %s", buf1) == 1) {
8394         if (cps == &first && hintRequested) {
8395             hintRequested = FALSE;
8396             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8397                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8398                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8399                                     PosFlags(forwardMostMove),
8400                                     fromY, fromX, toY, toX, promoChar, buf1);
8401                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8402                 DisplayInformation(buf2);
8403             } else {
8404                 /* Hint move could not be parsed!? */
8405               snprintf(buf2, sizeof(buf2),
8406                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8407                         buf1, _(cps->which));
8408                 DisplayError(buf2, 0);
8409             }
8410         } else {
8411           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8412         }
8413         return;
8414     }
8415
8416     /*
8417      * Ignore other messages if game is not in progress
8418      */
8419     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8420         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8421
8422     /*
8423      * look for win, lose, draw, or draw offer
8424      */
8425     if (strncmp(message, "1-0", 3) == 0) {
8426         char *p, *q, *r = "";
8427         p = strchr(message, '{');
8428         if (p) {
8429             q = strchr(p, '}');
8430             if (q) {
8431                 *q = NULLCHAR;
8432                 r = p + 1;
8433             }
8434         }
8435         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8436         return;
8437     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8448         if (strcmp(r, "Black resigns") == 0) {
8449             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8450             return;
8451         }
8452         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "1/2", 3) == 0) {
8455         char *p, *q, *r = "";
8456         p = strchr(message, '{');
8457         if (p) {
8458             q = strchr(p, '}');
8459             if (q) {
8460                 *q = NULLCHAR;
8461                 r = p + 1;
8462             }
8463         }
8464
8465         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8466         return;
8467
8468     } else if (strncmp(message, "White resign", 12) == 0) {
8469         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8470         return;
8471     } else if (strncmp(message, "Black resign", 12) == 0) {
8472         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "White matches", 13) == 0 ||
8475                strncmp(message, "Black matches", 13) == 0   ) {
8476         /* [HGM] ignore GNUShogi noises */
8477         return;
8478     } else if (strncmp(message, "White", 5) == 0 &&
8479                message[5] != '(' &&
8480                StrStr(message, "Black") == NULL) {
8481         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strncmp(message, "Black", 5) == 0 &&
8484                message[5] != '(') {
8485         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8486         return;
8487     } else if (strcmp(message, "resign") == 0 ||
8488                strcmp(message, "computer resigns") == 0) {
8489         switch (gameMode) {
8490           case MachinePlaysBlack:
8491           case IcsPlayingBlack:
8492             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8493             break;
8494           case MachinePlaysWhite:
8495           case IcsPlayingWhite:
8496             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8497             break;
8498           case TwoMachinesPlay:
8499             if (cps->twoMachinesColor[0] == 'w')
8500               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8501             else
8502               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8503             break;
8504           default:
8505             /* can't happen */
8506             break;
8507         }
8508         return;
8509     } else if (strncmp(message, "opponent mates", 14) == 0) {
8510         switch (gameMode) {
8511           case MachinePlaysBlack:
8512           case IcsPlayingBlack:
8513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8514             break;
8515           case MachinePlaysWhite:
8516           case IcsPlayingWhite:
8517             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8518             break;
8519           case TwoMachinesPlay:
8520             if (cps->twoMachinesColor[0] == 'w')
8521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8522             else
8523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8524             break;
8525           default:
8526             /* can't happen */
8527             break;
8528         }
8529         return;
8530     } else if (strncmp(message, "computer mates", 14) == 0) {
8531         switch (gameMode) {
8532           case MachinePlaysBlack:
8533           case IcsPlayingBlack:
8534             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8535             break;
8536           case MachinePlaysWhite:
8537           case IcsPlayingWhite:
8538             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8539             break;
8540           case TwoMachinesPlay:
8541             if (cps->twoMachinesColor[0] == 'w')
8542               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8543             else
8544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8545             break;
8546           default:
8547             /* can't happen */
8548             break;
8549         }
8550         return;
8551     } else if (strncmp(message, "checkmate", 9) == 0) {
8552         if (WhiteOnMove(forwardMostMove)) {
8553             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8554         } else {
8555             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8556         }
8557         return;
8558     } else if (strstr(message, "Draw") != NULL ||
8559                strstr(message, "game is a draw") != NULL) {
8560         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8561         return;
8562     } else if (strstr(message, "offer") != NULL &&
8563                strstr(message, "draw") != NULL) {
8564 #if ZIPPY
8565         if (appData.zippyPlay && first.initDone) {
8566             /* Relay offer to ICS */
8567             SendToICS(ics_prefix);
8568             SendToICS("draw\n");
8569         }
8570 #endif
8571         cps->offeredDraw = 2; /* valid until this engine moves twice */
8572         if (gameMode == TwoMachinesPlay) {
8573             if (cps->other->offeredDraw) {
8574                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8575             /* [HGM] in two-machine mode we delay relaying draw offer      */
8576             /* until after we also have move, to see if it is really claim */
8577             }
8578         } else if (gameMode == MachinePlaysWhite ||
8579                    gameMode == MachinePlaysBlack) {
8580           if (userOfferedDraw) {
8581             DisplayInformation(_("Machine accepts your draw offer"));
8582             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8583           } else {
8584             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8585           }
8586         }
8587     }
8588
8589
8590     /*
8591      * Look for thinking output
8592      */
8593     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8594           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8595                                 ) {
8596         int plylev, mvleft, mvtot, curscore, time;
8597         char mvname[MOVE_LEN];
8598         u64 nodes; // [DM]
8599         char plyext;
8600         int ignore = FALSE;
8601         int prefixHint = FALSE;
8602         mvname[0] = NULLCHAR;
8603
8604         switch (gameMode) {
8605           case MachinePlaysBlack:
8606           case IcsPlayingBlack:
8607             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8608             break;
8609           case MachinePlaysWhite:
8610           case IcsPlayingWhite:
8611             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8612             break;
8613           case AnalyzeMode:
8614           case AnalyzeFile:
8615             break;
8616           case IcsObserving: /* [DM] icsEngineAnalyze */
8617             if (!appData.icsEngineAnalyze) ignore = TRUE;
8618             break;
8619           case TwoMachinesPlay:
8620             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8621                 ignore = TRUE;
8622             }
8623             break;
8624           default:
8625             ignore = TRUE;
8626             break;
8627         }
8628
8629         if (!ignore) {
8630             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8631             buf1[0] = NULLCHAR;
8632             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8633                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8634
8635                 if (plyext != ' ' && plyext != '\t') {
8636                     time *= 100;
8637                 }
8638
8639                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8640                 if( cps->scoreIsAbsolute &&
8641                     ( gameMode == MachinePlaysBlack ||
8642                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8643                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8644                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8645                      !WhiteOnMove(currentMove)
8646                     ) )
8647                 {
8648                     curscore = -curscore;
8649                 }
8650
8651                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8652
8653                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8654                         char buf[MSG_SIZ];
8655                         FILE *f;
8656                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8657                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8658                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8659                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8660                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8661                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8662                                 fclose(f);
8663                         } else DisplayError(_("failed writing PV"), 0);
8664                 }
8665
8666                 tempStats.depth = plylev;
8667                 tempStats.nodes = nodes;
8668                 tempStats.time = time;
8669                 tempStats.score = curscore;
8670                 tempStats.got_only_move = 0;
8671
8672                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8673                         int ticklen;
8674
8675                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8676                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8677                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8678                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8679                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8680                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8681                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8682                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8683                 }
8684
8685                 /* Buffer overflow protection */
8686                 if (pv[0] != NULLCHAR) {
8687                     if (strlen(pv) >= sizeof(tempStats.movelist)
8688                         && appData.debugMode) {
8689                         fprintf(debugFP,
8690                                 "PV is too long; using the first %u bytes.\n",
8691                                 (unsigned) sizeof(tempStats.movelist) - 1);
8692                     }
8693
8694                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8695                 } else {
8696                     sprintf(tempStats.movelist, " no PV\n");
8697                 }
8698
8699                 if (tempStats.seen_stat) {
8700                     tempStats.ok_to_send = 1;
8701                 }
8702
8703                 if (strchr(tempStats.movelist, '(') != NULL) {
8704                     tempStats.line_is_book = 1;
8705                     tempStats.nr_moves = 0;
8706                     tempStats.moves_left = 0;
8707                 } else {
8708                     tempStats.line_is_book = 0;
8709                 }
8710
8711                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8712                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8713
8714                 SendProgramStatsToFrontend( cps, &tempStats );
8715
8716                 /*
8717                     [AS] Protect the thinkOutput buffer from overflow... this
8718                     is only useful if buf1 hasn't overflowed first!
8719                 */
8720                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8721                          plylev,
8722                          (gameMode == TwoMachinesPlay ?
8723                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8724                          ((double) curscore) / 100.0,
8725                          prefixHint ? lastHint : "",
8726                          prefixHint ? " " : "" );
8727
8728                 if( buf1[0] != NULLCHAR ) {
8729                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8730
8731                     if( strlen(pv) > max_len ) {
8732                         if( appData.debugMode) {
8733                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8734                         }
8735                         pv[max_len+1] = '\0';
8736                     }
8737
8738                     strcat( thinkOutput, pv);
8739                 }
8740
8741                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8742                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8743                     DisplayMove(currentMove - 1);
8744                 }
8745                 return;
8746
8747             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8748                 /* crafty (9.25+) says "(only move) <move>"
8749                  * if there is only 1 legal move
8750                  */
8751                 sscanf(p, "(only move) %s", buf1);
8752                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8753                 sprintf(programStats.movelist, "%s (only move)", buf1);
8754                 programStats.depth = 1;
8755                 programStats.nr_moves = 1;
8756                 programStats.moves_left = 1;
8757                 programStats.nodes = 1;
8758                 programStats.time = 1;
8759                 programStats.got_only_move = 1;
8760
8761                 /* Not really, but we also use this member to
8762                    mean "line isn't going to change" (Crafty
8763                    isn't searching, so stats won't change) */
8764                 programStats.line_is_book = 1;
8765
8766                 SendProgramStatsToFrontend( cps, &programStats );
8767
8768                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8769                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8770                     DisplayMove(currentMove - 1);
8771                 }
8772                 return;
8773             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8774                               &time, &nodes, &plylev, &mvleft,
8775                               &mvtot, mvname) >= 5) {
8776                 /* The stat01: line is from Crafty (9.29+) in response
8777                    to the "." command */
8778                 programStats.seen_stat = 1;
8779                 cps->maybeThinking = TRUE;
8780
8781                 if (programStats.got_only_move || !appData.periodicUpdates)
8782                   return;
8783
8784                 programStats.depth = plylev;
8785                 programStats.time = time;
8786                 programStats.nodes = nodes;
8787                 programStats.moves_left = mvleft;
8788                 programStats.nr_moves = mvtot;
8789                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8790                 programStats.ok_to_send = 1;
8791                 programStats.movelist[0] = '\0';
8792
8793                 SendProgramStatsToFrontend( cps, &programStats );
8794
8795                 return;
8796
8797             } else if (strncmp(message,"++",2) == 0) {
8798                 /* Crafty 9.29+ outputs this */
8799                 programStats.got_fail = 2;
8800                 return;
8801
8802             } else if (strncmp(message,"--",2) == 0) {
8803                 /* Crafty 9.29+ outputs this */
8804                 programStats.got_fail = 1;
8805                 return;
8806
8807             } else if (thinkOutput[0] != NULLCHAR &&
8808                        strncmp(message, "    ", 4) == 0) {
8809                 unsigned message_len;
8810
8811                 p = message;
8812                 while (*p && *p == ' ') p++;
8813
8814                 message_len = strlen( p );
8815
8816                 /* [AS] Avoid buffer overflow */
8817                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8818                     strcat(thinkOutput, " ");
8819                     strcat(thinkOutput, p);
8820                 }
8821
8822                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8823                     strcat(programStats.movelist, " ");
8824                     strcat(programStats.movelist, p);
8825                 }
8826
8827                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8828                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8829                     DisplayMove(currentMove - 1);
8830                 }
8831                 return;
8832             }
8833         }
8834         else {
8835             buf1[0] = NULLCHAR;
8836
8837             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8838                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8839             {
8840                 ChessProgramStats cpstats;
8841
8842                 if (plyext != ' ' && plyext != '\t') {
8843                     time *= 100;
8844                 }
8845
8846                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8847                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8848                     curscore = -curscore;
8849                 }
8850
8851                 cpstats.depth = plylev;
8852                 cpstats.nodes = nodes;
8853                 cpstats.time = time;
8854                 cpstats.score = curscore;
8855                 cpstats.got_only_move = 0;
8856                 cpstats.movelist[0] = '\0';
8857
8858                 if (buf1[0] != NULLCHAR) {
8859                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8860                 }
8861
8862                 cpstats.ok_to_send = 0;
8863                 cpstats.line_is_book = 0;
8864                 cpstats.nr_moves = 0;
8865                 cpstats.moves_left = 0;
8866
8867                 SendProgramStatsToFrontend( cps, &cpstats );
8868             }
8869         }
8870     }
8871 }
8872
8873
8874 /* Parse a game score from the character string "game", and
8875    record it as the history of the current game.  The game
8876    score is NOT assumed to start from the standard position.
8877    The display is not updated in any way.
8878    */
8879 void
8880 ParseGameHistory (char *game)
8881 {
8882     ChessMove moveType;
8883     int fromX, fromY, toX, toY, boardIndex;
8884     char promoChar;
8885     char *p, *q;
8886     char buf[MSG_SIZ];
8887
8888     if (appData.debugMode)
8889       fprintf(debugFP, "Parsing game history: %s\n", game);
8890
8891     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8892     gameInfo.site = StrSave(appData.icsHost);
8893     gameInfo.date = PGNDate();
8894     gameInfo.round = StrSave("-");
8895
8896     /* Parse out names of players */
8897     while (*game == ' ') game++;
8898     p = buf;
8899     while (*game != ' ') *p++ = *game++;
8900     *p = NULLCHAR;
8901     gameInfo.white = StrSave(buf);
8902     while (*game == ' ') game++;
8903     p = buf;
8904     while (*game != ' ' && *game != '\n') *p++ = *game++;
8905     *p = NULLCHAR;
8906     gameInfo.black = StrSave(buf);
8907
8908     /* Parse moves */
8909     boardIndex = blackPlaysFirst ? 1 : 0;
8910     yynewstr(game);
8911     for (;;) {
8912         yyboardindex = boardIndex;
8913         moveType = (ChessMove) Myylex();
8914         switch (moveType) {
8915           case IllegalMove:             /* maybe suicide chess, etc. */
8916   if (appData.debugMode) {
8917     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8918     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8919     setbuf(debugFP, NULL);
8920   }
8921           case WhitePromotion:
8922           case BlackPromotion:
8923           case WhiteNonPromotion:
8924           case BlackNonPromotion:
8925           case NormalMove:
8926           case WhiteCapturesEnPassant:
8927           case BlackCapturesEnPassant:
8928           case WhiteKingSideCastle:
8929           case WhiteQueenSideCastle:
8930           case BlackKingSideCastle:
8931           case BlackQueenSideCastle:
8932           case WhiteKingSideCastleWild:
8933           case WhiteQueenSideCastleWild:
8934           case BlackKingSideCastleWild:
8935           case BlackQueenSideCastleWild:
8936           /* PUSH Fabien */
8937           case WhiteHSideCastleFR:
8938           case WhiteASideCastleFR:
8939           case BlackHSideCastleFR:
8940           case BlackASideCastleFR:
8941           /* POP Fabien */
8942             fromX = currentMoveString[0] - AAA;
8943             fromY = currentMoveString[1] - ONE;
8944             toX = currentMoveString[2] - AAA;
8945             toY = currentMoveString[3] - ONE;
8946             promoChar = currentMoveString[4];
8947             break;
8948           case WhiteDrop:
8949           case BlackDrop:
8950             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8951             fromX = moveType == WhiteDrop ?
8952               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8953             (int) CharToPiece(ToLower(currentMoveString[0]));
8954             fromY = DROP_RANK;
8955             toX = currentMoveString[2] - AAA;
8956             toY = currentMoveString[3] - ONE;
8957             promoChar = NULLCHAR;
8958             break;
8959           case AmbiguousMove:
8960             /* bug? */
8961             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8962   if (appData.debugMode) {
8963     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8965     setbuf(debugFP, NULL);
8966   }
8967             DisplayError(buf, 0);
8968             return;
8969           case ImpossibleMove:
8970             /* bug? */
8971             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8972   if (appData.debugMode) {
8973     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8974     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8975     setbuf(debugFP, NULL);
8976   }
8977             DisplayError(buf, 0);
8978             return;
8979           case EndOfFile:
8980             if (boardIndex < backwardMostMove) {
8981                 /* Oops, gap.  How did that happen? */
8982                 DisplayError(_("Gap in move list"), 0);
8983                 return;
8984             }
8985             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8986             if (boardIndex > forwardMostMove) {
8987                 forwardMostMove = boardIndex;
8988             }
8989             return;
8990           case ElapsedTime:
8991             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8992                 strcat(parseList[boardIndex-1], " ");
8993                 strcat(parseList[boardIndex-1], yy_text);
8994             }
8995             continue;
8996           case Comment:
8997           case PGNTag:
8998           case NAG:
8999           default:
9000             /* ignore */
9001             continue;
9002           case WhiteWins:
9003           case BlackWins:
9004           case GameIsDrawn:
9005           case GameUnfinished:
9006             if (gameMode == IcsExamining) {
9007                 if (boardIndex < backwardMostMove) {
9008                     /* Oops, gap.  How did that happen? */
9009                     return;
9010                 }
9011                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9012                 return;
9013             }
9014             gameInfo.result = moveType;
9015             p = strchr(yy_text, '{');
9016             if (p == NULL) p = strchr(yy_text, '(');
9017             if (p == NULL) {
9018                 p = yy_text;
9019                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9020             } else {
9021                 q = strchr(p, *p == '{' ? '}' : ')');
9022                 if (q != NULL) *q = NULLCHAR;
9023                 p++;
9024             }
9025             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9026             gameInfo.resultDetails = StrSave(p);
9027             continue;
9028         }
9029         if (boardIndex >= forwardMostMove &&
9030             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9031             backwardMostMove = blackPlaysFirst ? 1 : 0;
9032             return;
9033         }
9034         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9035                                  fromY, fromX, toY, toX, promoChar,
9036                                  parseList[boardIndex]);
9037         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9038         /* currentMoveString is set as a side-effect of yylex */
9039         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9040         strcat(moveList[boardIndex], "\n");
9041         boardIndex++;
9042         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9043         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9044           case MT_NONE:
9045           case MT_STALEMATE:
9046           default:
9047             break;
9048           case MT_CHECK:
9049             if(gameInfo.variant != VariantShogi)
9050                 strcat(parseList[boardIndex - 1], "+");
9051             break;
9052           case MT_CHECKMATE:
9053           case MT_STAINMATE:
9054             strcat(parseList[boardIndex - 1], "#");
9055             break;
9056         }
9057     }
9058 }
9059
9060
9061 /* Apply a move to the given board  */
9062 void
9063 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9064 {
9065   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9066   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9067
9068     /* [HGM] compute & store e.p. status and castling rights for new position */
9069     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9070
9071       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9072       oldEP = (signed char)board[EP_STATUS];
9073       board[EP_STATUS] = EP_NONE;
9074
9075   if (fromY == DROP_RANK) {
9076         /* must be first */
9077         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9078             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9079             return;
9080         }
9081         piece = board[toY][toX] = (ChessSquare) fromX;
9082   } else {
9083       int i;
9084
9085       if( board[toY][toX] != EmptySquare )
9086            board[EP_STATUS] = EP_CAPTURE;
9087
9088       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9089            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9090                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9091       } else
9092       if( board[fromY][fromX] == WhitePawn ) {
9093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9094                board[EP_STATUS] = EP_PAWN_MOVE;
9095            if( toY-fromY==2) {
9096                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9097                         gameInfo.variant != VariantBerolina || toX < fromX)
9098                       board[EP_STATUS] = toX | berolina;
9099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9100                         gameInfo.variant != VariantBerolina || toX > fromX)
9101                       board[EP_STATUS] = toX;
9102            }
9103       } else
9104       if( board[fromY][fromX] == BlackPawn ) {
9105            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9106                board[EP_STATUS] = EP_PAWN_MOVE;
9107            if( toY-fromY== -2) {
9108                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9109                         gameInfo.variant != VariantBerolina || toX < fromX)
9110                       board[EP_STATUS] = toX | berolina;
9111                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9112                         gameInfo.variant != VariantBerolina || toX > fromX)
9113                       board[EP_STATUS] = toX;
9114            }
9115        }
9116
9117        for(i=0; i<nrCastlingRights; i++) {
9118            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9119               board[CASTLING][i] == toX   && castlingRank[i] == toY
9120              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9121        }
9122
9123      if (fromX == toX && fromY == toY) return;
9124
9125      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9126      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9127      if(gameInfo.variant == VariantKnightmate)
9128          king += (int) WhiteUnicorn - (int) WhiteKing;
9129
9130     /* Code added by Tord: */
9131     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9132     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9133         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9134       board[fromY][fromX] = EmptySquare;
9135       board[toY][toX] = EmptySquare;
9136       if((toX > fromX) != (piece == WhiteRook)) {
9137         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9138       } else {
9139         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9140       }
9141     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9142                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9143       board[fromY][fromX] = EmptySquare;
9144       board[toY][toX] = EmptySquare;
9145       if((toX > fromX) != (piece == BlackRook)) {
9146         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9147       } else {
9148         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9149       }
9150     /* End of code added by Tord */
9151
9152     } else if (board[fromY][fromX] == king
9153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9154         && toY == fromY && toX > fromX+1) {
9155         board[fromY][fromX] = EmptySquare;
9156         board[toY][toX] = king;
9157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9158         board[fromY][BOARD_RGHT-1] = EmptySquare;
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161                && toY == fromY && toX < fromX-1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9165         board[fromY][BOARD_LEFT] = EmptySquare;
9166     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9167                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9168                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9169                ) {
9170         /* white pawn promotion */
9171         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9172         if(gameInfo.variant==VariantBughouse ||
9173            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9174             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9175         board[fromY][fromX] = EmptySquare;
9176     } else if ((fromY >= BOARD_HEIGHT>>1)
9177                && (toX != fromX)
9178                && gameInfo.variant != VariantXiangqi
9179                && gameInfo.variant != VariantBerolina
9180                && (board[fromY][fromX] == WhitePawn)
9181                && (board[toY][toX] == EmptySquare)) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = WhitePawn;
9184         captured = board[toY - 1][toX];
9185         board[toY - 1][toX] = EmptySquare;
9186     } else if ((fromY == BOARD_HEIGHT-4)
9187                && (toX == fromX)
9188                && gameInfo.variant == VariantBerolina
9189                && (board[fromY][fromX] == WhitePawn)
9190                && (board[toY][toX] == EmptySquare)) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = WhitePawn;
9193         if(oldEP & EP_BEROLIN_A) {
9194                 captured = board[fromY][fromX-1];
9195                 board[fromY][fromX-1] = EmptySquare;
9196         }else{  captured = board[fromY][fromX+1];
9197                 board[fromY][fromX+1] = EmptySquare;
9198         }
9199     } else if (board[fromY][fromX] == king
9200         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9201                && toY == fromY && toX > fromX+1) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = king;
9204         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9205         board[fromY][BOARD_RGHT-1] = EmptySquare;
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX < fromX-1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9212         board[fromY][BOARD_LEFT] = EmptySquare;
9213     } else if (fromY == 7 && fromX == 3
9214                && board[fromY][fromX] == BlackKing
9215                && toY == 7 && toX == 5) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = BlackKing;
9218         board[fromY][7] = EmptySquare;
9219         board[toY][4] = BlackRook;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 1) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][0] = EmptySquare;
9226         board[toY][2] = BlackRook;
9227     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9228                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9229                && toY < promoRank && promoChar
9230                ) {
9231         /* black pawn promotion */
9232         board[toY][toX] = CharToPiece(ToLower(promoChar));
9233         if(gameInfo.variant==VariantBughouse ||
9234            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9235             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9236         board[fromY][fromX] = EmptySquare;
9237     } else if ((fromY < BOARD_HEIGHT>>1)
9238                && (toX != fromX)
9239                && gameInfo.variant != VariantXiangqi
9240                && gameInfo.variant != VariantBerolina
9241                && (board[fromY][fromX] == BlackPawn)
9242                && (board[toY][toX] == EmptySquare)) {
9243         board[fromY][fromX] = EmptySquare;
9244         board[toY][toX] = BlackPawn;
9245         captured = board[toY + 1][toX];
9246         board[toY + 1][toX] = EmptySquare;
9247     } else if ((fromY == 3)
9248                && (toX == fromX)
9249                && gameInfo.variant == VariantBerolina
9250                && (board[fromY][fromX] == BlackPawn)
9251                && (board[toY][toX] == EmptySquare)) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = BlackPawn;
9254         if(oldEP & EP_BEROLIN_A) {
9255                 captured = board[fromY][fromX-1];
9256                 board[fromY][fromX-1] = EmptySquare;
9257         }else{  captured = board[fromY][fromX+1];
9258                 board[fromY][fromX+1] = EmptySquare;
9259         }
9260     } else {
9261         board[toY][toX] = board[fromY][fromX];
9262         board[fromY][fromX] = EmptySquare;
9263     }
9264   }
9265
9266     if (gameInfo.holdingsWidth != 0) {
9267
9268       /* !!A lot more code needs to be written to support holdings  */
9269       /* [HGM] OK, so I have written it. Holdings are stored in the */
9270       /* penultimate board files, so they are automaticlly stored   */
9271       /* in the game history.                                       */
9272       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9273                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9274         /* Delete from holdings, by decreasing count */
9275         /* and erasing image if necessary            */
9276         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9277         if(p < (int) BlackPawn) { /* white drop */
9278              p -= (int)WhitePawn;
9279                  p = PieceToNumber((ChessSquare)p);
9280              if(p >= gameInfo.holdingsSize) p = 0;
9281              if(--board[p][BOARD_WIDTH-2] <= 0)
9282                   board[p][BOARD_WIDTH-1] = EmptySquare;
9283              if((int)board[p][BOARD_WIDTH-2] < 0)
9284                         board[p][BOARD_WIDTH-2] = 0;
9285         } else {                  /* black drop */
9286              p -= (int)BlackPawn;
9287                  p = PieceToNumber((ChessSquare)p);
9288              if(p >= gameInfo.holdingsSize) p = 0;
9289              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9290                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9291              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9292                         board[BOARD_HEIGHT-1-p][1] = 0;
9293         }
9294       }
9295       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9296           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9297         /* [HGM] holdings: Add to holdings, if holdings exist */
9298         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9299                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9300                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9301         }
9302         p = (int) captured;
9303         if (p >= (int) BlackPawn) {
9304           p -= (int)BlackPawn;
9305           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9306                   /* in Shogi restore piece to its original  first */
9307                   captured = (ChessSquare) (DEMOTED captured);
9308                   p = DEMOTED p;
9309           }
9310           p = PieceToNumber((ChessSquare)p);
9311           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9312           board[p][BOARD_WIDTH-2]++;
9313           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9314         } else {
9315           p -= (int)WhitePawn;
9316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9317                   captured = (ChessSquare) (DEMOTED captured);
9318                   p = DEMOTED p;
9319           }
9320           p = PieceToNumber((ChessSquare)p);
9321           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9322           board[BOARD_HEIGHT-1-p][1]++;
9323           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9324         }
9325       }
9326     } else if (gameInfo.variant == VariantAtomic) {
9327       if (captured != EmptySquare) {
9328         int y, x;
9329         for (y = toY-1; y <= toY+1; y++) {
9330           for (x = toX-1; x <= toX+1; x++) {
9331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9333               board[y][x] = EmptySquare;
9334             }
9335           }
9336         }
9337         board[toY][toX] = EmptySquare;
9338       }
9339     }
9340     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9341         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9342     } else
9343     if(promoChar == '+') {
9344         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9345         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9346     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9347         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9348     }
9349     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9350                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9351         // [HGM] superchess: take promotion piece out of holdings
9352         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9353         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9354             if(!--board[k][BOARD_WIDTH-2])
9355                 board[k][BOARD_WIDTH-1] = EmptySquare;
9356         } else {
9357             if(!--board[BOARD_HEIGHT-1-k][1])
9358                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9359         }
9360     }
9361
9362 }
9363
9364 /* Updates forwardMostMove */
9365 void
9366 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9367 {
9368 //    forwardMostMove++; // [HGM] bare: moved downstream
9369
9370     (void) CoordsToAlgebraic(boards[forwardMostMove],
9371                              PosFlags(forwardMostMove),
9372                              fromY, fromX, toY, toX, promoChar,
9373                              parseList[forwardMostMove]);
9374
9375     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9376         int timeLeft; static int lastLoadFlag=0; int king, piece;
9377         piece = boards[forwardMostMove][fromY][fromX];
9378         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9379         if(gameInfo.variant == VariantKnightmate)
9380             king += (int) WhiteUnicorn - (int) WhiteKing;
9381         if(forwardMostMove == 0) {
9382             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9383                 fprintf(serverMoves, "%s;", UserName());
9384             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9385                 fprintf(serverMoves, "%s;", second.tidy);
9386             fprintf(serverMoves, "%s;", first.tidy);
9387             if(gameMode == MachinePlaysWhite)
9388                 fprintf(serverMoves, "%s;", UserName());
9389             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9390                 fprintf(serverMoves, "%s;", second.tidy);
9391         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9392         lastLoadFlag = loadFlag;
9393         // print base move
9394         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9395         // print castling suffix
9396         if( toY == fromY && piece == king ) {
9397             if(toX-fromX > 1)
9398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9399             if(fromX-toX >1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9401         }
9402         // e.p. suffix
9403         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9404              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9405              boards[forwardMostMove][toY][toX] == EmptySquare
9406              && fromX != toX && fromY != toY)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9408         // promotion suffix
9409         if(promoChar != NULLCHAR)
9410                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9411         if(!loadFlag) {
9412                 char buf[MOVE_LEN*2], *p; int len;
9413             fprintf(serverMoves, "/%d/%d",
9414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9416             else                      timeLeft = blackTimeRemaining/1000;
9417             fprintf(serverMoves, "/%d", timeLeft);
9418                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9419                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9420                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9421             fprintf(serverMoves, "/%s", buf);
9422         }
9423         fflush(serverMoves);
9424     }
9425
9426     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9427         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9428       return;
9429     }
9430     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9431     if (commentList[forwardMostMove+1] != NULL) {
9432         free(commentList[forwardMostMove+1]);
9433         commentList[forwardMostMove+1] = NULL;
9434     }
9435     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9437     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9438     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9439     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9440     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9441     adjustedClock = FALSE;
9442     gameInfo.result = GameUnfinished;
9443     if (gameInfo.resultDetails != NULL) {
9444         free(gameInfo.resultDetails);
9445         gameInfo.resultDetails = NULL;
9446     }
9447     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9448                               moveList[forwardMostMove - 1]);
9449     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9450       case MT_NONE:
9451       case MT_STALEMATE:
9452       default:
9453         break;
9454       case MT_CHECK:
9455         if(gameInfo.variant != VariantShogi)
9456             strcat(parseList[forwardMostMove - 1], "+");
9457         break;
9458       case MT_CHECKMATE:
9459       case MT_STAINMATE:
9460         strcat(parseList[forwardMostMove - 1], "#");
9461         break;
9462     }
9463     if (appData.debugMode) {
9464         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9465     }
9466
9467 }
9468
9469 /* Updates currentMove if not pausing */
9470 void
9471 ShowMove (int fromX, int fromY, int toX, int toY)
9472 {
9473     int instant = (gameMode == PlayFromGameFile) ?
9474         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9475     if(appData.noGUI) return;
9476     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9477         if (!instant) {
9478             if (forwardMostMove == currentMove + 1) {
9479                 AnimateMove(boards[forwardMostMove - 1],
9480                             fromX, fromY, toX, toY);
9481             }
9482             if (appData.highlightLastMove) {
9483                 SetHighlights(fromX, fromY, toX, toY);
9484             }
9485         }
9486         currentMove = forwardMostMove;
9487     }
9488
9489     if (instant) return;
9490
9491     DisplayMove(currentMove - 1);
9492     DrawPosition(FALSE, boards[currentMove]);
9493     DisplayBothClocks();
9494     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9495 }
9496
9497 void
9498 SendEgtPath (ChessProgramState *cps)
9499 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9500         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9501
9502         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9503
9504         while(*p) {
9505             char c, *q = name+1, *r, *s;
9506
9507             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9508             while(*p && *p != ',') *q++ = *p++;
9509             *q++ = ':'; *q = 0;
9510             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9511                 strcmp(name, ",nalimov:") == 0 ) {
9512                 // take nalimov path from the menu-changeable option first, if it is defined
9513               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9514                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9515             } else
9516             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9517                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9518                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9519                 s = r = StrStr(s, ":") + 1; // beginning of path info
9520                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9521                 c = *r; *r = 0;             // temporarily null-terminate path info
9522                     *--q = 0;               // strip of trailig ':' from name
9523                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9524                 *r = c;
9525                 SendToProgram(buf,cps);     // send egtbpath command for this format
9526             }
9527             if(*p == ',') p++; // read away comma to position for next format name
9528         }
9529 }
9530
9531 void
9532 InitChessProgram (ChessProgramState *cps, int setup)
9533 /* setup needed to setup FRC opening position */
9534 {
9535     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9536     if (appData.noChessProgram) return;
9537     hintRequested = FALSE;
9538     bookRequested = FALSE;
9539
9540     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9541     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9542     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9543     if(cps->memSize) { /* [HGM] memory */
9544       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9545         SendToProgram(buf, cps);
9546     }
9547     SendEgtPath(cps); /* [HGM] EGT */
9548     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9549       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9550         SendToProgram(buf, cps);
9551     }
9552
9553     SendToProgram(cps->initString, cps);
9554     if (gameInfo.variant != VariantNormal &&
9555         gameInfo.variant != VariantLoadable
9556         /* [HGM] also send variant if board size non-standard */
9557         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9558                                             ) {
9559       char *v = VariantName(gameInfo.variant);
9560       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9561         /* [HGM] in protocol 1 we have to assume all variants valid */
9562         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9563         DisplayFatalError(buf, 0, 1);
9564         return;
9565       }
9566
9567       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9568       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantXiangqi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9571       if( gameInfo.variant == VariantShogi )
9572            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9573       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9574            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9575       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9576           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9577            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantCourier )
9579            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantSuper )
9581            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantGreat )
9583            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9584       if( gameInfo.variant == VariantSChess )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9586       if( gameInfo.variant == VariantGrand )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9588
9589       if(overruled) {
9590         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9591                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9592            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9593            if(StrStr(cps->variants, b) == NULL) {
9594                // specific sized variant not known, check if general sizing allowed
9595                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9596                    if(StrStr(cps->variants, "boardsize") == NULL) {
9597                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9598                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9599                        DisplayFatalError(buf, 0, 1);
9600                        return;
9601                    }
9602                    /* [HGM] here we really should compare with the maximum supported board size */
9603                }
9604            }
9605       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9606       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9607       SendToProgram(buf, cps);
9608     }
9609     currentlyInitializedVariant = gameInfo.variant;
9610
9611     /* [HGM] send opening position in FRC to first engine */
9612     if(setup) {
9613           SendToProgram("force\n", cps);
9614           SendBoard(cps, 0);
9615           /* engine is now in force mode! Set flag to wake it up after first move. */
9616           setboardSpoiledMachineBlack = 1;
9617     }
9618
9619     if (cps->sendICS) {
9620       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9621       SendToProgram(buf, cps);
9622     }
9623     cps->maybeThinking = FALSE;
9624     cps->offeredDraw = 0;
9625     if (!appData.icsActive) {
9626         SendTimeControl(cps, movesPerSession, timeControl,
9627                         timeIncrement, appData.searchDepth,
9628                         searchTime);
9629     }
9630     if (appData.showThinking
9631         // [HGM] thinking: four options require thinking output to be sent
9632         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9633                                 ) {
9634         SendToProgram("post\n", cps);
9635     }
9636     SendToProgram("hard\n", cps);
9637     if (!appData.ponderNextMove) {
9638         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9639            it without being sure what state we are in first.  "hard"
9640            is not a toggle, so that one is OK.
9641          */
9642         SendToProgram("easy\n", cps);
9643     }
9644     if (cps->usePing) {
9645       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9646       SendToProgram(buf, cps);
9647     }
9648     cps->initDone = TRUE;
9649     ClearEngineOutputPane(cps == &second);
9650 }
9651
9652
9653 void
9654 StartChessProgram (ChessProgramState *cps)
9655 {
9656     char buf[MSG_SIZ];
9657     int err;
9658
9659     if (appData.noChessProgram) return;
9660     cps->initDone = FALSE;
9661
9662     if (strcmp(cps->host, "localhost") == 0) {
9663         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9664     } else if (*appData.remoteShell == NULLCHAR) {
9665         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9666     } else {
9667         if (*appData.remoteUser == NULLCHAR) {
9668           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9669                     cps->program);
9670         } else {
9671           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9672                     cps->host, appData.remoteUser, cps->program);
9673         }
9674         err = StartChildProcess(buf, "", &cps->pr);
9675     }
9676
9677     if (err != 0) {
9678       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9679         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9680         if(cps != &first) return;
9681         appData.noChessProgram = TRUE;
9682         ThawUI();
9683         SetNCPMode();
9684 //      DisplayFatalError(buf, err, 1);
9685 //      cps->pr = NoProc;
9686 //      cps->isr = NULL;
9687         return;
9688     }
9689
9690     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9691     if (cps->protocolVersion > 1) {
9692       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9693       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9694       cps->comboCnt = 0;  //                and values of combo boxes
9695       SendToProgram(buf, cps);
9696     } else {
9697       SendToProgram("xboard\n", cps);
9698     }
9699 }
9700
9701 void
9702 TwoMachinesEventIfReady P((void))
9703 {
9704   static int curMess = 0;
9705   if (first.lastPing != first.lastPong) {
9706     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9707     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9708     return;
9709   }
9710   if (second.lastPing != second.lastPong) {
9711     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9712     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9713     return;
9714   }
9715   DisplayMessage("", ""); curMess = 0;
9716   ThawUI();
9717   TwoMachinesEvent();
9718 }
9719
9720 char *
9721 MakeName (char *template)
9722 {
9723     time_t clock;
9724     struct tm *tm;
9725     static char buf[MSG_SIZ];
9726     char *p = buf;
9727     int i;
9728
9729     clock = time((time_t *)NULL);
9730     tm = localtime(&clock);
9731
9732     while(*p++ = *template++) if(p[-1] == '%') {
9733         switch(*template++) {
9734           case 0:   *p = 0; return buf;
9735           case 'Y': i = tm->tm_year+1900; break;
9736           case 'y': i = tm->tm_year-100; break;
9737           case 'M': i = tm->tm_mon+1; break;
9738           case 'd': i = tm->tm_mday; break;
9739           case 'h': i = tm->tm_hour; break;
9740           case 'm': i = tm->tm_min; break;
9741           case 's': i = tm->tm_sec; break;
9742           default:  i = 0;
9743         }
9744         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9745     }
9746     return buf;
9747 }
9748
9749 int
9750 CountPlayers (char *p)
9751 {
9752     int n = 0;
9753     while(p = strchr(p, '\n')) p++, n++; // count participants
9754     return n;
9755 }
9756
9757 FILE *
9758 WriteTourneyFile (char *results, FILE *f)
9759 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9760     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9761     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9762         // create a file with tournament description
9763         fprintf(f, "-participants {%s}\n", appData.participants);
9764         fprintf(f, "-seedBase %d\n", appData.seedBase);
9765         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9766         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9767         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9768         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9769         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9770         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9771         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9772         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9773         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9774         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9775         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9776         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9777         if(searchTime > 0)
9778                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9779         else {
9780                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9781                 fprintf(f, "-tc %s\n", appData.timeControl);
9782                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9783         }
9784         fprintf(f, "-results \"%s\"\n", results);
9785     }
9786     return f;
9787 }
9788
9789 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9790
9791 void
9792 Substitute (char *participants, int expunge)
9793 {
9794     int i, changed, changes=0, nPlayers=0;
9795     char *p, *q, *r, buf[MSG_SIZ];
9796     if(participants == NULL) return;
9797     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9798     r = p = participants; q = appData.participants;
9799     while(*p && *p == *q) {
9800         if(*p == '\n') r = p+1, nPlayers++;
9801         p++; q++;
9802     }
9803     if(*p) { // difference
9804         while(*p && *p++ != '\n');
9805         while(*q && *q++ != '\n');
9806       changed = nPlayers;
9807         changes = 1 + (strcmp(p, q) != 0);
9808     }
9809     if(changes == 1) { // a single engine mnemonic was changed
9810         q = r; while(*q) nPlayers += (*q++ == '\n');
9811         p = buf; while(*r && (*p = *r++) != '\n') p++;
9812         *p = NULLCHAR;
9813         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9814         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9815         if(mnemonic[i]) { // The substitute is valid
9816             FILE *f;
9817             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9818                 flock(fileno(f), LOCK_EX);
9819                 ParseArgsFromFile(f);
9820                 fseek(f, 0, SEEK_SET);
9821                 FREE(appData.participants); appData.participants = participants;
9822                 if(expunge) { // erase results of replaced engine
9823                     int len = strlen(appData.results), w, b, dummy;
9824                     for(i=0; i<len; i++) {
9825                         Pairing(i, nPlayers, &w, &b, &dummy);
9826                         if((w == changed || b == changed) && appData.results[i] == '*') {
9827                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9828                             fclose(f);
9829                             return;
9830                         }
9831                     }
9832                     for(i=0; i<len; i++) {
9833                         Pairing(i, nPlayers, &w, &b, &dummy);
9834                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9835                     }
9836                 }
9837                 WriteTourneyFile(appData.results, f);
9838                 fclose(f); // release lock
9839                 return;
9840             }
9841         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9842     }
9843     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9844     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9845     free(participants);
9846     return;
9847 }
9848
9849 int
9850 CreateTourney (char *name)
9851 {
9852         FILE *f;
9853         if(matchMode && strcmp(name, appData.tourneyFile)) {
9854              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9855         }
9856         if(name[0] == NULLCHAR) {
9857             if(appData.participants[0])
9858                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9859             return 0;
9860         }
9861         f = fopen(name, "r");
9862         if(f) { // file exists
9863             ASSIGN(appData.tourneyFile, name);
9864             ParseArgsFromFile(f); // parse it
9865         } else {
9866             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9867             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9868                 DisplayError(_("Not enough participants"), 0);
9869                 return 0;
9870             }
9871             ASSIGN(appData.tourneyFile, name);
9872             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9873             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9874         }
9875         fclose(f);
9876         appData.noChessProgram = FALSE;
9877         appData.clockMode = TRUE;
9878         SetGNUMode();
9879         return 1;
9880 }
9881
9882 int
9883 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9884 {
9885     char buf[MSG_SIZ], *p, *q;
9886     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9887     skip = !all && group[0]; // if group requested, we start in skip mode
9888     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9889         p = names; q = buf; header = 0;
9890         while(*p && *p != '\n') *q++ = *p++;
9891         *q = 0;
9892         if(*p == '\n') p++;
9893         if(buf[0] == '#') {
9894             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9895             depth++; // we must be entering a new group
9896             if(all) continue; // suppress printing group headers when complete list requested
9897             header = 1;
9898             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9899         }
9900         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9901         if(engineList[i]) free(engineList[i]);
9902         engineList[i] = strdup(buf);
9903         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9904         if(engineMnemonic[i]) free(engineMnemonic[i]);
9905         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9906             strcat(buf, " (");
9907             sscanf(q + 8, "%s", buf + strlen(buf));
9908             strcat(buf, ")");
9909         }
9910         engineMnemonic[i] = strdup(buf);
9911         i++;
9912     }
9913     engineList[i] = engineMnemonic[i] = NULL;
9914     return i;
9915 }
9916
9917 // following implemented as macro to avoid type limitations
9918 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9919
9920 void
9921 SwapEngines (int n)
9922 {   // swap settings for first engine and other engine (so far only some selected options)
9923     int h;
9924     char *p;
9925     if(n == 0) return;
9926     SWAP(directory, p)
9927     SWAP(chessProgram, p)
9928     SWAP(isUCI, h)
9929     SWAP(hasOwnBookUCI, h)
9930     SWAP(protocolVersion, h)
9931     SWAP(reuse, h)
9932     SWAP(scoreIsAbsolute, h)
9933     SWAP(timeOdds, h)
9934     SWAP(logo, p)
9935     SWAP(pgnName, p)
9936     SWAP(pvSAN, h)
9937     SWAP(engOptions, p)
9938 }
9939
9940 int
9941 SetPlayer (int player, char *p)
9942 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9943     int i;
9944     char buf[MSG_SIZ], *engineName;
9945     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9946     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9947     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9948     if(mnemonic[i]) {
9949         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9950         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9951         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9952         ParseArgsFromString(buf);
9953     }
9954     free(engineName);
9955     return i;
9956 }
9957
9958 char *recentEngines;
9959
9960 void
9961 RecentEngineEvent (int nr)
9962 {
9963     int n;
9964 //    SwapEngines(1); // bump first to second
9965 //    ReplaceEngine(&second, 1); // and load it there
9966     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9967     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9968     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9969         ReplaceEngine(&first, 0);
9970         FloatToFront(&appData.recentEngineList, command[n]);
9971     }
9972 }
9973
9974 int
9975 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9976 {   // determine players from game number
9977     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9978
9979     if(appData.tourneyType == 0) {
9980         roundsPerCycle = (nPlayers - 1) | 1;
9981         pairingsPerRound = nPlayers / 2;
9982     } else if(appData.tourneyType > 0) {
9983         roundsPerCycle = nPlayers - appData.tourneyType;
9984         pairingsPerRound = appData.tourneyType;
9985     }
9986     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9987     gamesPerCycle = gamesPerRound * roundsPerCycle;
9988     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9989     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9990     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9991     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9992     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9993     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9994
9995     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9996     if(appData.roundSync) *syncInterval = gamesPerRound;
9997
9998     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9999
10000     if(appData.tourneyType == 0) {
10001         if(curPairing == (nPlayers-1)/2 ) {
10002             *whitePlayer = curRound;
10003             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10004         } else {
10005             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10006             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10007             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10008             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10009         }
10010     } else if(appData.tourneyType > 1) {
10011         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10012         *whitePlayer = curRound + appData.tourneyType;
10013     } else if(appData.tourneyType > 0) {
10014         *whitePlayer = curPairing;
10015         *blackPlayer = curRound + appData.tourneyType;
10016     }
10017
10018     // take care of white/black alternation per round. 
10019     // For cycles and games this is already taken care of by default, derived from matchGame!
10020     return curRound & 1;
10021 }
10022
10023 int
10024 NextTourneyGame (int nr, int *swapColors)
10025 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10026     char *p, *q;
10027     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10028     FILE *tf;
10029     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10030     tf = fopen(appData.tourneyFile, "r");
10031     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10032     ParseArgsFromFile(tf); fclose(tf);
10033     InitTimeControls(); // TC might be altered from tourney file
10034
10035     nPlayers = CountPlayers(appData.participants); // count participants
10036     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10037     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10038
10039     if(syncInterval) {
10040         p = q = appData.results;
10041         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10042         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10043             DisplayMessage(_("Waiting for other game(s)"),"");
10044             waitingForGame = TRUE;
10045             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10046             return 0;
10047         }
10048         waitingForGame = FALSE;
10049     }
10050
10051     if(appData.tourneyType < 0) {
10052         if(nr>=0 && !pairingReceived) {
10053             char buf[1<<16];
10054             if(pairing.pr == NoProc) {
10055                 if(!appData.pairingEngine[0]) {
10056                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10057                     return 0;
10058                 }
10059                 StartChessProgram(&pairing); // starts the pairing engine
10060             }
10061             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10062             SendToProgram(buf, &pairing);
10063             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10064             SendToProgram(buf, &pairing);
10065             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10066         }
10067         pairingReceived = 0;                              // ... so we continue here 
10068         *swapColors = 0;
10069         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10070         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10071         matchGame = 1; roundNr = nr / syncInterval + 1;
10072     }
10073
10074     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10075
10076     // redefine engines, engine dir, etc.
10077     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10078     if(first.pr == NoProc || nr < 0) {
10079       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10080       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10081     }
10082     if(second.pr == NoProc) {
10083       SwapEngines(1);
10084       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10085       SwapEngines(1);         // and make that valid for second engine by swapping
10086       InitEngine(&second, 1);
10087     }
10088     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10089     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10090     return 1;
10091 }
10092
10093 void
10094 NextMatchGame ()
10095 {   // performs game initialization that does not invoke engines, and then tries to start the game
10096     int res, firstWhite, swapColors = 0;
10097     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10098     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10099     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10100     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10101     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10102     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10103     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10104     Reset(FALSE, first.pr != NoProc);
10105     res = LoadGameOrPosition(matchGame); // setup game
10106     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10107     if(!res) return; // abort when bad game/pos file
10108     TwoMachinesEvent();
10109 }
10110
10111 void
10112 UserAdjudicationEvent (int result)
10113 {
10114     ChessMove gameResult = GameIsDrawn;
10115
10116     if( result > 0 ) {
10117         gameResult = WhiteWins;
10118     }
10119     else if( result < 0 ) {
10120         gameResult = BlackWins;
10121     }
10122
10123     if( gameMode == TwoMachinesPlay ) {
10124         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10125     }
10126 }
10127
10128
10129 // [HGM] save: calculate checksum of game to make games easily identifiable
10130 int
10131 StringCheckSum (char *s)
10132 {
10133         int i = 0;
10134         if(s==NULL) return 0;
10135         while(*s) i = i*259 + *s++;
10136         return i;
10137 }
10138
10139 int
10140 GameCheckSum ()
10141 {
10142         int i, sum=0;
10143         for(i=backwardMostMove; i<forwardMostMove; i++) {
10144                 sum += pvInfoList[i].depth;
10145                 sum += StringCheckSum(parseList[i]);
10146                 sum += StringCheckSum(commentList[i]);
10147                 sum *= 261;
10148         }
10149         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10150         return sum + StringCheckSum(commentList[i]);
10151 } // end of save patch
10152
10153 void
10154 GameEnds (ChessMove result, char *resultDetails, int whosays)
10155 {
10156     GameMode nextGameMode;
10157     int isIcsGame;
10158     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10159
10160     if(endingGame) return; /* [HGM] crash: forbid recursion */
10161     endingGame = 1;
10162     if(twoBoards) { // [HGM] dual: switch back to one board
10163         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10164         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10165     }
10166     if (appData.debugMode) {
10167       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10168               result, resultDetails ? resultDetails : "(null)", whosays);
10169     }
10170
10171     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10172
10173     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10174         /* If we are playing on ICS, the server decides when the
10175            game is over, but the engine can offer to draw, claim
10176            a draw, or resign.
10177          */
10178 #if ZIPPY
10179         if (appData.zippyPlay && first.initDone) {
10180             if (result == GameIsDrawn) {
10181                 /* In case draw still needs to be claimed */
10182                 SendToICS(ics_prefix);
10183                 SendToICS("draw\n");
10184             } else if (StrCaseStr(resultDetails, "resign")) {
10185                 SendToICS(ics_prefix);
10186                 SendToICS("resign\n");
10187             }
10188         }
10189 #endif
10190         endingGame = 0; /* [HGM] crash */
10191         return;
10192     }
10193
10194     /* If we're loading the game from a file, stop */
10195     if (whosays == GE_FILE) {
10196       (void) StopLoadGameTimer();
10197       gameFileFP = NULL;
10198     }
10199
10200     /* Cancel draw offers */
10201     first.offeredDraw = second.offeredDraw = 0;
10202
10203     /* If this is an ICS game, only ICS can really say it's done;
10204        if not, anyone can. */
10205     isIcsGame = (gameMode == IcsPlayingWhite ||
10206                  gameMode == IcsPlayingBlack ||
10207                  gameMode == IcsObserving    ||
10208                  gameMode == IcsExamining);
10209
10210     if (!isIcsGame || whosays == GE_ICS) {
10211         /* OK -- not an ICS game, or ICS said it was done */
10212         StopClocks();
10213         if (!isIcsGame && !appData.noChessProgram)
10214           SetUserThinkingEnables();
10215
10216         /* [HGM] if a machine claims the game end we verify this claim */
10217         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10218             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10219                 char claimer;
10220                 ChessMove trueResult = (ChessMove) -1;
10221
10222                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10223                                             first.twoMachinesColor[0] :
10224                                             second.twoMachinesColor[0] ;
10225
10226                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10227                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10228                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10229                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10230                 } else
10231                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10232                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10233                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10234                 } else
10235                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10236                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10237                 }
10238
10239                 // now verify win claims, but not in drop games, as we don't understand those yet
10240                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10241                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10242                     (result == WhiteWins && claimer == 'w' ||
10243                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10244                       if (appData.debugMode) {
10245                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10246                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10247                       }
10248                       if(result != trueResult) {
10249                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10250                               result = claimer == 'w' ? BlackWins : WhiteWins;
10251                               resultDetails = buf;
10252                       }
10253                 } else
10254                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10255                     && (forwardMostMove <= backwardMostMove ||
10256                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10257                         (claimer=='b')==(forwardMostMove&1))
10258                                                                                   ) {
10259                       /* [HGM] verify: draws that were not flagged are false claims */
10260                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10261                       result = claimer == 'w' ? BlackWins : WhiteWins;
10262                       resultDetails = buf;
10263                 }
10264                 /* (Claiming a loss is accepted no questions asked!) */
10265             }
10266             /* [HGM] bare: don't allow bare King to win */
10267             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10268                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10269                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10270                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10271                && result != GameIsDrawn)
10272             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10273                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10274                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10275                         if(p >= 0 && p <= (int)WhiteKing) k++;
10276                 }
10277                 if (appData.debugMode) {
10278                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10279                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10280                 }
10281                 if(k <= 1) {
10282                         result = GameIsDrawn;
10283                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10284                         resultDetails = buf;
10285                 }
10286             }
10287         }
10288
10289
10290         if(serverMoves != NULL && !loadFlag) { char c = '=';
10291             if(result==WhiteWins) c = '+';
10292             if(result==BlackWins) c = '-';
10293             if(resultDetails != NULL)
10294                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10295         }
10296         if (resultDetails != NULL) {
10297             gameInfo.result = result;
10298             gameInfo.resultDetails = StrSave(resultDetails);
10299
10300             /* display last move only if game was not loaded from file */
10301             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10302                 DisplayMove(currentMove - 1);
10303
10304             if (forwardMostMove != 0) {
10305                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10306                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10307                                                                 ) {
10308                     if (*appData.saveGameFile != NULLCHAR) {
10309                         SaveGameToFile(appData.saveGameFile, TRUE);
10310                     } else if (appData.autoSaveGames) {
10311                         AutoSaveGame();
10312                     }
10313                     if (*appData.savePositionFile != NULLCHAR) {
10314                         SavePositionToFile(appData.savePositionFile);
10315                     }
10316                 }
10317             }
10318
10319             /* Tell program how game ended in case it is learning */
10320             /* [HGM] Moved this to after saving the PGN, just in case */
10321             /* engine died and we got here through time loss. In that */
10322             /* case we will get a fatal error writing the pipe, which */
10323             /* would otherwise lose us the PGN.                       */
10324             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10325             /* output during GameEnds should never be fatal anymore   */
10326             if (gameMode == MachinePlaysWhite ||
10327                 gameMode == MachinePlaysBlack ||
10328                 gameMode == TwoMachinesPlay ||
10329                 gameMode == IcsPlayingWhite ||
10330                 gameMode == IcsPlayingBlack ||
10331                 gameMode == BeginningOfGame) {
10332                 char buf[MSG_SIZ];
10333                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10334                         resultDetails);
10335                 if (first.pr != NoProc) {
10336                     SendToProgram(buf, &first);
10337                 }
10338                 if (second.pr != NoProc &&
10339                     gameMode == TwoMachinesPlay) {
10340                     SendToProgram(buf, &second);
10341                 }
10342             }
10343         }
10344
10345         if (appData.icsActive) {
10346             if (appData.quietPlay &&
10347                 (gameMode == IcsPlayingWhite ||
10348                  gameMode == IcsPlayingBlack)) {
10349                 SendToICS(ics_prefix);
10350                 SendToICS("set shout 1\n");
10351             }
10352             nextGameMode = IcsIdle;
10353             ics_user_moved = FALSE;
10354             /* clean up premove.  It's ugly when the game has ended and the
10355              * premove highlights are still on the board.
10356              */
10357             if (gotPremove) {
10358               gotPremove = FALSE;
10359               ClearPremoveHighlights();
10360               DrawPosition(FALSE, boards[currentMove]);
10361             }
10362             if (whosays == GE_ICS) {
10363                 switch (result) {
10364                 case WhiteWins:
10365                     if (gameMode == IcsPlayingWhite)
10366                         PlayIcsWinSound();
10367                     else if(gameMode == IcsPlayingBlack)
10368                         PlayIcsLossSound();
10369                     break;
10370                 case BlackWins:
10371                     if (gameMode == IcsPlayingBlack)
10372                         PlayIcsWinSound();
10373                     else if(gameMode == IcsPlayingWhite)
10374                         PlayIcsLossSound();
10375                     break;
10376                 case GameIsDrawn:
10377                     PlayIcsDrawSound();
10378                     break;
10379                 default:
10380                     PlayIcsUnfinishedSound();
10381                 }
10382             }
10383         } else if (gameMode == EditGame ||
10384                    gameMode == PlayFromGameFile ||
10385                    gameMode == AnalyzeMode ||
10386                    gameMode == AnalyzeFile) {
10387             nextGameMode = gameMode;
10388         } else {
10389             nextGameMode = EndOfGame;
10390         }
10391         pausing = FALSE;
10392         ModeHighlight();
10393     } else {
10394         nextGameMode = gameMode;
10395     }
10396
10397     if (appData.noChessProgram) {
10398         gameMode = nextGameMode;
10399         ModeHighlight();
10400         endingGame = 0; /* [HGM] crash */
10401         return;
10402     }
10403
10404     if (first.reuse) {
10405         /* Put first chess program into idle state */
10406         if (first.pr != NoProc &&
10407             (gameMode == MachinePlaysWhite ||
10408              gameMode == MachinePlaysBlack ||
10409              gameMode == TwoMachinesPlay ||
10410              gameMode == IcsPlayingWhite ||
10411              gameMode == IcsPlayingBlack ||
10412              gameMode == BeginningOfGame)) {
10413             SendToProgram("force\n", &first);
10414             if (first.usePing) {
10415               char buf[MSG_SIZ];
10416               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10417               SendToProgram(buf, &first);
10418             }
10419         }
10420     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10421         /* Kill off first chess program */
10422         if (first.isr != NULL)
10423           RemoveInputSource(first.isr);
10424         first.isr = NULL;
10425
10426         if (first.pr != NoProc) {
10427             ExitAnalyzeMode();
10428             DoSleep( appData.delayBeforeQuit );
10429             SendToProgram("quit\n", &first);
10430             DoSleep( appData.delayAfterQuit );
10431             DestroyChildProcess(first.pr, first.useSigterm);
10432         }
10433         first.pr = NoProc;
10434     }
10435     if (second.reuse) {
10436         /* Put second chess program into idle state */
10437         if (second.pr != NoProc &&
10438             gameMode == TwoMachinesPlay) {
10439             SendToProgram("force\n", &second);
10440             if (second.usePing) {
10441               char buf[MSG_SIZ];
10442               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10443               SendToProgram(buf, &second);
10444             }
10445         }
10446     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10447         /* Kill off second chess program */
10448         if (second.isr != NULL)
10449           RemoveInputSource(second.isr);
10450         second.isr = NULL;
10451
10452         if (second.pr != NoProc) {
10453             DoSleep( appData.delayBeforeQuit );
10454             SendToProgram("quit\n", &second);
10455             DoSleep( appData.delayAfterQuit );
10456             DestroyChildProcess(second.pr, second.useSigterm);
10457         }
10458         second.pr = NoProc;
10459     }
10460
10461     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10462         char resChar = '=';
10463         switch (result) {
10464         case WhiteWins:
10465           resChar = '+';
10466           if (first.twoMachinesColor[0] == 'w') {
10467             first.matchWins++;
10468           } else {
10469             second.matchWins++;
10470           }
10471           break;
10472         case BlackWins:
10473           resChar = '-';
10474           if (first.twoMachinesColor[0] == 'b') {
10475             first.matchWins++;
10476           } else {
10477             second.matchWins++;
10478           }
10479           break;
10480         case GameUnfinished:
10481           resChar = ' ';
10482         default:
10483           break;
10484         }
10485
10486         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10487         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10488             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10489             ReserveGame(nextGame, resChar); // sets nextGame
10490             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10491             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10492         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10493
10494         if (nextGame <= appData.matchGames && !abortMatch) {
10495             gameMode = nextGameMode;
10496             matchGame = nextGame; // this will be overruled in tourney mode!
10497             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10498             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10499             endingGame = 0; /* [HGM] crash */
10500             return;
10501         } else {
10502             gameMode = nextGameMode;
10503             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10504                      first.tidy, second.tidy,
10505                      first.matchWins, second.matchWins,
10506                      appData.matchGames - (first.matchWins + second.matchWins));
10507             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10508             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10509             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10510             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10511                 first.twoMachinesColor = "black\n";
10512                 second.twoMachinesColor = "white\n";
10513             } else {
10514                 first.twoMachinesColor = "white\n";
10515                 second.twoMachinesColor = "black\n";
10516             }
10517         }
10518     }
10519     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10520         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10521       ExitAnalyzeMode();
10522     gameMode = nextGameMode;
10523     ModeHighlight();
10524     endingGame = 0;  /* [HGM] crash */
10525     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10526         if(matchMode == TRUE) { // match through command line: exit with or without popup
10527             if(ranking) {
10528                 ToNrEvent(forwardMostMove);
10529                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10530                 else ExitEvent(0);
10531             } else DisplayFatalError(buf, 0, 0);
10532         } else { // match through menu; just stop, with or without popup
10533             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10534             ModeHighlight();
10535             if(ranking){
10536                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10537             } else DisplayNote(buf);
10538       }
10539       if(ranking) free(ranking);
10540     }
10541 }
10542
10543 /* Assumes program was just initialized (initString sent).
10544    Leaves program in force mode. */
10545 void
10546 FeedMovesToProgram (ChessProgramState *cps, int upto)
10547 {
10548     int i;
10549
10550     if (appData.debugMode)
10551       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10552               startedFromSetupPosition ? "position and " : "",
10553               backwardMostMove, upto, cps->which);
10554     if(currentlyInitializedVariant != gameInfo.variant) {
10555       char buf[MSG_SIZ];
10556         // [HGM] variantswitch: make engine aware of new variant
10557         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10558                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10559         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10560         SendToProgram(buf, cps);
10561         currentlyInitializedVariant = gameInfo.variant;
10562     }
10563     SendToProgram("force\n", cps);
10564     if (startedFromSetupPosition) {
10565         SendBoard(cps, backwardMostMove);
10566     if (appData.debugMode) {
10567         fprintf(debugFP, "feedMoves\n");
10568     }
10569     }
10570     for (i = backwardMostMove; i < upto; i++) {
10571         SendMoveToProgram(i, cps);
10572     }
10573 }
10574
10575
10576 int
10577 ResurrectChessProgram ()
10578 {
10579      /* The chess program may have exited.
10580         If so, restart it and feed it all the moves made so far. */
10581     static int doInit = 0;
10582
10583     if (appData.noChessProgram) return 1;
10584
10585     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10586         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10587         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10588         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10589     } else {
10590         if (first.pr != NoProc) return 1;
10591         StartChessProgram(&first);
10592     }
10593     InitChessProgram(&first, FALSE);
10594     FeedMovesToProgram(&first, currentMove);
10595
10596     if (!first.sendTime) {
10597         /* can't tell gnuchess what its clock should read,
10598            so we bow to its notion. */
10599         ResetClocks();
10600         timeRemaining[0][currentMove] = whiteTimeRemaining;
10601         timeRemaining[1][currentMove] = blackTimeRemaining;
10602     }
10603
10604     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10605                 appData.icsEngineAnalyze) && first.analysisSupport) {
10606       SendToProgram("analyze\n", &first);
10607       first.analyzing = TRUE;
10608     }
10609     return 1;
10610 }
10611
10612 /*
10613  * Button procedures
10614  */
10615 void
10616 Reset (int redraw, int init)
10617 {
10618     int i;
10619
10620     if (appData.debugMode) {
10621         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10622                 redraw, init, gameMode);
10623     }
10624     CleanupTail(); // [HGM] vari: delete any stored variations
10625     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10626     pausing = pauseExamInvalid = FALSE;
10627     startedFromSetupPosition = blackPlaysFirst = FALSE;
10628     firstMove = TRUE;
10629     whiteFlag = blackFlag = FALSE;
10630     userOfferedDraw = FALSE;
10631     hintRequested = bookRequested = FALSE;
10632     first.maybeThinking = FALSE;
10633     second.maybeThinking = FALSE;
10634     first.bookSuspend = FALSE; // [HGM] book
10635     second.bookSuspend = FALSE;
10636     thinkOutput[0] = NULLCHAR;
10637     lastHint[0] = NULLCHAR;
10638     ClearGameInfo(&gameInfo);
10639     gameInfo.variant = StringToVariant(appData.variant);
10640     ics_user_moved = ics_clock_paused = FALSE;
10641     ics_getting_history = H_FALSE;
10642     ics_gamenum = -1;
10643     white_holding[0] = black_holding[0] = NULLCHAR;
10644     ClearProgramStats();
10645     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10646
10647     ResetFrontEnd();
10648     ClearHighlights();
10649     flipView = appData.flipView;
10650     ClearPremoveHighlights();
10651     gotPremove = FALSE;
10652     alarmSounded = FALSE;
10653
10654     GameEnds(EndOfFile, NULL, GE_PLAYER);
10655     if(appData.serverMovesName != NULL) {
10656         /* [HGM] prepare to make moves file for broadcasting */
10657         clock_t t = clock();
10658         if(serverMoves != NULL) fclose(serverMoves);
10659         serverMoves = fopen(appData.serverMovesName, "r");
10660         if(serverMoves != NULL) {
10661             fclose(serverMoves);
10662             /* delay 15 sec before overwriting, so all clients can see end */
10663             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10664         }
10665         serverMoves = fopen(appData.serverMovesName, "w");
10666     }
10667
10668     ExitAnalyzeMode();
10669     gameMode = BeginningOfGame;
10670     ModeHighlight();
10671     if(appData.icsActive) gameInfo.variant = VariantNormal;
10672     currentMove = forwardMostMove = backwardMostMove = 0;
10673     MarkTargetSquares(1);
10674     InitPosition(redraw);
10675     for (i = 0; i < MAX_MOVES; i++) {
10676         if (commentList[i] != NULL) {
10677             free(commentList[i]);
10678             commentList[i] = NULL;
10679         }
10680     }
10681     ResetClocks();
10682     timeRemaining[0][0] = whiteTimeRemaining;
10683     timeRemaining[1][0] = blackTimeRemaining;
10684
10685     if (first.pr == NoProc) {
10686         StartChessProgram(&first);
10687     }
10688     if (init) {
10689             InitChessProgram(&first, startedFromSetupPosition);
10690     }
10691     DisplayTitle("");
10692     DisplayMessage("", "");
10693     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10694     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10695 }
10696
10697 void
10698 AutoPlayGameLoop ()
10699 {
10700     for (;;) {
10701         if (!AutoPlayOneMove())
10702           return;
10703         if (matchMode || appData.timeDelay == 0)
10704           continue;
10705         if (appData.timeDelay < 0)
10706           return;
10707         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10708         break;
10709     }
10710 }
10711
10712
10713 int
10714 AutoPlayOneMove ()
10715 {
10716     int fromX, fromY, toX, toY;
10717
10718     if (appData.debugMode) {
10719       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10720     }
10721
10722     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10723       return FALSE;
10724
10725     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10726       pvInfoList[currentMove].depth = programStats.depth;
10727       pvInfoList[currentMove].score = programStats.score;
10728       pvInfoList[currentMove].time  = 0;
10729       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10730     }
10731
10732     if (currentMove >= forwardMostMove) {
10733       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10734 //      gameMode = EndOfGame;
10735 //      ModeHighlight();
10736
10737       /* [AS] Clear current move marker at the end of a game */
10738       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10739
10740       return FALSE;
10741     }
10742
10743     toX = moveList[currentMove][2] - AAA;
10744     toY = moveList[currentMove][3] - ONE;
10745
10746     if (moveList[currentMove][1] == '@') {
10747         if (appData.highlightLastMove) {
10748             SetHighlights(-1, -1, toX, toY);
10749         }
10750     } else {
10751         fromX = moveList[currentMove][0] - AAA;
10752         fromY = moveList[currentMove][1] - ONE;
10753
10754         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10755
10756         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10757
10758         if (appData.highlightLastMove) {
10759             SetHighlights(fromX, fromY, toX, toY);
10760         }
10761     }
10762     DisplayMove(currentMove);
10763     SendMoveToProgram(currentMove++, &first);
10764     DisplayBothClocks();
10765     DrawPosition(FALSE, boards[currentMove]);
10766     // [HGM] PV info: always display, routine tests if empty
10767     DisplayComment(currentMove - 1, commentList[currentMove]);
10768     return TRUE;
10769 }
10770
10771
10772 int
10773 LoadGameOneMove (ChessMove readAhead)
10774 {
10775     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10776     char promoChar = NULLCHAR;
10777     ChessMove moveType;
10778     char move[MSG_SIZ];
10779     char *p, *q;
10780
10781     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10782         gameMode != AnalyzeMode && gameMode != Training) {
10783         gameFileFP = NULL;
10784         return FALSE;
10785     }
10786
10787     yyboardindex = forwardMostMove;
10788     if (readAhead != EndOfFile) {
10789       moveType = readAhead;
10790     } else {
10791       if (gameFileFP == NULL)
10792           return FALSE;
10793       moveType = (ChessMove) Myylex();
10794     }
10795
10796     done = FALSE;
10797     switch (moveType) {
10798       case Comment:
10799         if (appData.debugMode)
10800           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10801         p = yy_text;
10802
10803         /* append the comment but don't display it */
10804         AppendComment(currentMove, p, FALSE);
10805         return TRUE;
10806
10807       case WhiteCapturesEnPassant:
10808       case BlackCapturesEnPassant:
10809       case WhitePromotion:
10810       case BlackPromotion:
10811       case WhiteNonPromotion:
10812       case BlackNonPromotion:
10813       case NormalMove:
10814       case WhiteKingSideCastle:
10815       case WhiteQueenSideCastle:
10816       case BlackKingSideCastle:
10817       case BlackQueenSideCastle:
10818       case WhiteKingSideCastleWild:
10819       case WhiteQueenSideCastleWild:
10820       case BlackKingSideCastleWild:
10821       case BlackQueenSideCastleWild:
10822       /* PUSH Fabien */
10823       case WhiteHSideCastleFR:
10824       case WhiteASideCastleFR:
10825       case BlackHSideCastleFR:
10826       case BlackASideCastleFR:
10827       /* POP Fabien */
10828         if (appData.debugMode)
10829           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10830         fromX = currentMoveString[0] - AAA;
10831         fromY = currentMoveString[1] - ONE;
10832         toX = currentMoveString[2] - AAA;
10833         toY = currentMoveString[3] - ONE;
10834         promoChar = currentMoveString[4];
10835         break;
10836
10837       case WhiteDrop:
10838       case BlackDrop:
10839         if (appData.debugMode)
10840           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10841         fromX = moveType == WhiteDrop ?
10842           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10843         (int) CharToPiece(ToLower(currentMoveString[0]));
10844         fromY = DROP_RANK;
10845         toX = currentMoveString[2] - AAA;
10846         toY = currentMoveString[3] - ONE;
10847         break;
10848
10849       case WhiteWins:
10850       case BlackWins:
10851       case GameIsDrawn:
10852       case GameUnfinished:
10853         if (appData.debugMode)
10854           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10855         p = strchr(yy_text, '{');
10856         if (p == NULL) p = strchr(yy_text, '(');
10857         if (p == NULL) {
10858             p = yy_text;
10859             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10860         } else {
10861             q = strchr(p, *p == '{' ? '}' : ')');
10862             if (q != NULL) *q = NULLCHAR;
10863             p++;
10864         }
10865         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10866         GameEnds(moveType, p, GE_FILE);
10867         done = TRUE;
10868         if (cmailMsgLoaded) {
10869             ClearHighlights();
10870             flipView = WhiteOnMove(currentMove);
10871             if (moveType == GameUnfinished) flipView = !flipView;
10872             if (appData.debugMode)
10873               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10874         }
10875         break;
10876
10877       case EndOfFile:
10878         if (appData.debugMode)
10879           fprintf(debugFP, "Parser hit end of file\n");
10880         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10881           case MT_NONE:
10882           case MT_CHECK:
10883             break;
10884           case MT_CHECKMATE:
10885           case MT_STAINMATE:
10886             if (WhiteOnMove(currentMove)) {
10887                 GameEnds(BlackWins, "Black mates", GE_FILE);
10888             } else {
10889                 GameEnds(WhiteWins, "White mates", GE_FILE);
10890             }
10891             break;
10892           case MT_STALEMATE:
10893             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10894             break;
10895         }
10896         done = TRUE;
10897         break;
10898
10899       case MoveNumberOne:
10900         if (lastLoadGameStart == GNUChessGame) {
10901             /* GNUChessGames have numbers, but they aren't move numbers */
10902             if (appData.debugMode)
10903               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10904                       yy_text, (int) moveType);
10905             return LoadGameOneMove(EndOfFile); /* tail recursion */
10906         }
10907         /* else fall thru */
10908
10909       case XBoardGame:
10910       case GNUChessGame:
10911       case PGNTag:
10912         /* Reached start of next game in file */
10913         if (appData.debugMode)
10914           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10915         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10916           case MT_NONE:
10917           case MT_CHECK:
10918             break;
10919           case MT_CHECKMATE:
10920           case MT_STAINMATE:
10921             if (WhiteOnMove(currentMove)) {
10922                 GameEnds(BlackWins, "Black mates", GE_FILE);
10923             } else {
10924                 GameEnds(WhiteWins, "White mates", GE_FILE);
10925             }
10926             break;
10927           case MT_STALEMATE:
10928             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10929             break;
10930         }
10931         done = TRUE;
10932         break;
10933
10934       case PositionDiagram:     /* should not happen; ignore */
10935       case ElapsedTime:         /* ignore */
10936       case NAG:                 /* ignore */
10937         if (appData.debugMode)
10938           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10939                   yy_text, (int) moveType);
10940         return LoadGameOneMove(EndOfFile); /* tail recursion */
10941
10942       case IllegalMove:
10943         if (appData.testLegality) {
10944             if (appData.debugMode)
10945               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10946             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10947                     (forwardMostMove / 2) + 1,
10948                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10949             DisplayError(move, 0);
10950             done = TRUE;
10951         } else {
10952             if (appData.debugMode)
10953               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10954                       yy_text, currentMoveString);
10955             fromX = currentMoveString[0] - AAA;
10956             fromY = currentMoveString[1] - ONE;
10957             toX = currentMoveString[2] - AAA;
10958             toY = currentMoveString[3] - ONE;
10959             promoChar = currentMoveString[4];
10960         }
10961         break;
10962
10963       case AmbiguousMove:
10964         if (appData.debugMode)
10965           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10966         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10967                 (forwardMostMove / 2) + 1,
10968                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10969         DisplayError(move, 0);
10970         done = TRUE;
10971         break;
10972
10973       default:
10974       case ImpossibleMove:
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10977         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10978                 (forwardMostMove / 2) + 1,
10979                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10980         DisplayError(move, 0);
10981         done = TRUE;
10982         break;
10983     }
10984
10985     if (done) {
10986         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10987             DrawPosition(FALSE, boards[currentMove]);
10988             DisplayBothClocks();
10989             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10990               DisplayComment(currentMove - 1, commentList[currentMove]);
10991         }
10992         (void) StopLoadGameTimer();
10993         gameFileFP = NULL;
10994         cmailOldMove = forwardMostMove;
10995         return FALSE;
10996     } else {
10997         /* currentMoveString is set as a side-effect of yylex */
10998
10999         thinkOutput[0] = NULLCHAR;
11000         MakeMove(fromX, fromY, toX, toY, promoChar);
11001         currentMove = forwardMostMove;
11002         return TRUE;
11003     }
11004 }
11005
11006 /* Load the nth game from the given file */
11007 int
11008 LoadGameFromFile (char *filename, int n, char *title, int useList)
11009 {
11010     FILE *f;
11011     char buf[MSG_SIZ];
11012
11013     if (strcmp(filename, "-") == 0) {
11014         f = stdin;
11015         title = "stdin";
11016     } else {
11017         f = fopen(filename, "rb");
11018         if (f == NULL) {
11019           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11020             DisplayError(buf, errno);
11021             return FALSE;
11022         }
11023     }
11024     if (fseek(f, 0, 0) == -1) {
11025         /* f is not seekable; probably a pipe */
11026         useList = FALSE;
11027     }
11028     if (useList && n == 0) {
11029         int error = GameListBuild(f);
11030         if (error) {
11031             DisplayError(_("Cannot build game list"), error);
11032         } else if (!ListEmpty(&gameList) &&
11033                    ((ListGame *) gameList.tailPred)->number > 1) {
11034             GameListPopUp(f, title);
11035             return TRUE;
11036         }
11037         GameListDestroy();
11038         n = 1;
11039     }
11040     if (n == 0) n = 1;
11041     return LoadGame(f, n, title, FALSE);
11042 }
11043
11044
11045 void
11046 MakeRegisteredMove ()
11047 {
11048     int fromX, fromY, toX, toY;
11049     char promoChar;
11050     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11051         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11052           case CMAIL_MOVE:
11053           case CMAIL_DRAW:
11054             if (appData.debugMode)
11055               fprintf(debugFP, "Restoring %s for game %d\n",
11056                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11057
11058             thinkOutput[0] = NULLCHAR;
11059             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11060             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11061             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11062             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11063             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11064             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11065             MakeMove(fromX, fromY, toX, toY, promoChar);
11066             ShowMove(fromX, fromY, toX, toY);
11067
11068             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11069               case MT_NONE:
11070               case MT_CHECK:
11071                 break;
11072
11073               case MT_CHECKMATE:
11074               case MT_STAINMATE:
11075                 if (WhiteOnMove(currentMove)) {
11076                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11077                 } else {
11078                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11079                 }
11080                 break;
11081
11082               case MT_STALEMATE:
11083                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11084                 break;
11085             }
11086
11087             break;
11088
11089           case CMAIL_RESIGN:
11090             if (WhiteOnMove(currentMove)) {
11091                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11092             } else {
11093                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11094             }
11095             break;
11096
11097           case CMAIL_ACCEPT:
11098             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11099             break;
11100
11101           default:
11102             break;
11103         }
11104     }
11105
11106     return;
11107 }
11108
11109 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11110 int
11111 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11112 {
11113     int retVal;
11114
11115     if (gameNumber > nCmailGames) {
11116         DisplayError(_("No more games in this message"), 0);
11117         return FALSE;
11118     }
11119     if (f == lastLoadGameFP) {
11120         int offset = gameNumber - lastLoadGameNumber;
11121         if (offset == 0) {
11122             cmailMsg[0] = NULLCHAR;
11123             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11124                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11125                 nCmailMovesRegistered--;
11126             }
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11128             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11129                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11130             }
11131         } else {
11132             if (! RegisterMove()) return FALSE;
11133         }
11134     }
11135
11136     retVal = LoadGame(f, gameNumber, title, useList);
11137
11138     /* Make move registered during previous look at this game, if any */
11139     MakeRegisteredMove();
11140
11141     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11142         commentList[currentMove]
11143           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11144         DisplayComment(currentMove - 1, commentList[currentMove]);
11145     }
11146
11147     return retVal;
11148 }
11149
11150 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11151 int
11152 ReloadGame (int offset)
11153 {
11154     int gameNumber = lastLoadGameNumber + offset;
11155     if (lastLoadGameFP == NULL) {
11156         DisplayError(_("No game has been loaded yet"), 0);
11157         return FALSE;
11158     }
11159     if (gameNumber <= 0) {
11160         DisplayError(_("Can't back up any further"), 0);
11161         return FALSE;
11162     }
11163     if (cmailMsgLoaded) {
11164         return CmailLoadGame(lastLoadGameFP, gameNumber,
11165                              lastLoadGameTitle, lastLoadGameUseList);
11166     } else {
11167         return LoadGame(lastLoadGameFP, gameNumber,
11168                         lastLoadGameTitle, lastLoadGameUseList);
11169     }
11170 }
11171
11172 int keys[EmptySquare+1];
11173
11174 int
11175 PositionMatches (Board b1, Board b2)
11176 {
11177     int r, f, sum=0;
11178     switch(appData.searchMode) {
11179         case 1: return CompareWithRights(b1, b2);
11180         case 2:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11183             }
11184             return TRUE;
11185         case 3:
11186             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11187               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11188                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11189             }
11190             return sum==0;
11191         case 4:
11192             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11193                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11194             }
11195             return sum==0;
11196     }
11197     return TRUE;
11198 }
11199
11200 #define Q_PROMO  4
11201 #define Q_EP     3
11202 #define Q_BCASTL 2
11203 #define Q_WCASTL 1
11204
11205 int pieceList[256], quickBoard[256];
11206 ChessSquare pieceType[256] = { EmptySquare };
11207 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11208 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11209 int soughtTotal, turn;
11210 Boolean epOK, flipSearch;
11211
11212 typedef struct {
11213     unsigned char piece, to;
11214 } Move;
11215
11216 #define DSIZE (250000)
11217
11218 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11219 Move *moveDatabase = initialSpace;
11220 unsigned int movePtr, dataSize = DSIZE;
11221
11222 int
11223 MakePieceList (Board board, int *counts)
11224 {
11225     int r, f, n=Q_PROMO, total=0;
11226     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11227     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11228         int sq = f + (r<<4);
11229         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11230             quickBoard[sq] = ++n;
11231             pieceList[n] = sq;
11232             pieceType[n] = board[r][f];
11233             counts[board[r][f]]++;
11234             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11235             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11236             total++;
11237         }
11238     }
11239     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11240     return total;
11241 }
11242
11243 void
11244 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11245 {
11246     int sq = fromX + (fromY<<4);
11247     int piece = quickBoard[sq];
11248     quickBoard[sq] = 0;
11249     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11250     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11251         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11252         moveDatabase[movePtr++].piece = Q_WCASTL;
11253         quickBoard[sq] = piece;
11254         piece = quickBoard[from]; quickBoard[from] = 0;
11255         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11256     } else
11257     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11258         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11259         moveDatabase[movePtr++].piece = Q_BCASTL;
11260         quickBoard[sq] = piece;
11261         piece = quickBoard[from]; quickBoard[from] = 0;
11262         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11263     } else
11264     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11265         quickBoard[(fromY<<4)+toX] = 0;
11266         moveDatabase[movePtr].piece = Q_EP;
11267         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11268         moveDatabase[movePtr].to = sq;
11269     } else
11270     if(promoPiece != pieceType[piece]) {
11271         moveDatabase[movePtr++].piece = Q_PROMO;
11272         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11273     }
11274     moveDatabase[movePtr].piece = piece;
11275     quickBoard[sq] = piece;
11276     movePtr++;
11277 }
11278
11279 int
11280 PackGame (Board board)
11281 {
11282     Move *newSpace = NULL;
11283     moveDatabase[movePtr].piece = 0; // terminate previous game
11284     if(movePtr > dataSize) {
11285         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11286         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11287         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11288         if(newSpace) {
11289             int i;
11290             Move *p = moveDatabase, *q = newSpace;
11291             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11292             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11293             moveDatabase = newSpace;
11294         } else { // calloc failed, we must be out of memory. Too bad...
11295             dataSize = 0; // prevent calloc events for all subsequent games
11296             return 0;     // and signal this one isn't cached
11297         }
11298     }
11299     movePtr++;
11300     MakePieceList(board, counts);
11301     return movePtr;
11302 }
11303
11304 int
11305 QuickCompare (Board board, int *minCounts, int *maxCounts)
11306 {   // compare according to search mode
11307     int r, f;
11308     switch(appData.searchMode)
11309     {
11310       case 1: // exact position match
11311         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11313             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11314         }
11315         break;
11316       case 2: // can have extra material on empty squares
11317         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11318             if(board[r][f] == EmptySquare) continue;
11319             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11320         }
11321         break;
11322       case 3: // material with exact Pawn structure
11323         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11324             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11325             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11326         } // fall through to material comparison
11327       case 4: // exact material
11328         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11329         break;
11330       case 6: // material range with given imbalance
11331         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11332         // fall through to range comparison
11333       case 5: // material range
11334         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11335     }
11336     return TRUE;
11337 }
11338
11339 int
11340 QuickScan (Board board, Move *move)
11341 {   // reconstruct game,and compare all positions in it
11342     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11343     do {
11344         int piece = move->piece;
11345         int to = move->to, from = pieceList[piece];
11346         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11347           if(!piece) return -1;
11348           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11349             piece = (++move)->piece;
11350             from = pieceList[piece];
11351             counts[pieceType[piece]]--;
11352             pieceType[piece] = (ChessSquare) move->to;
11353             counts[move->to]++;
11354           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11355             counts[pieceType[quickBoard[to]]]--;
11356             quickBoard[to] = 0; total--;
11357             move++;
11358             continue;
11359           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11360             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11361             from  = pieceList[piece]; // so this must be King
11362             quickBoard[from] = 0;
11363             quickBoard[to] = piece;
11364             pieceList[piece] = to;
11365             move++;
11366             continue;
11367           }
11368         }
11369         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11370         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11371         quickBoard[from] = 0;
11372         quickBoard[to] = piece;
11373         pieceList[piece] = to;
11374         cnt++; turn ^= 3;
11375         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11376            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11377            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11378                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11379           ) {
11380             static int lastCounts[EmptySquare+1];
11381             int i;
11382             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11383             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11384         } else stretch = 0;
11385         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11386         move++;
11387     } while(1);
11388 }
11389
11390 void
11391 InitSearch ()
11392 {
11393     int r, f;
11394     flipSearch = FALSE;
11395     CopyBoard(soughtBoard, boards[currentMove]);
11396     soughtTotal = MakePieceList(soughtBoard, maxSought);
11397     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11398     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11399     CopyBoard(reverseBoard, boards[currentMove]);
11400     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11401         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11402         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11403         reverseBoard[r][f] = piece;
11404     }
11405     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11406     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11407     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11408                  || (boards[currentMove][CASTLING][2] == NoRights || 
11409                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11410                  && (boards[currentMove][CASTLING][5] == NoRights || 
11411                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11412       ) {
11413         flipSearch = TRUE;
11414         CopyBoard(flipBoard, soughtBoard);
11415         CopyBoard(rotateBoard, reverseBoard);
11416         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11417             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11418             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11419         }
11420     }
11421     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11422     if(appData.searchMode >= 5) {
11423         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11424         MakePieceList(soughtBoard, minSought);
11425         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11426     }
11427     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11428         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11429 }
11430
11431 GameInfo dummyInfo;
11432
11433 int
11434 GameContainsPosition (FILE *f, ListGame *lg)
11435 {
11436     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11437     int fromX, fromY, toX, toY;
11438     char promoChar;
11439     static int initDone=FALSE;
11440
11441     // weed out games based on numerical tag comparison
11442     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11443     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11444     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11445     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11446     if(!initDone) {
11447         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11448         initDone = TRUE;
11449     }
11450     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11451     else CopyBoard(boards[scratch], initialPosition); // default start position
11452     if(lg->moves) {
11453         turn = btm + 1;
11454         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11455         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11456     }
11457     if(btm) plyNr++;
11458     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11459     fseek(f, lg->offset, 0);
11460     yynewfile(f);
11461     while(1) {
11462         yyboardindex = scratch;
11463         quickFlag = plyNr+1;
11464         next = Myylex();
11465         quickFlag = 0;
11466         switch(next) {
11467             case PGNTag:
11468                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11469             default:
11470                 continue;
11471
11472             case XBoardGame:
11473             case GNUChessGame:
11474                 if(plyNr) return -1; // after we have seen moves, this is for new game
11475               continue;
11476
11477             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11478             case ImpossibleMove:
11479             case WhiteWins: // game ends here with these four
11480             case BlackWins:
11481             case GameIsDrawn:
11482             case GameUnfinished:
11483                 return -1;
11484
11485             case IllegalMove:
11486                 if(appData.testLegality) return -1;
11487             case WhiteCapturesEnPassant:
11488             case BlackCapturesEnPassant:
11489             case WhitePromotion:
11490             case BlackPromotion:
11491             case WhiteNonPromotion:
11492             case BlackNonPromotion:
11493             case NormalMove:
11494             case WhiteKingSideCastle:
11495             case WhiteQueenSideCastle:
11496             case BlackKingSideCastle:
11497             case BlackQueenSideCastle:
11498             case WhiteKingSideCastleWild:
11499             case WhiteQueenSideCastleWild:
11500             case BlackKingSideCastleWild:
11501             case BlackQueenSideCastleWild:
11502             case WhiteHSideCastleFR:
11503             case WhiteASideCastleFR:
11504             case BlackHSideCastleFR:
11505             case BlackASideCastleFR:
11506                 fromX = currentMoveString[0] - AAA;
11507                 fromY = currentMoveString[1] - ONE;
11508                 toX = currentMoveString[2] - AAA;
11509                 toY = currentMoveString[3] - ONE;
11510                 promoChar = currentMoveString[4];
11511                 break;
11512             case WhiteDrop:
11513             case BlackDrop:
11514                 fromX = next == WhiteDrop ?
11515                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11516                   (int) CharToPiece(ToLower(currentMoveString[0]));
11517                 fromY = DROP_RANK;
11518                 toX = currentMoveString[2] - AAA;
11519                 toY = currentMoveString[3] - ONE;
11520                 promoChar = 0;
11521                 break;
11522         }
11523         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11524         plyNr++;
11525         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11526         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11527         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11528         if(appData.findMirror) {
11529             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11530             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11531         }
11532     }
11533 }
11534
11535 /* Load the nth game from open file f */
11536 int
11537 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11538 {
11539     ChessMove cm;
11540     char buf[MSG_SIZ];
11541     int gn = gameNumber;
11542     ListGame *lg = NULL;
11543     int numPGNTags = 0;
11544     int err, pos = -1;
11545     GameMode oldGameMode;
11546     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11547
11548     if (appData.debugMode)
11549         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11550
11551     if (gameMode == Training )
11552         SetTrainingModeOff();
11553
11554     oldGameMode = gameMode;
11555     if (gameMode != BeginningOfGame) {
11556       Reset(FALSE, TRUE);
11557     }
11558
11559     gameFileFP = f;
11560     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11561         fclose(lastLoadGameFP);
11562     }
11563
11564     if (useList) {
11565         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11566
11567         if (lg) {
11568             fseek(f, lg->offset, 0);
11569             GameListHighlight(gameNumber);
11570             pos = lg->position;
11571             gn = 1;
11572         }
11573         else {
11574             DisplayError(_("Game number out of range"), 0);
11575             return FALSE;
11576         }
11577     } else {
11578         GameListDestroy();
11579         if (fseek(f, 0, 0) == -1) {
11580             if (f == lastLoadGameFP ?
11581                 gameNumber == lastLoadGameNumber + 1 :
11582                 gameNumber == 1) {
11583                 gn = 1;
11584             } else {
11585                 DisplayError(_("Can't seek on game file"), 0);
11586                 return FALSE;
11587             }
11588         }
11589     }
11590     lastLoadGameFP = f;
11591     lastLoadGameNumber = gameNumber;
11592     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11593     lastLoadGameUseList = useList;
11594
11595     yynewfile(f);
11596
11597     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11598       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11599                 lg->gameInfo.black);
11600             DisplayTitle(buf);
11601     } else if (*title != NULLCHAR) {
11602         if (gameNumber > 1) {
11603           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11604             DisplayTitle(buf);
11605         } else {
11606             DisplayTitle(title);
11607         }
11608     }
11609
11610     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11611         gameMode = PlayFromGameFile;
11612         ModeHighlight();
11613     }
11614
11615     currentMove = forwardMostMove = backwardMostMove = 0;
11616     CopyBoard(boards[0], initialPosition);
11617     StopClocks();
11618
11619     /*
11620      * Skip the first gn-1 games in the file.
11621      * Also skip over anything that precedes an identifiable
11622      * start of game marker, to avoid being confused by
11623      * garbage at the start of the file.  Currently
11624      * recognized start of game markers are the move number "1",
11625      * the pattern "gnuchess .* game", the pattern
11626      * "^[#;%] [^ ]* game file", and a PGN tag block.
11627      * A game that starts with one of the latter two patterns
11628      * will also have a move number 1, possibly
11629      * following a position diagram.
11630      * 5-4-02: Let's try being more lenient and allowing a game to
11631      * start with an unnumbered move.  Does that break anything?
11632      */
11633     cm = lastLoadGameStart = EndOfFile;
11634     while (gn > 0) {
11635         yyboardindex = forwardMostMove;
11636         cm = (ChessMove) Myylex();
11637         switch (cm) {
11638           case EndOfFile:
11639             if (cmailMsgLoaded) {
11640                 nCmailGames = CMAIL_MAX_GAMES - gn;
11641             } else {
11642                 Reset(TRUE, TRUE);
11643                 DisplayError(_("Game not found in file"), 0);
11644             }
11645             return FALSE;
11646
11647           case GNUChessGame:
11648           case XBoardGame:
11649             gn--;
11650             lastLoadGameStart = cm;
11651             break;
11652
11653           case MoveNumberOne:
11654             switch (lastLoadGameStart) {
11655               case GNUChessGame:
11656               case XBoardGame:
11657               case PGNTag:
11658                 break;
11659               case MoveNumberOne:
11660               case EndOfFile:
11661                 gn--;           /* count this game */
11662                 lastLoadGameStart = cm;
11663                 break;
11664               default:
11665                 /* impossible */
11666                 break;
11667             }
11668             break;
11669
11670           case PGNTag:
11671             switch (lastLoadGameStart) {
11672               case GNUChessGame:
11673               case PGNTag:
11674               case MoveNumberOne:
11675               case EndOfFile:
11676                 gn--;           /* count this game */
11677                 lastLoadGameStart = cm;
11678                 break;
11679               case XBoardGame:
11680                 lastLoadGameStart = cm; /* game counted already */
11681                 break;
11682               default:
11683                 /* impossible */
11684                 break;
11685             }
11686             if (gn > 0) {
11687                 do {
11688                     yyboardindex = forwardMostMove;
11689                     cm = (ChessMove) Myylex();
11690                 } while (cm == PGNTag || cm == Comment);
11691             }
11692             break;
11693
11694           case WhiteWins:
11695           case BlackWins:
11696           case GameIsDrawn:
11697             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11698                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11699                     != CMAIL_OLD_RESULT) {
11700                     nCmailResults ++ ;
11701                     cmailResult[  CMAIL_MAX_GAMES
11702                                 - gn - 1] = CMAIL_OLD_RESULT;
11703                 }
11704             }
11705             break;
11706
11707           case NormalMove:
11708             /* Only a NormalMove can be at the start of a game
11709              * without a position diagram. */
11710             if (lastLoadGameStart == EndOfFile ) {
11711               gn--;
11712               lastLoadGameStart = MoveNumberOne;
11713             }
11714             break;
11715
11716           default:
11717             break;
11718         }
11719     }
11720
11721     if (appData.debugMode)
11722       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11723
11724     if (cm == XBoardGame) {
11725         /* Skip any header junk before position diagram and/or move 1 */
11726         for (;;) {
11727             yyboardindex = forwardMostMove;
11728             cm = (ChessMove) Myylex();
11729
11730             if (cm == EndOfFile ||
11731                 cm == GNUChessGame || cm == XBoardGame) {
11732                 /* Empty game; pretend end-of-file and handle later */
11733                 cm = EndOfFile;
11734                 break;
11735             }
11736
11737             if (cm == MoveNumberOne || cm == PositionDiagram ||
11738                 cm == PGNTag || cm == Comment)
11739               break;
11740         }
11741     } else if (cm == GNUChessGame) {
11742         if (gameInfo.event != NULL) {
11743             free(gameInfo.event);
11744         }
11745         gameInfo.event = StrSave(yy_text);
11746     }
11747
11748     startedFromSetupPosition = FALSE;
11749     while (cm == PGNTag) {
11750         if (appData.debugMode)
11751           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11752         err = ParsePGNTag(yy_text, &gameInfo);
11753         if (!err) numPGNTags++;
11754
11755         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11756         if(gameInfo.variant != oldVariant) {
11757             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11758             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11759             InitPosition(TRUE);
11760             oldVariant = gameInfo.variant;
11761             if (appData.debugMode)
11762               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11763         }
11764
11765
11766         if (gameInfo.fen != NULL) {
11767           Board initial_position;
11768           startedFromSetupPosition = TRUE;
11769           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11770             Reset(TRUE, TRUE);
11771             DisplayError(_("Bad FEN position in file"), 0);
11772             return FALSE;
11773           }
11774           CopyBoard(boards[0], initial_position);
11775           if (blackPlaysFirst) {
11776             currentMove = forwardMostMove = backwardMostMove = 1;
11777             CopyBoard(boards[1], initial_position);
11778             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11779             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11780             timeRemaining[0][1] = whiteTimeRemaining;
11781             timeRemaining[1][1] = blackTimeRemaining;
11782             if (commentList[0] != NULL) {
11783               commentList[1] = commentList[0];
11784               commentList[0] = NULL;
11785             }
11786           } else {
11787             currentMove = forwardMostMove = backwardMostMove = 0;
11788           }
11789           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11790           {   int i;
11791               initialRulePlies = FENrulePlies;
11792               for( i=0; i< nrCastlingRights; i++ )
11793                   initialRights[i] = initial_position[CASTLING][i];
11794           }
11795           yyboardindex = forwardMostMove;
11796           free(gameInfo.fen);
11797           gameInfo.fen = NULL;
11798         }
11799
11800         yyboardindex = forwardMostMove;
11801         cm = (ChessMove) Myylex();
11802
11803         /* Handle comments interspersed among the tags */
11804         while (cm == Comment) {
11805             char *p;
11806             if (appData.debugMode)
11807               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11808             p = yy_text;
11809             AppendComment(currentMove, p, FALSE);
11810             yyboardindex = forwardMostMove;
11811             cm = (ChessMove) Myylex();
11812         }
11813     }
11814
11815     /* don't rely on existence of Event tag since if game was
11816      * pasted from clipboard the Event tag may not exist
11817      */
11818     if (numPGNTags > 0){
11819         char *tags;
11820         if (gameInfo.variant == VariantNormal) {
11821           VariantClass v = StringToVariant(gameInfo.event);
11822           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11823           if(v < VariantShogi) gameInfo.variant = v;
11824         }
11825         if (!matchMode) {
11826           if( appData.autoDisplayTags ) {
11827             tags = PGNTags(&gameInfo);
11828             TagsPopUp(tags, CmailMsg());
11829             free(tags);
11830           }
11831         }
11832     } else {
11833         /* Make something up, but don't display it now */
11834         SetGameInfo();
11835         TagsPopDown();
11836     }
11837
11838     if (cm == PositionDiagram) {
11839         int i, j;
11840         char *p;
11841         Board initial_position;
11842
11843         if (appData.debugMode)
11844           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11845
11846         if (!startedFromSetupPosition) {
11847             p = yy_text;
11848             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11849               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11850                 switch (*p) {
11851                   case '{':
11852                   case '[':
11853                   case '-':
11854                   case ' ':
11855                   case '\t':
11856                   case '\n':
11857                   case '\r':
11858                     break;
11859                   default:
11860                     initial_position[i][j++] = CharToPiece(*p);
11861                     break;
11862                 }
11863             while (*p == ' ' || *p == '\t' ||
11864                    *p == '\n' || *p == '\r') p++;
11865
11866             if (strncmp(p, "black", strlen("black"))==0)
11867               blackPlaysFirst = TRUE;
11868             else
11869               blackPlaysFirst = FALSE;
11870             startedFromSetupPosition = TRUE;
11871
11872             CopyBoard(boards[0], initial_position);
11873             if (blackPlaysFirst) {
11874                 currentMove = forwardMostMove = backwardMostMove = 1;
11875                 CopyBoard(boards[1], initial_position);
11876                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11877                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11878                 timeRemaining[0][1] = whiteTimeRemaining;
11879                 timeRemaining[1][1] = blackTimeRemaining;
11880                 if (commentList[0] != NULL) {
11881                     commentList[1] = commentList[0];
11882                     commentList[0] = NULL;
11883                 }
11884             } else {
11885                 currentMove = forwardMostMove = backwardMostMove = 0;
11886             }
11887         }
11888         yyboardindex = forwardMostMove;
11889         cm = (ChessMove) Myylex();
11890     }
11891
11892     if (first.pr == NoProc) {
11893         StartChessProgram(&first);
11894     }
11895     InitChessProgram(&first, FALSE);
11896     SendToProgram("force\n", &first);
11897     if (startedFromSetupPosition) {
11898         SendBoard(&first, forwardMostMove);
11899     if (appData.debugMode) {
11900         fprintf(debugFP, "Load Game\n");
11901     }
11902         DisplayBothClocks();
11903     }
11904
11905     /* [HGM] server: flag to write setup moves in broadcast file as one */
11906     loadFlag = appData.suppressLoadMoves;
11907
11908     while (cm == Comment) {
11909         char *p;
11910         if (appData.debugMode)
11911           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11912         p = yy_text;
11913         AppendComment(currentMove, p, FALSE);
11914         yyboardindex = forwardMostMove;
11915         cm = (ChessMove) Myylex();
11916     }
11917
11918     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11919         cm == WhiteWins || cm == BlackWins ||
11920         cm == GameIsDrawn || cm == GameUnfinished) {
11921         DisplayMessage("", _("No moves in game"));
11922         if (cmailMsgLoaded) {
11923             if (appData.debugMode)
11924               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11925             ClearHighlights();
11926             flipView = FALSE;
11927         }
11928         DrawPosition(FALSE, boards[currentMove]);
11929         DisplayBothClocks();
11930         gameMode = EditGame;
11931         ModeHighlight();
11932         gameFileFP = NULL;
11933         cmailOldMove = 0;
11934         return TRUE;
11935     }
11936
11937     // [HGM] PV info: routine tests if comment empty
11938     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11939         DisplayComment(currentMove - 1, commentList[currentMove]);
11940     }
11941     if (!matchMode && appData.timeDelay != 0)
11942       DrawPosition(FALSE, boards[currentMove]);
11943
11944     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11945       programStats.ok_to_send = 1;
11946     }
11947
11948     /* if the first token after the PGN tags is a move
11949      * and not move number 1, retrieve it from the parser
11950      */
11951     if (cm != MoveNumberOne)
11952         LoadGameOneMove(cm);
11953
11954     /* load the remaining moves from the file */
11955     while (LoadGameOneMove(EndOfFile)) {
11956       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11957       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11958     }
11959
11960     /* rewind to the start of the game */
11961     currentMove = backwardMostMove;
11962
11963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11964
11965     if (oldGameMode == AnalyzeFile ||
11966         oldGameMode == AnalyzeMode) {
11967       AnalyzeFileEvent();
11968     }
11969
11970     if (!matchMode && pos >= 0) {
11971         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11972     } else
11973     if (matchMode || appData.timeDelay == 0) {
11974       ToEndEvent();
11975     } else if (appData.timeDelay > 0) {
11976       AutoPlayGameLoop();
11977     }
11978
11979     if (appData.debugMode)
11980         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11981
11982     loadFlag = 0; /* [HGM] true game starts */
11983     return TRUE;
11984 }
11985
11986 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11987 int
11988 ReloadPosition (int offset)
11989 {
11990     int positionNumber = lastLoadPositionNumber + offset;
11991     if (lastLoadPositionFP == NULL) {
11992         DisplayError(_("No position has been loaded yet"), 0);
11993         return FALSE;
11994     }
11995     if (positionNumber <= 0) {
11996         DisplayError(_("Can't back up any further"), 0);
11997         return FALSE;
11998     }
11999     return LoadPosition(lastLoadPositionFP, positionNumber,
12000                         lastLoadPositionTitle);
12001 }
12002
12003 /* Load the nth position from the given file */
12004 int
12005 LoadPositionFromFile (char *filename, int n, char *title)
12006 {
12007     FILE *f;
12008     char buf[MSG_SIZ];
12009
12010     if (strcmp(filename, "-") == 0) {
12011         return LoadPosition(stdin, n, "stdin");
12012     } else {
12013         f = fopen(filename, "rb");
12014         if (f == NULL) {
12015             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12016             DisplayError(buf, errno);
12017             return FALSE;
12018         } else {
12019             return LoadPosition(f, n, title);
12020         }
12021     }
12022 }
12023
12024 /* Load the nth position from the given open file, and close it */
12025 int
12026 LoadPosition (FILE *f, int positionNumber, char *title)
12027 {
12028     char *p, line[MSG_SIZ];
12029     Board initial_position;
12030     int i, j, fenMode, pn;
12031
12032     if (gameMode == Training )
12033         SetTrainingModeOff();
12034
12035     if (gameMode != BeginningOfGame) {
12036         Reset(FALSE, TRUE);
12037     }
12038     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12039         fclose(lastLoadPositionFP);
12040     }
12041     if (positionNumber == 0) positionNumber = 1;
12042     lastLoadPositionFP = f;
12043     lastLoadPositionNumber = positionNumber;
12044     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12045     if (first.pr == NoProc && !appData.noChessProgram) {
12046       StartChessProgram(&first);
12047       InitChessProgram(&first, FALSE);
12048     }
12049     pn = positionNumber;
12050     if (positionNumber < 0) {
12051         /* Negative position number means to seek to that byte offset */
12052         if (fseek(f, -positionNumber, 0) == -1) {
12053             DisplayError(_("Can't seek on position file"), 0);
12054             return FALSE;
12055         };
12056         pn = 1;
12057     } else {
12058         if (fseek(f, 0, 0) == -1) {
12059             if (f == lastLoadPositionFP ?
12060                 positionNumber == lastLoadPositionNumber + 1 :
12061                 positionNumber == 1) {
12062                 pn = 1;
12063             } else {
12064                 DisplayError(_("Can't seek on position file"), 0);
12065                 return FALSE;
12066             }
12067         }
12068     }
12069     /* See if this file is FEN or old-style xboard */
12070     if (fgets(line, MSG_SIZ, f) == NULL) {
12071         DisplayError(_("Position not found in file"), 0);
12072         return FALSE;
12073     }
12074     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12075     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12076
12077     if (pn >= 2) {
12078         if (fenMode || line[0] == '#') pn--;
12079         while (pn > 0) {
12080             /* skip positions before number pn */
12081             if (fgets(line, MSG_SIZ, f) == NULL) {
12082                 Reset(TRUE, TRUE);
12083                 DisplayError(_("Position not found in file"), 0);
12084                 return FALSE;
12085             }
12086             if (fenMode || line[0] == '#') pn--;
12087         }
12088     }
12089
12090     if (fenMode) {
12091         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12092             DisplayError(_("Bad FEN position in file"), 0);
12093             return FALSE;
12094         }
12095     } else {
12096         (void) fgets(line, MSG_SIZ, f);
12097         (void) fgets(line, MSG_SIZ, f);
12098
12099         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12100             (void) fgets(line, MSG_SIZ, f);
12101             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12102                 if (*p == ' ')
12103                   continue;
12104                 initial_position[i][j++] = CharToPiece(*p);
12105             }
12106         }
12107
12108         blackPlaysFirst = FALSE;
12109         if (!feof(f)) {
12110             (void) fgets(line, MSG_SIZ, f);
12111             if (strncmp(line, "black", strlen("black"))==0)
12112               blackPlaysFirst = TRUE;
12113         }
12114     }
12115     startedFromSetupPosition = TRUE;
12116
12117     CopyBoard(boards[0], initial_position);
12118     if (blackPlaysFirst) {
12119         currentMove = forwardMostMove = backwardMostMove = 1;
12120         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12121         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12122         CopyBoard(boards[1], initial_position);
12123         DisplayMessage("", _("Black to play"));
12124     } else {
12125         currentMove = forwardMostMove = backwardMostMove = 0;
12126         DisplayMessage("", _("White to play"));
12127     }
12128     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12129     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12130         SendToProgram("force\n", &first);
12131         SendBoard(&first, forwardMostMove);
12132     }
12133     if (appData.debugMode) {
12134 int i, j;
12135   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12136   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12137         fprintf(debugFP, "Load Position\n");
12138     }
12139
12140     if (positionNumber > 1) {
12141       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12142         DisplayTitle(line);
12143     } else {
12144         DisplayTitle(title);
12145     }
12146     gameMode = EditGame;
12147     ModeHighlight();
12148     ResetClocks();
12149     timeRemaining[0][1] = whiteTimeRemaining;
12150     timeRemaining[1][1] = blackTimeRemaining;
12151     DrawPosition(FALSE, boards[currentMove]);
12152
12153     return TRUE;
12154 }
12155
12156
12157 void
12158 CopyPlayerNameIntoFileName (char **dest, char *src)
12159 {
12160     while (*src != NULLCHAR && *src != ',') {
12161         if (*src == ' ') {
12162             *(*dest)++ = '_';
12163             src++;
12164         } else {
12165             *(*dest)++ = *src++;
12166         }
12167     }
12168 }
12169
12170 char *
12171 DefaultFileName (char *ext)
12172 {
12173     static char def[MSG_SIZ];
12174     char *p;
12175
12176     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12177         p = def;
12178         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12179         *p++ = '-';
12180         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12181         *p++ = '.';
12182         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12183     } else {
12184         def[0] = NULLCHAR;
12185     }
12186     return def;
12187 }
12188
12189 /* Save the current game to the given file */
12190 int
12191 SaveGameToFile (char *filename, int append)
12192 {
12193     FILE *f;
12194     char buf[MSG_SIZ];
12195     int result, i, t,tot=0;
12196
12197     if (strcmp(filename, "-") == 0) {
12198         return SaveGame(stdout, 0, NULL);
12199     } else {
12200         for(i=0; i<10; i++) { // upto 10 tries
12201              f = fopen(filename, append ? "a" : "w");
12202              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12203              if(f || errno != 13) break;
12204              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12205              tot += t;
12206         }
12207         if (f == NULL) {
12208             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12209             DisplayError(buf, errno);
12210             return FALSE;
12211         } else {
12212             safeStrCpy(buf, lastMsg, MSG_SIZ);
12213             DisplayMessage(_("Waiting for access to save file"), "");
12214             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12215             DisplayMessage(_("Saving game"), "");
12216             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12217             result = SaveGame(f, 0, NULL);
12218             DisplayMessage(buf, "");
12219             return result;
12220         }
12221     }
12222 }
12223
12224 char *
12225 SavePart (char *str)
12226 {
12227     static char buf[MSG_SIZ];
12228     char *p;
12229
12230     p = strchr(str, ' ');
12231     if (p == NULL) return str;
12232     strncpy(buf, str, p - str);
12233     buf[p - str] = NULLCHAR;
12234     return buf;
12235 }
12236
12237 #define PGN_MAX_LINE 75
12238
12239 #define PGN_SIDE_WHITE  0
12240 #define PGN_SIDE_BLACK  1
12241
12242 static int
12243 FindFirstMoveOutOfBook (int side)
12244 {
12245     int result = -1;
12246
12247     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12248         int index = backwardMostMove;
12249         int has_book_hit = 0;
12250
12251         if( (index % 2) != side ) {
12252             index++;
12253         }
12254
12255         while( index < forwardMostMove ) {
12256             /* Check to see if engine is in book */
12257             int depth = pvInfoList[index].depth;
12258             int score = pvInfoList[index].score;
12259             int in_book = 0;
12260
12261             if( depth <= 2 ) {
12262                 in_book = 1;
12263             }
12264             else if( score == 0 && depth == 63 ) {
12265                 in_book = 1; /* Zappa */
12266             }
12267             else if( score == 2 && depth == 99 ) {
12268                 in_book = 1; /* Abrok */
12269             }
12270
12271             has_book_hit += in_book;
12272
12273             if( ! in_book ) {
12274                 result = index;
12275
12276                 break;
12277             }
12278
12279             index += 2;
12280         }
12281     }
12282
12283     return result;
12284 }
12285
12286 void
12287 GetOutOfBookInfo (char * buf)
12288 {
12289     int oob[2];
12290     int i;
12291     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12292
12293     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12294     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12295
12296     *buf = '\0';
12297
12298     if( oob[0] >= 0 || oob[1] >= 0 ) {
12299         for( i=0; i<2; i++ ) {
12300             int idx = oob[i];
12301
12302             if( idx >= 0 ) {
12303                 if( i > 0 && oob[0] >= 0 ) {
12304                     strcat( buf, "   " );
12305                 }
12306
12307                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12308                 sprintf( buf+strlen(buf), "%s%.2f",
12309                     pvInfoList[idx].score >= 0 ? "+" : "",
12310                     pvInfoList[idx].score / 100.0 );
12311             }
12312         }
12313     }
12314 }
12315
12316 /* Save game in PGN style and close the file */
12317 int
12318 SaveGamePGN (FILE *f)
12319 {
12320     int i, offset, linelen, newblock;
12321     time_t tm;
12322 //    char *movetext;
12323     char numtext[32];
12324     int movelen, numlen, blank;
12325     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12326
12327     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12328
12329     tm = time((time_t *) NULL);
12330
12331     PrintPGNTags(f, &gameInfo);
12332
12333     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12334
12335     if (backwardMostMove > 0 || startedFromSetupPosition) {
12336         char *fen = PositionToFEN(backwardMostMove, NULL);
12337         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12338         fprintf(f, "\n{--------------\n");
12339         PrintPosition(f, backwardMostMove);
12340         fprintf(f, "--------------}\n");
12341         free(fen);
12342     }
12343     else {
12344         /* [AS] Out of book annotation */
12345         if( appData.saveOutOfBookInfo ) {
12346             char buf[64];
12347
12348             GetOutOfBookInfo( buf );
12349
12350             if( buf[0] != '\0' ) {
12351                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12352             }
12353         }
12354
12355         fprintf(f, "\n");
12356     }
12357
12358     i = backwardMostMove;
12359     linelen = 0;
12360     newblock = TRUE;
12361
12362     while (i < forwardMostMove) {
12363         /* Print comments preceding this move */
12364         if (commentList[i] != NULL) {
12365             if (linelen > 0) fprintf(f, "\n");
12366             fprintf(f, "%s", commentList[i]);
12367             linelen = 0;
12368             newblock = TRUE;
12369         }
12370
12371         /* Format move number */
12372         if ((i % 2) == 0)
12373           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12374         else
12375           if (newblock)
12376             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12377           else
12378             numtext[0] = NULLCHAR;
12379
12380         numlen = strlen(numtext);
12381         newblock = FALSE;
12382
12383         /* Print move number */
12384         blank = linelen > 0 && numlen > 0;
12385         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12386             fprintf(f, "\n");
12387             linelen = 0;
12388             blank = 0;
12389         }
12390         if (blank) {
12391             fprintf(f, " ");
12392             linelen++;
12393         }
12394         fprintf(f, "%s", numtext);
12395         linelen += numlen;
12396
12397         /* Get move */
12398         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12399         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12400
12401         /* Print move */
12402         blank = linelen > 0 && movelen > 0;
12403         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12404             fprintf(f, "\n");
12405             linelen = 0;
12406             blank = 0;
12407         }
12408         if (blank) {
12409             fprintf(f, " ");
12410             linelen++;
12411         }
12412         fprintf(f, "%s", move_buffer);
12413         linelen += movelen;
12414
12415         /* [AS] Add PV info if present */
12416         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12417             /* [HGM] add time */
12418             char buf[MSG_SIZ]; int seconds;
12419
12420             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12421
12422             if( seconds <= 0)
12423               buf[0] = 0;
12424             else
12425               if( seconds < 30 )
12426                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12427               else
12428                 {
12429                   seconds = (seconds + 4)/10; // round to full seconds
12430                   if( seconds < 60 )
12431                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12432                   else
12433                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12434                 }
12435
12436             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12437                       pvInfoList[i].score >= 0 ? "+" : "",
12438                       pvInfoList[i].score / 100.0,
12439                       pvInfoList[i].depth,
12440                       buf );
12441
12442             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12443
12444             /* Print score/depth */
12445             blank = linelen > 0 && movelen > 0;
12446             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12447                 fprintf(f, "\n");
12448                 linelen = 0;
12449                 blank = 0;
12450             }
12451             if (blank) {
12452                 fprintf(f, " ");
12453                 linelen++;
12454             }
12455             fprintf(f, "%s", move_buffer);
12456             linelen += movelen;
12457         }
12458
12459         i++;
12460     }
12461
12462     /* Start a new line */
12463     if (linelen > 0) fprintf(f, "\n");
12464
12465     /* Print comments after last move */
12466     if (commentList[i] != NULL) {
12467         fprintf(f, "%s\n", commentList[i]);
12468     }
12469
12470     /* Print result */
12471     if (gameInfo.resultDetails != NULL &&
12472         gameInfo.resultDetails[0] != NULLCHAR) {
12473         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12474                 PGNResult(gameInfo.result));
12475     } else {
12476         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12477     }
12478
12479     fclose(f);
12480     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12481     return TRUE;
12482 }
12483
12484 /* Save game in old style and close the file */
12485 int
12486 SaveGameOldStyle (FILE *f)
12487 {
12488     int i, offset;
12489     time_t tm;
12490
12491     tm = time((time_t *) NULL);
12492
12493     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12494     PrintOpponents(f);
12495
12496     if (backwardMostMove > 0 || startedFromSetupPosition) {
12497         fprintf(f, "\n[--------------\n");
12498         PrintPosition(f, backwardMostMove);
12499         fprintf(f, "--------------]\n");
12500     } else {
12501         fprintf(f, "\n");
12502     }
12503
12504     i = backwardMostMove;
12505     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12506
12507     while (i < forwardMostMove) {
12508         if (commentList[i] != NULL) {
12509             fprintf(f, "[%s]\n", commentList[i]);
12510         }
12511
12512         if ((i % 2) == 1) {
12513             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12514             i++;
12515         } else {
12516             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12517             i++;
12518             if (commentList[i] != NULL) {
12519                 fprintf(f, "\n");
12520                 continue;
12521             }
12522             if (i >= forwardMostMove) {
12523                 fprintf(f, "\n");
12524                 break;
12525             }
12526             fprintf(f, "%s\n", parseList[i]);
12527             i++;
12528         }
12529     }
12530
12531     if (commentList[i] != NULL) {
12532         fprintf(f, "[%s]\n", commentList[i]);
12533     }
12534
12535     /* This isn't really the old style, but it's close enough */
12536     if (gameInfo.resultDetails != NULL &&
12537         gameInfo.resultDetails[0] != NULLCHAR) {
12538         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12539                 gameInfo.resultDetails);
12540     } else {
12541         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12542     }
12543
12544     fclose(f);
12545     return TRUE;
12546 }
12547
12548 /* Save the current game to open file f and close the file */
12549 int
12550 SaveGame (FILE *f, int dummy, char *dummy2)
12551 {
12552     if (gameMode == EditPosition) EditPositionDone(TRUE);
12553     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12554     if (appData.oldSaveStyle)
12555       return SaveGameOldStyle(f);
12556     else
12557       return SaveGamePGN(f);
12558 }
12559
12560 /* Save the current position to the given file */
12561 int
12562 SavePositionToFile (char *filename)
12563 {
12564     FILE *f;
12565     char buf[MSG_SIZ];
12566
12567     if (strcmp(filename, "-") == 0) {
12568         return SavePosition(stdout, 0, NULL);
12569     } else {
12570         f = fopen(filename, "a");
12571         if (f == NULL) {
12572             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12573             DisplayError(buf, errno);
12574             return FALSE;
12575         } else {
12576             safeStrCpy(buf, lastMsg, MSG_SIZ);
12577             DisplayMessage(_("Waiting for access to save file"), "");
12578             flock(fileno(f), LOCK_EX); // [HGM] lock
12579             DisplayMessage(_("Saving position"), "");
12580             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12581             SavePosition(f, 0, NULL);
12582             DisplayMessage(buf, "");
12583             return TRUE;
12584         }
12585     }
12586 }
12587
12588 /* Save the current position to the given open file and close the file */
12589 int
12590 SavePosition (FILE *f, int dummy, char *dummy2)
12591 {
12592     time_t tm;
12593     char *fen;
12594
12595     if (gameMode == EditPosition) EditPositionDone(TRUE);
12596     if (appData.oldSaveStyle) {
12597         tm = time((time_t *) NULL);
12598
12599         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12600         PrintOpponents(f);
12601         fprintf(f, "[--------------\n");
12602         PrintPosition(f, currentMove);
12603         fprintf(f, "--------------]\n");
12604     } else {
12605         fen = PositionToFEN(currentMove, NULL);
12606         fprintf(f, "%s\n", fen);
12607         free(fen);
12608     }
12609     fclose(f);
12610     return TRUE;
12611 }
12612
12613 void
12614 ReloadCmailMsgEvent (int unregister)
12615 {
12616 #if !WIN32
12617     static char *inFilename = NULL;
12618     static char *outFilename;
12619     int i;
12620     struct stat inbuf, outbuf;
12621     int status;
12622
12623     /* Any registered moves are unregistered if unregister is set, */
12624     /* i.e. invoked by the signal handler */
12625     if (unregister) {
12626         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12627             cmailMoveRegistered[i] = FALSE;
12628             if (cmailCommentList[i] != NULL) {
12629                 free(cmailCommentList[i]);
12630                 cmailCommentList[i] = NULL;
12631             }
12632         }
12633         nCmailMovesRegistered = 0;
12634     }
12635
12636     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12637         cmailResult[i] = CMAIL_NOT_RESULT;
12638     }
12639     nCmailResults = 0;
12640
12641     if (inFilename == NULL) {
12642         /* Because the filenames are static they only get malloced once  */
12643         /* and they never get freed                                      */
12644         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12645         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12646
12647         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12648         sprintf(outFilename, "%s.out", appData.cmailGameName);
12649     }
12650
12651     status = stat(outFilename, &outbuf);
12652     if (status < 0) {
12653         cmailMailedMove = FALSE;
12654     } else {
12655         status = stat(inFilename, &inbuf);
12656         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12657     }
12658
12659     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12660        counts the games, notes how each one terminated, etc.
12661
12662        It would be nice to remove this kludge and instead gather all
12663        the information while building the game list.  (And to keep it
12664        in the game list nodes instead of having a bunch of fixed-size
12665        parallel arrays.)  Note this will require getting each game's
12666        termination from the PGN tags, as the game list builder does
12667        not process the game moves.  --mann
12668        */
12669     cmailMsgLoaded = TRUE;
12670     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12671
12672     /* Load first game in the file or popup game menu */
12673     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12674
12675 #endif /* !WIN32 */
12676     return;
12677 }
12678
12679 int
12680 RegisterMove ()
12681 {
12682     FILE *f;
12683     char string[MSG_SIZ];
12684
12685     if (   cmailMailedMove
12686         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12687         return TRUE;            /* Allow free viewing  */
12688     }
12689
12690     /* Unregister move to ensure that we don't leave RegisterMove        */
12691     /* with the move registered when the conditions for registering no   */
12692     /* longer hold                                                       */
12693     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12694         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12695         nCmailMovesRegistered --;
12696
12697         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12698           {
12699               free(cmailCommentList[lastLoadGameNumber - 1]);
12700               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12701           }
12702     }
12703
12704     if (cmailOldMove == -1) {
12705         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12706         return FALSE;
12707     }
12708
12709     if (currentMove > cmailOldMove + 1) {
12710         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12711         return FALSE;
12712     }
12713
12714     if (currentMove < cmailOldMove) {
12715         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12716         return FALSE;
12717     }
12718
12719     if (forwardMostMove > currentMove) {
12720         /* Silently truncate extra moves */
12721         TruncateGame();
12722     }
12723
12724     if (   (currentMove == cmailOldMove + 1)
12725         || (   (currentMove == cmailOldMove)
12726             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12727                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12728         if (gameInfo.result != GameUnfinished) {
12729             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12730         }
12731
12732         if (commentList[currentMove] != NULL) {
12733             cmailCommentList[lastLoadGameNumber - 1]
12734               = StrSave(commentList[currentMove]);
12735         }
12736         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12737
12738         if (appData.debugMode)
12739           fprintf(debugFP, "Saving %s for game %d\n",
12740                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12741
12742         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12743
12744         f = fopen(string, "w");
12745         if (appData.oldSaveStyle) {
12746             SaveGameOldStyle(f); /* also closes the file */
12747
12748             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12749             f = fopen(string, "w");
12750             SavePosition(f, 0, NULL); /* also closes the file */
12751         } else {
12752             fprintf(f, "{--------------\n");
12753             PrintPosition(f, currentMove);
12754             fprintf(f, "--------------}\n\n");
12755
12756             SaveGame(f, 0, NULL); /* also closes the file*/
12757         }
12758
12759         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12760         nCmailMovesRegistered ++;
12761     } else if (nCmailGames == 1) {
12762         DisplayError(_("You have not made a move yet"), 0);
12763         return FALSE;
12764     }
12765
12766     return TRUE;
12767 }
12768
12769 void
12770 MailMoveEvent ()
12771 {
12772 #if !WIN32
12773     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12774     FILE *commandOutput;
12775     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12776     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12777     int nBuffers;
12778     int i;
12779     int archived;
12780     char *arcDir;
12781
12782     if (! cmailMsgLoaded) {
12783         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12784         return;
12785     }
12786
12787     if (nCmailGames == nCmailResults) {
12788         DisplayError(_("No unfinished games"), 0);
12789         return;
12790     }
12791
12792 #if CMAIL_PROHIBIT_REMAIL
12793     if (cmailMailedMove) {
12794       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);
12795         DisplayError(msg, 0);
12796         return;
12797     }
12798 #endif
12799
12800     if (! (cmailMailedMove || RegisterMove())) return;
12801
12802     if (   cmailMailedMove
12803         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12804       snprintf(string, MSG_SIZ, partCommandString,
12805                appData.debugMode ? " -v" : "", appData.cmailGameName);
12806         commandOutput = popen(string, "r");
12807
12808         if (commandOutput == NULL) {
12809             DisplayError(_("Failed to invoke cmail"), 0);
12810         } else {
12811             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12812                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12813             }
12814             if (nBuffers > 1) {
12815                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12816                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12817                 nBytes = MSG_SIZ - 1;
12818             } else {
12819                 (void) memcpy(msg, buffer, nBytes);
12820             }
12821             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12822
12823             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12824                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12825
12826                 archived = TRUE;
12827                 for (i = 0; i < nCmailGames; i ++) {
12828                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12829                         archived = FALSE;
12830                     }
12831                 }
12832                 if (   archived
12833                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12834                         != NULL)) {
12835                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12836                            arcDir,
12837                            appData.cmailGameName,
12838                            gameInfo.date);
12839                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12840                     cmailMsgLoaded = FALSE;
12841                 }
12842             }
12843
12844             DisplayInformation(msg);
12845             pclose(commandOutput);
12846         }
12847     } else {
12848         if ((*cmailMsg) != '\0') {
12849             DisplayInformation(cmailMsg);
12850         }
12851     }
12852
12853     return;
12854 #endif /* !WIN32 */
12855 }
12856
12857 char *
12858 CmailMsg ()
12859 {
12860 #if WIN32
12861     return NULL;
12862 #else
12863     int  prependComma = 0;
12864     char number[5];
12865     char string[MSG_SIZ];       /* Space for game-list */
12866     int  i;
12867
12868     if (!cmailMsgLoaded) return "";
12869
12870     if (cmailMailedMove) {
12871       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12872     } else {
12873         /* Create a list of games left */
12874       snprintf(string, MSG_SIZ, "[");
12875         for (i = 0; i < nCmailGames; i ++) {
12876             if (! (   cmailMoveRegistered[i]
12877                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12878                 if (prependComma) {
12879                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12880                 } else {
12881                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12882                     prependComma = 1;
12883                 }
12884
12885                 strcat(string, number);
12886             }
12887         }
12888         strcat(string, "]");
12889
12890         if (nCmailMovesRegistered + nCmailResults == 0) {
12891             switch (nCmailGames) {
12892               case 1:
12893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12894                 break;
12895
12896               case 2:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12898                 break;
12899
12900               default:
12901                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12902                          nCmailGames);
12903                 break;
12904             }
12905         } else {
12906             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12907               case 1:
12908                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12909                          string);
12910                 break;
12911
12912               case 0:
12913                 if (nCmailResults == nCmailGames) {
12914                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12915                 } else {
12916                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12917                 }
12918                 break;
12919
12920               default:
12921                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12922                          string);
12923             }
12924         }
12925     }
12926     return cmailMsg;
12927 #endif /* WIN32 */
12928 }
12929
12930 void
12931 ResetGameEvent ()
12932 {
12933     if (gameMode == Training)
12934       SetTrainingModeOff();
12935
12936     Reset(TRUE, TRUE);
12937     cmailMsgLoaded = FALSE;
12938     if (appData.icsActive) {
12939       SendToICS(ics_prefix);
12940       SendToICS("refresh\n");
12941     }
12942 }
12943
12944 void
12945 ExitEvent (int status)
12946 {
12947     exiting++;
12948     if (exiting > 2) {
12949       /* Give up on clean exit */
12950       exit(status);
12951     }
12952     if (exiting > 1) {
12953       /* Keep trying for clean exit */
12954       return;
12955     }
12956
12957     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12958
12959     if (telnetISR != NULL) {
12960       RemoveInputSource(telnetISR);
12961     }
12962     if (icsPR != NoProc) {
12963       DestroyChildProcess(icsPR, TRUE);
12964     }
12965
12966     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12967     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12968
12969     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12970     /* make sure this other one finishes before killing it!                  */
12971     if(endingGame) { int count = 0;
12972         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12973         while(endingGame && count++ < 10) DoSleep(1);
12974         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12975     }
12976
12977     /* Kill off chess programs */
12978     if (first.pr != NoProc) {
12979         ExitAnalyzeMode();
12980
12981         DoSleep( appData.delayBeforeQuit );
12982         SendToProgram("quit\n", &first);
12983         DoSleep( appData.delayAfterQuit );
12984         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12985     }
12986     if (second.pr != NoProc) {
12987         DoSleep( appData.delayBeforeQuit );
12988         SendToProgram("quit\n", &second);
12989         DoSleep( appData.delayAfterQuit );
12990         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12991     }
12992     if (first.isr != NULL) {
12993         RemoveInputSource(first.isr);
12994     }
12995     if (second.isr != NULL) {
12996         RemoveInputSource(second.isr);
12997     }
12998
12999     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13000     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13001
13002     ShutDownFrontEnd();
13003     exit(status);
13004 }
13005
13006 void
13007 PauseEvent ()
13008 {
13009     if (appData.debugMode)
13010         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13011     if (pausing) {
13012         pausing = FALSE;
13013         ModeHighlight();
13014         if (gameMode == MachinePlaysWhite ||
13015             gameMode == MachinePlaysBlack) {
13016             StartClocks();
13017         } else {
13018             DisplayBothClocks();
13019         }
13020         if (gameMode == PlayFromGameFile) {
13021             if (appData.timeDelay >= 0)
13022                 AutoPlayGameLoop();
13023         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13024             Reset(FALSE, TRUE);
13025             SendToICS(ics_prefix);
13026             SendToICS("refresh\n");
13027         } else if (currentMove < forwardMostMove) {
13028             ForwardInner(forwardMostMove);
13029         }
13030         pauseExamInvalid = FALSE;
13031     } else {
13032         switch (gameMode) {
13033           default:
13034             return;
13035           case IcsExamining:
13036             pauseExamForwardMostMove = forwardMostMove;
13037             pauseExamInvalid = FALSE;
13038             /* fall through */
13039           case IcsObserving:
13040           case IcsPlayingWhite:
13041           case IcsPlayingBlack:
13042             pausing = TRUE;
13043             ModeHighlight();
13044             return;
13045           case PlayFromGameFile:
13046             (void) StopLoadGameTimer();
13047             pausing = TRUE;
13048             ModeHighlight();
13049             break;
13050           case BeginningOfGame:
13051             if (appData.icsActive) return;
13052             /* else fall through */
13053           case MachinePlaysWhite:
13054           case MachinePlaysBlack:
13055           case TwoMachinesPlay:
13056             if (forwardMostMove == 0)
13057               return;           /* don't pause if no one has moved */
13058             if ((gameMode == MachinePlaysWhite &&
13059                  !WhiteOnMove(forwardMostMove)) ||
13060                 (gameMode == MachinePlaysBlack &&
13061                  WhiteOnMove(forwardMostMove))) {
13062                 StopClocks();
13063             }
13064             pausing = TRUE;
13065             ModeHighlight();
13066             break;
13067         }
13068     }
13069 }
13070
13071 void
13072 EditCommentEvent ()
13073 {
13074     char title[MSG_SIZ];
13075
13076     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13077       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13078     } else {
13079       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13080                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13081                parseList[currentMove - 1]);
13082     }
13083
13084     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13085 }
13086
13087
13088 void
13089 EditTagsEvent ()
13090 {
13091     char *tags = PGNTags(&gameInfo);
13092     bookUp = FALSE;
13093     EditTagsPopUp(tags, NULL);
13094     free(tags);
13095 }
13096
13097 void
13098 AnalyzeModeEvent ()
13099 {
13100     if (appData.noChessProgram || gameMode == AnalyzeMode)
13101       return;
13102
13103     if (gameMode != AnalyzeFile) {
13104         if (!appData.icsEngineAnalyze) {
13105                EditGameEvent();
13106                if (gameMode != EditGame) return;
13107         }
13108         ResurrectChessProgram();
13109         SendToProgram("analyze\n", &first);
13110         first.analyzing = TRUE;
13111         /*first.maybeThinking = TRUE;*/
13112         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13113         EngineOutputPopUp();
13114     }
13115     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13116     pausing = FALSE;
13117     ModeHighlight();
13118     SetGameInfo();
13119
13120     StartAnalysisClock();
13121     GetTimeMark(&lastNodeCountTime);
13122     lastNodeCount = 0;
13123 }
13124
13125 void
13126 AnalyzeFileEvent ()
13127 {
13128     if (appData.noChessProgram || gameMode == AnalyzeFile)
13129       return;
13130
13131     if (gameMode != AnalyzeMode) {
13132         EditGameEvent();
13133         if (gameMode != EditGame) return;
13134         ResurrectChessProgram();
13135         SendToProgram("analyze\n", &first);
13136         first.analyzing = TRUE;
13137         /*first.maybeThinking = TRUE;*/
13138         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13139         EngineOutputPopUp();
13140     }
13141     gameMode = AnalyzeFile;
13142     pausing = FALSE;
13143     ModeHighlight();
13144     SetGameInfo();
13145
13146     StartAnalysisClock();
13147     GetTimeMark(&lastNodeCountTime);
13148     lastNodeCount = 0;
13149     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13150 }
13151
13152 void
13153 MachineWhiteEvent ()
13154 {
13155     char buf[MSG_SIZ];
13156     char *bookHit = NULL;
13157
13158     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13159       return;
13160
13161
13162     if (gameMode == PlayFromGameFile ||
13163         gameMode == TwoMachinesPlay  ||
13164         gameMode == Training         ||
13165         gameMode == AnalyzeMode      ||
13166         gameMode == EndOfGame)
13167         EditGameEvent();
13168
13169     if (gameMode == EditPosition)
13170         EditPositionDone(TRUE);
13171
13172     if (!WhiteOnMove(currentMove)) {
13173         DisplayError(_("It is not White's turn"), 0);
13174         return;
13175     }
13176
13177     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13178       ExitAnalyzeMode();
13179
13180     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13181         gameMode == AnalyzeFile)
13182         TruncateGame();
13183
13184     ResurrectChessProgram();    /* in case it isn't running */
13185     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13186         gameMode = MachinePlaysWhite;
13187         ResetClocks();
13188     } else
13189     gameMode = MachinePlaysWhite;
13190     pausing = FALSE;
13191     ModeHighlight();
13192     SetGameInfo();
13193     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13194     DisplayTitle(buf);
13195     if (first.sendName) {
13196       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13197       SendToProgram(buf, &first);
13198     }
13199     if (first.sendTime) {
13200       if (first.useColors) {
13201         SendToProgram("black\n", &first); /*gnu kludge*/
13202       }
13203       SendTimeRemaining(&first, TRUE);
13204     }
13205     if (first.useColors) {
13206       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13207     }
13208     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13209     SetMachineThinkingEnables();
13210     first.maybeThinking = TRUE;
13211     StartClocks();
13212     firstMove = FALSE;
13213
13214     if (appData.autoFlipView && !flipView) {
13215       flipView = !flipView;
13216       DrawPosition(FALSE, NULL);
13217       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13218     }
13219
13220     if(bookHit) { // [HGM] book: simulate book reply
13221         static char bookMove[MSG_SIZ]; // a bit generous?
13222
13223         programStats.nodes = programStats.depth = programStats.time =
13224         programStats.score = programStats.got_only_move = 0;
13225         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13226
13227         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13228         strcat(bookMove, bookHit);
13229         HandleMachineMove(bookMove, &first);
13230     }
13231 }
13232
13233 void
13234 MachineBlackEvent ()
13235 {
13236   char buf[MSG_SIZ];
13237   char *bookHit = NULL;
13238
13239     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13240         return;
13241
13242
13243     if (gameMode == PlayFromGameFile ||
13244         gameMode == TwoMachinesPlay  ||
13245         gameMode == Training         ||
13246         gameMode == AnalyzeMode      ||
13247         gameMode == EndOfGame)
13248         EditGameEvent();
13249
13250     if (gameMode == EditPosition)
13251         EditPositionDone(TRUE);
13252
13253     if (WhiteOnMove(currentMove)) {
13254         DisplayError(_("It is not Black's turn"), 0);
13255         return;
13256     }
13257
13258     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13259       ExitAnalyzeMode();
13260
13261     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13262         gameMode == AnalyzeFile)
13263         TruncateGame();
13264
13265     ResurrectChessProgram();    /* in case it isn't running */
13266     gameMode = MachinePlaysBlack;
13267     pausing = FALSE;
13268     ModeHighlight();
13269     SetGameInfo();
13270     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13271     DisplayTitle(buf);
13272     if (first.sendName) {
13273       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13274       SendToProgram(buf, &first);
13275     }
13276     if (first.sendTime) {
13277       if (first.useColors) {
13278         SendToProgram("white\n", &first); /*gnu kludge*/
13279       }
13280       SendTimeRemaining(&first, FALSE);
13281     }
13282     if (first.useColors) {
13283       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13284     }
13285     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13286     SetMachineThinkingEnables();
13287     first.maybeThinking = TRUE;
13288     StartClocks();
13289
13290     if (appData.autoFlipView && flipView) {
13291       flipView = !flipView;
13292       DrawPosition(FALSE, NULL);
13293       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13294     }
13295     if(bookHit) { // [HGM] book: simulate book reply
13296         static char bookMove[MSG_SIZ]; // a bit generous?
13297
13298         programStats.nodes = programStats.depth = programStats.time =
13299         programStats.score = programStats.got_only_move = 0;
13300         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13301
13302         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13303         strcat(bookMove, bookHit);
13304         HandleMachineMove(bookMove, &first);
13305     }
13306 }
13307
13308
13309 void
13310 DisplayTwoMachinesTitle ()
13311 {
13312     char buf[MSG_SIZ];
13313     if (appData.matchGames > 0) {
13314         if(appData.tourneyFile[0]) {
13315           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13316                    gameInfo.white, _("vs."), gameInfo.black,
13317                    nextGame+1, appData.matchGames+1,
13318                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13319         } else 
13320         if (first.twoMachinesColor[0] == 'w') {
13321           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13322                    gameInfo.white, _("vs."),  gameInfo.black,
13323                    first.matchWins, second.matchWins,
13324                    matchGame - 1 - (first.matchWins + second.matchWins));
13325         } else {
13326           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13327                    gameInfo.white, _("vs."), gameInfo.black,
13328                    second.matchWins, first.matchWins,
13329                    matchGame - 1 - (first.matchWins + second.matchWins));
13330         }
13331     } else {
13332       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13333     }
13334     DisplayTitle(buf);
13335 }
13336
13337 void
13338 SettingsMenuIfReady ()
13339 {
13340   if (second.lastPing != second.lastPong) {
13341     DisplayMessage("", _("Waiting for second chess program"));
13342     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13343     return;
13344   }
13345   ThawUI();
13346   DisplayMessage("", "");
13347   SettingsPopUp(&second);
13348 }
13349
13350 int
13351 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13352 {
13353     char buf[MSG_SIZ];
13354     if (cps->pr == NoProc) {
13355         StartChessProgram(cps);
13356         if (cps->protocolVersion == 1) {
13357           retry();
13358         } else {
13359           /* kludge: allow timeout for initial "feature" command */
13360           FreezeUI();
13361           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13362           DisplayMessage("", buf);
13363           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13364         }
13365         return 1;
13366     }
13367     return 0;
13368 }
13369
13370 void
13371 TwoMachinesEvent P((void))
13372 {
13373     int i;
13374     char buf[MSG_SIZ];
13375     ChessProgramState *onmove;
13376     char *bookHit = NULL;
13377     static int stalling = 0;
13378     TimeMark now;
13379     long wait;
13380
13381     if (appData.noChessProgram) return;
13382
13383     switch (gameMode) {
13384       case TwoMachinesPlay:
13385         return;
13386       case MachinePlaysWhite:
13387       case MachinePlaysBlack:
13388         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13389             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13390             return;
13391         }
13392         /* fall through */
13393       case BeginningOfGame:
13394       case PlayFromGameFile:
13395       case EndOfGame:
13396         EditGameEvent();
13397         if (gameMode != EditGame) return;
13398         break;
13399       case EditPosition:
13400         EditPositionDone(TRUE);
13401         break;
13402       case AnalyzeMode:
13403       case AnalyzeFile:
13404         ExitAnalyzeMode();
13405         break;
13406       case EditGame:
13407       default:
13408         break;
13409     }
13410
13411     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
13412         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
13413         if(strcmp(buf, currentDebugFile)) { // name has changed
13414             FILE *f = fopen(buf, "w");
13415             if(f) { // if opening the new file failed, just keep using the old one
13416                 ASSIGN(currentDebugFile, buf);
13417                 fclose(debugFP);
13418                 debugFP = f;
13419             }
13420         }
13421     }
13422 //    forwardMostMove = currentMove;
13423     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13424
13425     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13426
13427     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13428     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13429       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13430       return;
13431     }
13432     if(!stalling) {
13433       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13434       SendToProgram("force\n", &second);
13435       stalling = 1;
13436       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13437       return;
13438     }
13439     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13440     if(appData.matchPause>10000 || appData.matchPause<10)
13441                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13442     wait = SubtractTimeMarks(&now, &pauseStart);
13443     if(wait < appData.matchPause) {
13444         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13445         return;
13446     }
13447     // we are now committed to starting the game
13448     stalling = 0;
13449     DisplayMessage("", "");
13450     if (startedFromSetupPosition) {
13451         SendBoard(&second, backwardMostMove);
13452     if (appData.debugMode) {
13453         fprintf(debugFP, "Two Machines\n");
13454     }
13455     }
13456     for (i = backwardMostMove; i < forwardMostMove; i++) {
13457         SendMoveToProgram(i, &second);
13458     }
13459
13460     gameMode = TwoMachinesPlay;
13461     pausing = FALSE;
13462     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13463     SetGameInfo();
13464     DisplayTwoMachinesTitle();
13465     firstMove = TRUE;
13466     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13467         onmove = &first;
13468     } else {
13469         onmove = &second;
13470     }
13471     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13472     SendToProgram(first.computerString, &first);
13473     if (first.sendName) {
13474       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13475       SendToProgram(buf, &first);
13476     }
13477     SendToProgram(second.computerString, &second);
13478     if (second.sendName) {
13479       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13480       SendToProgram(buf, &second);
13481     }
13482
13483     ResetClocks();
13484     if (!first.sendTime || !second.sendTime) {
13485         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13486         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13487     }
13488     if (onmove->sendTime) {
13489       if (onmove->useColors) {
13490         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13491       }
13492       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13493     }
13494     if (onmove->useColors) {
13495       SendToProgram(onmove->twoMachinesColor, onmove);
13496     }
13497     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13498 //    SendToProgram("go\n", onmove);
13499     onmove->maybeThinking = TRUE;
13500     SetMachineThinkingEnables();
13501
13502     StartClocks();
13503
13504     if(bookHit) { // [HGM] book: simulate book reply
13505         static char bookMove[MSG_SIZ]; // a bit generous?
13506
13507         programStats.nodes = programStats.depth = programStats.time =
13508         programStats.score = programStats.got_only_move = 0;
13509         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13510
13511         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13512         strcat(bookMove, bookHit);
13513         savedMessage = bookMove; // args for deferred call
13514         savedState = onmove;
13515         ScheduleDelayedEvent(DeferredBookMove, 1);
13516     }
13517 }
13518
13519 void
13520 TrainingEvent ()
13521 {
13522     if (gameMode == Training) {
13523       SetTrainingModeOff();
13524       gameMode = PlayFromGameFile;
13525       DisplayMessage("", _("Training mode off"));
13526     } else {
13527       gameMode = Training;
13528       animateTraining = appData.animate;
13529
13530       /* make sure we are not already at the end of the game */
13531       if (currentMove < forwardMostMove) {
13532         SetTrainingModeOn();
13533         DisplayMessage("", _("Training mode on"));
13534       } else {
13535         gameMode = PlayFromGameFile;
13536         DisplayError(_("Already at end of game"), 0);
13537       }
13538     }
13539     ModeHighlight();
13540 }
13541
13542 void
13543 IcsClientEvent ()
13544 {
13545     if (!appData.icsActive) return;
13546     switch (gameMode) {
13547       case IcsPlayingWhite:
13548       case IcsPlayingBlack:
13549       case IcsObserving:
13550       case IcsIdle:
13551       case BeginningOfGame:
13552       case IcsExamining:
13553         return;
13554
13555       case EditGame:
13556         break;
13557
13558       case EditPosition:
13559         EditPositionDone(TRUE);
13560         break;
13561
13562       case AnalyzeMode:
13563       case AnalyzeFile:
13564         ExitAnalyzeMode();
13565         break;
13566
13567       default:
13568         EditGameEvent();
13569         break;
13570     }
13571
13572     gameMode = IcsIdle;
13573     ModeHighlight();
13574     return;
13575 }
13576
13577 void
13578 EditGameEvent ()
13579 {
13580     int i;
13581
13582     switch (gameMode) {
13583       case Training:
13584         SetTrainingModeOff();
13585         break;
13586       case MachinePlaysWhite:
13587       case MachinePlaysBlack:
13588       case BeginningOfGame:
13589         SendToProgram("force\n", &first);
13590         SetUserThinkingEnables();
13591         break;
13592       case PlayFromGameFile:
13593         (void) StopLoadGameTimer();
13594         if (gameFileFP != NULL) {
13595             gameFileFP = NULL;
13596         }
13597         break;
13598       case EditPosition:
13599         EditPositionDone(TRUE);
13600         break;
13601       case AnalyzeMode:
13602       case AnalyzeFile:
13603         ExitAnalyzeMode();
13604         SendToProgram("force\n", &first);
13605         break;
13606       case TwoMachinesPlay:
13607         GameEnds(EndOfFile, NULL, GE_PLAYER);
13608         ResurrectChessProgram();
13609         SetUserThinkingEnables();
13610         break;
13611       case EndOfGame:
13612         ResurrectChessProgram();
13613         break;
13614       case IcsPlayingBlack:
13615       case IcsPlayingWhite:
13616         DisplayError(_("Warning: You are still playing a game"), 0);
13617         break;
13618       case IcsObserving:
13619         DisplayError(_("Warning: You are still observing a game"), 0);
13620         break;
13621       case IcsExamining:
13622         DisplayError(_("Warning: You are still examining a game"), 0);
13623         break;
13624       case IcsIdle:
13625         break;
13626       case EditGame:
13627       default:
13628         return;
13629     }
13630
13631     pausing = FALSE;
13632     StopClocks();
13633     first.offeredDraw = second.offeredDraw = 0;
13634
13635     if (gameMode == PlayFromGameFile) {
13636         whiteTimeRemaining = timeRemaining[0][currentMove];
13637         blackTimeRemaining = timeRemaining[1][currentMove];
13638         DisplayTitle("");
13639     }
13640
13641     if (gameMode == MachinePlaysWhite ||
13642         gameMode == MachinePlaysBlack ||
13643         gameMode == TwoMachinesPlay ||
13644         gameMode == EndOfGame) {
13645         i = forwardMostMove;
13646         while (i > currentMove) {
13647             SendToProgram("undo\n", &first);
13648             i--;
13649         }
13650         if(!adjustedClock) {
13651         whiteTimeRemaining = timeRemaining[0][currentMove];
13652         blackTimeRemaining = timeRemaining[1][currentMove];
13653         DisplayBothClocks();
13654         }
13655         if (whiteFlag || blackFlag) {
13656             whiteFlag = blackFlag = 0;
13657         }
13658         DisplayTitle("");
13659     }
13660
13661     gameMode = EditGame;
13662     ModeHighlight();
13663     SetGameInfo();
13664 }
13665
13666
13667 void
13668 EditPositionEvent ()
13669 {
13670     if (gameMode == EditPosition) {
13671         EditGameEvent();
13672         return;
13673     }
13674
13675     EditGameEvent();
13676     if (gameMode != EditGame) return;
13677
13678     gameMode = EditPosition;
13679     ModeHighlight();
13680     SetGameInfo();
13681     if (currentMove > 0)
13682       CopyBoard(boards[0], boards[currentMove]);
13683
13684     blackPlaysFirst = !WhiteOnMove(currentMove);
13685     ResetClocks();
13686     currentMove = forwardMostMove = backwardMostMove = 0;
13687     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13688     DisplayMove(-1);
13689 }
13690
13691 void
13692 ExitAnalyzeMode ()
13693 {
13694     /* [DM] icsEngineAnalyze - possible call from other functions */
13695     if (appData.icsEngineAnalyze) {
13696         appData.icsEngineAnalyze = FALSE;
13697
13698         DisplayMessage("",_("Close ICS engine analyze..."));
13699     }
13700     if (first.analysisSupport && first.analyzing) {
13701       SendToProgram("exit\n", &first);
13702       first.analyzing = FALSE;
13703     }
13704     thinkOutput[0] = NULLCHAR;
13705 }
13706
13707 void
13708 EditPositionDone (Boolean fakeRights)
13709 {
13710     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13711
13712     startedFromSetupPosition = TRUE;
13713     InitChessProgram(&first, FALSE);
13714     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13715       boards[0][EP_STATUS] = EP_NONE;
13716       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13717     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13718         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13719         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13720       } else boards[0][CASTLING][2] = NoRights;
13721     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13722         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13723         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13724       } else boards[0][CASTLING][5] = NoRights;
13725     }
13726     SendToProgram("force\n", &first);
13727     if (blackPlaysFirst) {
13728         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13729         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13730         currentMove = forwardMostMove = backwardMostMove = 1;
13731         CopyBoard(boards[1], boards[0]);
13732     } else {
13733         currentMove = forwardMostMove = backwardMostMove = 0;
13734     }
13735     SendBoard(&first, forwardMostMove);
13736     if (appData.debugMode) {
13737         fprintf(debugFP, "EditPosDone\n");
13738     }
13739     DisplayTitle("");
13740     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13741     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13742     gameMode = EditGame;
13743     ModeHighlight();
13744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13745     ClearHighlights(); /* [AS] */
13746 }
13747
13748 /* Pause for `ms' milliseconds */
13749 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13750 void
13751 TimeDelay (long ms)
13752 {
13753     TimeMark m1, m2;
13754
13755     GetTimeMark(&m1);
13756     do {
13757         GetTimeMark(&m2);
13758     } while (SubtractTimeMarks(&m2, &m1) < ms);
13759 }
13760
13761 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13762 void
13763 SendMultiLineToICS (char *buf)
13764 {
13765     char temp[MSG_SIZ+1], *p;
13766     int len;
13767
13768     len = strlen(buf);
13769     if (len > MSG_SIZ)
13770       len = MSG_SIZ;
13771
13772     strncpy(temp, buf, len);
13773     temp[len] = 0;
13774
13775     p = temp;
13776     while (*p) {
13777         if (*p == '\n' || *p == '\r')
13778           *p = ' ';
13779         ++p;
13780     }
13781
13782     strcat(temp, "\n");
13783     SendToICS(temp);
13784     SendToPlayer(temp, strlen(temp));
13785 }
13786
13787 void
13788 SetWhiteToPlayEvent ()
13789 {
13790     if (gameMode == EditPosition) {
13791         blackPlaysFirst = FALSE;
13792         DisplayBothClocks();    /* works because currentMove is 0 */
13793     } else if (gameMode == IcsExamining) {
13794         SendToICS(ics_prefix);
13795         SendToICS("tomove white\n");
13796     }
13797 }
13798
13799 void
13800 SetBlackToPlayEvent ()
13801 {
13802     if (gameMode == EditPosition) {
13803         blackPlaysFirst = TRUE;
13804         currentMove = 1;        /* kludge */
13805         DisplayBothClocks();
13806         currentMove = 0;
13807     } else if (gameMode == IcsExamining) {
13808         SendToICS(ics_prefix);
13809         SendToICS("tomove black\n");
13810     }
13811 }
13812
13813 void
13814 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13815 {
13816     char buf[MSG_SIZ];
13817     ChessSquare piece = boards[0][y][x];
13818
13819     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13820
13821     switch (selection) {
13822       case ClearBoard:
13823         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13824             SendToICS(ics_prefix);
13825             SendToICS("bsetup clear\n");
13826         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13827             SendToICS(ics_prefix);
13828             SendToICS("clearboard\n");
13829         } else {
13830             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13831                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13832                 for (y = 0; y < BOARD_HEIGHT; y++) {
13833                     if (gameMode == IcsExamining) {
13834                         if (boards[currentMove][y][x] != EmptySquare) {
13835                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13836                                     AAA + x, ONE + y);
13837                             SendToICS(buf);
13838                         }
13839                     } else {
13840                         boards[0][y][x] = p;
13841                     }
13842                 }
13843             }
13844         }
13845         if (gameMode == EditPosition) {
13846             DrawPosition(FALSE, boards[0]);
13847         }
13848         break;
13849
13850       case WhitePlay:
13851         SetWhiteToPlayEvent();
13852         break;
13853
13854       case BlackPlay:
13855         SetBlackToPlayEvent();
13856         break;
13857
13858       case EmptySquare:
13859         if (gameMode == IcsExamining) {
13860             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13861             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13862             SendToICS(buf);
13863         } else {
13864             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13865                 if(x == BOARD_LEFT-2) {
13866                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13867                     boards[0][y][1] = 0;
13868                 } else
13869                 if(x == BOARD_RGHT+1) {
13870                     if(y >= gameInfo.holdingsSize) break;
13871                     boards[0][y][BOARD_WIDTH-2] = 0;
13872                 } else break;
13873             }
13874             boards[0][y][x] = EmptySquare;
13875             DrawPosition(FALSE, boards[0]);
13876         }
13877         break;
13878
13879       case PromotePiece:
13880         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13881            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13882             selection = (ChessSquare) (PROMOTED piece);
13883         } else if(piece == EmptySquare) selection = WhiteSilver;
13884         else selection = (ChessSquare)((int)piece - 1);
13885         goto defaultlabel;
13886
13887       case DemotePiece:
13888         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13889            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13890             selection = (ChessSquare) (DEMOTED piece);
13891         } else if(piece == EmptySquare) selection = BlackSilver;
13892         else selection = (ChessSquare)((int)piece + 1);
13893         goto defaultlabel;
13894
13895       case WhiteQueen:
13896       case BlackQueen:
13897         if(gameInfo.variant == VariantShatranj ||
13898            gameInfo.variant == VariantXiangqi  ||
13899            gameInfo.variant == VariantCourier  ||
13900            gameInfo.variant == VariantMakruk     )
13901             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13902         goto defaultlabel;
13903
13904       case WhiteKing:
13905       case BlackKing:
13906         if(gameInfo.variant == VariantXiangqi)
13907             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13908         if(gameInfo.variant == VariantKnightmate)
13909             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13910       default:
13911         defaultlabel:
13912         if (gameMode == IcsExamining) {
13913             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13914             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13915                      PieceToChar(selection), AAA + x, ONE + y);
13916             SendToICS(buf);
13917         } else {
13918             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13919                 int n;
13920                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13921                     n = PieceToNumber(selection - BlackPawn);
13922                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13923                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13924                     boards[0][BOARD_HEIGHT-1-n][1]++;
13925                 } else
13926                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13927                     n = PieceToNumber(selection);
13928                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13929                     boards[0][n][BOARD_WIDTH-1] = selection;
13930                     boards[0][n][BOARD_WIDTH-2]++;
13931                 }
13932             } else
13933             boards[0][y][x] = selection;
13934             DrawPosition(TRUE, boards[0]);
13935         }
13936         break;
13937     }
13938 }
13939
13940
13941 void
13942 DropMenuEvent (ChessSquare selection, int x, int y)
13943 {
13944     ChessMove moveType;
13945
13946     switch (gameMode) {
13947       case IcsPlayingWhite:
13948       case MachinePlaysBlack:
13949         if (!WhiteOnMove(currentMove)) {
13950             DisplayMoveError(_("It is Black's turn"));
13951             return;
13952         }
13953         moveType = WhiteDrop;
13954         break;
13955       case IcsPlayingBlack:
13956       case MachinePlaysWhite:
13957         if (WhiteOnMove(currentMove)) {
13958             DisplayMoveError(_("It is White's turn"));
13959             return;
13960         }
13961         moveType = BlackDrop;
13962         break;
13963       case EditGame:
13964         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13965         break;
13966       default:
13967         return;
13968     }
13969
13970     if (moveType == BlackDrop && selection < BlackPawn) {
13971       selection = (ChessSquare) ((int) selection
13972                                  + (int) BlackPawn - (int) WhitePawn);
13973     }
13974     if (boards[currentMove][y][x] != EmptySquare) {
13975         DisplayMoveError(_("That square is occupied"));
13976         return;
13977     }
13978
13979     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13980 }
13981
13982 void
13983 AcceptEvent ()
13984 {
13985     /* Accept a pending offer of any kind from opponent */
13986
13987     if (appData.icsActive) {
13988         SendToICS(ics_prefix);
13989         SendToICS("accept\n");
13990     } else if (cmailMsgLoaded) {
13991         if (currentMove == cmailOldMove &&
13992             commentList[cmailOldMove] != NULL &&
13993             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13994                    "Black offers a draw" : "White offers a draw")) {
13995             TruncateGame();
13996             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13997             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13998         } else {
13999             DisplayError(_("There is no pending offer on this move"), 0);
14000             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14001         }
14002     } else {
14003         /* Not used for offers from chess program */
14004     }
14005 }
14006
14007 void
14008 DeclineEvent ()
14009 {
14010     /* Decline a pending offer of any kind from opponent */
14011
14012     if (appData.icsActive) {
14013         SendToICS(ics_prefix);
14014         SendToICS("decline\n");
14015     } else if (cmailMsgLoaded) {
14016         if (currentMove == cmailOldMove &&
14017             commentList[cmailOldMove] != NULL &&
14018             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14019                    "Black offers a draw" : "White offers a draw")) {
14020 #ifdef NOTDEF
14021             AppendComment(cmailOldMove, "Draw declined", TRUE);
14022             DisplayComment(cmailOldMove - 1, "Draw declined");
14023 #endif /*NOTDEF*/
14024         } else {
14025             DisplayError(_("There is no pending offer on this move"), 0);
14026         }
14027     } else {
14028         /* Not used for offers from chess program */
14029     }
14030 }
14031
14032 void
14033 RematchEvent ()
14034 {
14035     /* Issue ICS rematch command */
14036     if (appData.icsActive) {
14037         SendToICS(ics_prefix);
14038         SendToICS("rematch\n");
14039     }
14040 }
14041
14042 void
14043 CallFlagEvent ()
14044 {
14045     /* Call your opponent's flag (claim a win on time) */
14046     if (appData.icsActive) {
14047         SendToICS(ics_prefix);
14048         SendToICS("flag\n");
14049     } else {
14050         switch (gameMode) {
14051           default:
14052             return;
14053           case MachinePlaysWhite:
14054             if (whiteFlag) {
14055                 if (blackFlag)
14056                   GameEnds(GameIsDrawn, "Both players ran out of time",
14057                            GE_PLAYER);
14058                 else
14059                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14060             } else {
14061                 DisplayError(_("Your opponent is not out of time"), 0);
14062             }
14063             break;
14064           case MachinePlaysBlack:
14065             if (blackFlag) {
14066                 if (whiteFlag)
14067                   GameEnds(GameIsDrawn, "Both players ran out of time",
14068                            GE_PLAYER);
14069                 else
14070                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14071             } else {
14072                 DisplayError(_("Your opponent is not out of time"), 0);
14073             }
14074             break;
14075         }
14076     }
14077 }
14078
14079 void
14080 ClockClick (int which)
14081 {       // [HGM] code moved to back-end from winboard.c
14082         if(which) { // black clock
14083           if (gameMode == EditPosition || gameMode == IcsExamining) {
14084             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14085             SetBlackToPlayEvent();
14086           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14087           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14088           } else if (shiftKey) {
14089             AdjustClock(which, -1);
14090           } else if (gameMode == IcsPlayingWhite ||
14091                      gameMode == MachinePlaysBlack) {
14092             CallFlagEvent();
14093           }
14094         } else { // white clock
14095           if (gameMode == EditPosition || gameMode == IcsExamining) {
14096             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14097             SetWhiteToPlayEvent();
14098           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14099           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14100           } else if (shiftKey) {
14101             AdjustClock(which, -1);
14102           } else if (gameMode == IcsPlayingBlack ||
14103                    gameMode == MachinePlaysWhite) {
14104             CallFlagEvent();
14105           }
14106         }
14107 }
14108
14109 void
14110 DrawEvent ()
14111 {
14112     /* Offer draw or accept pending draw offer from opponent */
14113
14114     if (appData.icsActive) {
14115         /* Note: tournament rules require draw offers to be
14116            made after you make your move but before you punch
14117            your clock.  Currently ICS doesn't let you do that;
14118            instead, you immediately punch your clock after making
14119            a move, but you can offer a draw at any time. */
14120
14121         SendToICS(ics_prefix);
14122         SendToICS("draw\n");
14123         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14124     } else if (cmailMsgLoaded) {
14125         if (currentMove == cmailOldMove &&
14126             commentList[cmailOldMove] != NULL &&
14127             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14128                    "Black offers a draw" : "White offers a draw")) {
14129             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14130             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14131         } else if (currentMove == cmailOldMove + 1) {
14132             char *offer = WhiteOnMove(cmailOldMove) ?
14133               "White offers a draw" : "Black offers a draw";
14134             AppendComment(currentMove, offer, TRUE);
14135             DisplayComment(currentMove - 1, offer);
14136             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14137         } else {
14138             DisplayError(_("You must make your move before offering a draw"), 0);
14139             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14140         }
14141     } else if (first.offeredDraw) {
14142         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14143     } else {
14144         if (first.sendDrawOffers) {
14145             SendToProgram("draw\n", &first);
14146             userOfferedDraw = TRUE;
14147         }
14148     }
14149 }
14150
14151 void
14152 AdjournEvent ()
14153 {
14154     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14155
14156     if (appData.icsActive) {
14157         SendToICS(ics_prefix);
14158         SendToICS("adjourn\n");
14159     } else {
14160         /* Currently GNU Chess doesn't offer or accept Adjourns */
14161     }
14162 }
14163
14164
14165 void
14166 AbortEvent ()
14167 {
14168     /* Offer Abort or accept pending Abort offer from opponent */
14169
14170     if (appData.icsActive) {
14171         SendToICS(ics_prefix);
14172         SendToICS("abort\n");
14173     } else {
14174         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14175     }
14176 }
14177
14178 void
14179 ResignEvent ()
14180 {
14181     /* Resign.  You can do this even if it's not your turn. */
14182
14183     if (appData.icsActive) {
14184         SendToICS(ics_prefix);
14185         SendToICS("resign\n");
14186     } else {
14187         switch (gameMode) {
14188           case MachinePlaysWhite:
14189             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14190             break;
14191           case MachinePlaysBlack:
14192             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14193             break;
14194           case EditGame:
14195             if (cmailMsgLoaded) {
14196                 TruncateGame();
14197                 if (WhiteOnMove(cmailOldMove)) {
14198                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14199                 } else {
14200                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14201                 }
14202                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14203             }
14204             break;
14205           default:
14206             break;
14207         }
14208     }
14209 }
14210
14211
14212 void
14213 StopObservingEvent ()
14214 {
14215     /* Stop observing current games */
14216     SendToICS(ics_prefix);
14217     SendToICS("unobserve\n");
14218 }
14219
14220 void
14221 StopExaminingEvent ()
14222 {
14223     /* Stop observing current game */
14224     SendToICS(ics_prefix);
14225     SendToICS("unexamine\n");
14226 }
14227
14228 void
14229 ForwardInner (int target)
14230 {
14231     int limit;
14232
14233     if (appData.debugMode)
14234         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14235                 target, currentMove, forwardMostMove);
14236
14237     if (gameMode == EditPosition)
14238       return;
14239
14240     seekGraphUp = FALSE;
14241     MarkTargetSquares(1);
14242
14243     if (gameMode == PlayFromGameFile && !pausing)
14244       PauseEvent();
14245
14246     if (gameMode == IcsExamining && pausing)
14247       limit = pauseExamForwardMostMove;
14248     else
14249       limit = forwardMostMove;
14250
14251     if (target > limit) target = limit;
14252
14253     if (target > 0 && moveList[target - 1][0]) {
14254         int fromX, fromY, toX, toY;
14255         toX = moveList[target - 1][2] - AAA;
14256         toY = moveList[target - 1][3] - ONE;
14257         if (moveList[target - 1][1] == '@') {
14258             if (appData.highlightLastMove) {
14259                 SetHighlights(-1, -1, toX, toY);
14260             }
14261         } else {
14262             fromX = moveList[target - 1][0] - AAA;
14263             fromY = moveList[target - 1][1] - ONE;
14264             if (target == currentMove + 1) {
14265                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14266             }
14267             if (appData.highlightLastMove) {
14268                 SetHighlights(fromX, fromY, toX, toY);
14269             }
14270         }
14271     }
14272     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14273         gameMode == Training || gameMode == PlayFromGameFile ||
14274         gameMode == AnalyzeFile) {
14275         while (currentMove < target) {
14276             SendMoveToProgram(currentMove++, &first);
14277         }
14278     } else {
14279         currentMove = target;
14280     }
14281
14282     if (gameMode == EditGame || gameMode == EndOfGame) {
14283         whiteTimeRemaining = timeRemaining[0][currentMove];
14284         blackTimeRemaining = timeRemaining[1][currentMove];
14285     }
14286     DisplayBothClocks();
14287     DisplayMove(currentMove - 1);
14288     DrawPosition(FALSE, boards[currentMove]);
14289     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14290     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14291         DisplayComment(currentMove - 1, commentList[currentMove]);
14292     }
14293 }
14294
14295
14296 void
14297 ForwardEvent ()
14298 {
14299     if (gameMode == IcsExamining && !pausing) {
14300         SendToICS(ics_prefix);
14301         SendToICS("forward\n");
14302     } else {
14303         ForwardInner(currentMove + 1);
14304     }
14305 }
14306
14307 void
14308 ToEndEvent ()
14309 {
14310     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14311         /* to optimze, we temporarily turn off analysis mode while we feed
14312          * the remaining moves to the engine. Otherwise we get analysis output
14313          * after each move.
14314          */
14315         if (first.analysisSupport) {
14316           SendToProgram("exit\nforce\n", &first);
14317           first.analyzing = FALSE;
14318         }
14319     }
14320
14321     if (gameMode == IcsExamining && !pausing) {
14322         SendToICS(ics_prefix);
14323         SendToICS("forward 999999\n");
14324     } else {
14325         ForwardInner(forwardMostMove);
14326     }
14327
14328     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14329         /* we have fed all the moves, so reactivate analysis mode */
14330         SendToProgram("analyze\n", &first);
14331         first.analyzing = TRUE;
14332         /*first.maybeThinking = TRUE;*/
14333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14334     }
14335 }
14336
14337 void
14338 BackwardInner (int target)
14339 {
14340     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14341
14342     if (appData.debugMode)
14343         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14344                 target, currentMove, forwardMostMove);
14345
14346     if (gameMode == EditPosition) return;
14347     seekGraphUp = FALSE;
14348     MarkTargetSquares(1);
14349     if (currentMove <= backwardMostMove) {
14350         ClearHighlights();
14351         DrawPosition(full_redraw, boards[currentMove]);
14352         return;
14353     }
14354     if (gameMode == PlayFromGameFile && !pausing)
14355       PauseEvent();
14356
14357     if (moveList[target][0]) {
14358         int fromX, fromY, toX, toY;
14359         toX = moveList[target][2] - AAA;
14360         toY = moveList[target][3] - ONE;
14361         if (moveList[target][1] == '@') {
14362             if (appData.highlightLastMove) {
14363                 SetHighlights(-1, -1, toX, toY);
14364             }
14365         } else {
14366             fromX = moveList[target][0] - AAA;
14367             fromY = moveList[target][1] - ONE;
14368             if (target == currentMove - 1) {
14369                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14370             }
14371             if (appData.highlightLastMove) {
14372                 SetHighlights(fromX, fromY, toX, toY);
14373             }
14374         }
14375     }
14376     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14377         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14378         while (currentMove > target) {
14379             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14380                 // null move cannot be undone. Reload program with move history before it.
14381                 int i;
14382                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14383                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14384                 }
14385                 SendBoard(&first, i); 
14386                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14387                 break;
14388             }
14389             SendToProgram("undo\n", &first);
14390             currentMove--;
14391         }
14392     } else {
14393         currentMove = target;
14394     }
14395
14396     if (gameMode == EditGame || gameMode == EndOfGame) {
14397         whiteTimeRemaining = timeRemaining[0][currentMove];
14398         blackTimeRemaining = timeRemaining[1][currentMove];
14399     }
14400     DisplayBothClocks();
14401     DisplayMove(currentMove - 1);
14402     DrawPosition(full_redraw, boards[currentMove]);
14403     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14404     // [HGM] PV info: routine tests if comment empty
14405     DisplayComment(currentMove - 1, commentList[currentMove]);
14406 }
14407
14408 void
14409 BackwardEvent ()
14410 {
14411     if (gameMode == IcsExamining && !pausing) {
14412         SendToICS(ics_prefix);
14413         SendToICS("backward\n");
14414     } else {
14415         BackwardInner(currentMove - 1);
14416     }
14417 }
14418
14419 void
14420 ToStartEvent ()
14421 {
14422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14423         /* to optimize, we temporarily turn off analysis mode while we undo
14424          * all the moves. Otherwise we get analysis output after each undo.
14425          */
14426         if (first.analysisSupport) {
14427           SendToProgram("exit\nforce\n", &first);
14428           first.analyzing = FALSE;
14429         }
14430     }
14431
14432     if (gameMode == IcsExamining && !pausing) {
14433         SendToICS(ics_prefix);
14434         SendToICS("backward 999999\n");
14435     } else {
14436         BackwardInner(backwardMostMove);
14437     }
14438
14439     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14440         /* we have fed all the moves, so reactivate analysis mode */
14441         SendToProgram("analyze\n", &first);
14442         first.analyzing = TRUE;
14443         /*first.maybeThinking = TRUE;*/
14444         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14445     }
14446 }
14447
14448 void
14449 ToNrEvent (int to)
14450 {
14451   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14452   if (to >= forwardMostMove) to = forwardMostMove;
14453   if (to <= backwardMostMove) to = backwardMostMove;
14454   if (to < currentMove) {
14455     BackwardInner(to);
14456   } else {
14457     ForwardInner(to);
14458   }
14459 }
14460
14461 void
14462 RevertEvent (Boolean annotate)
14463 {
14464     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14465         return;
14466     }
14467     if (gameMode != IcsExamining) {
14468         DisplayError(_("You are not examining a game"), 0);
14469         return;
14470     }
14471     if (pausing) {
14472         DisplayError(_("You can't revert while pausing"), 0);
14473         return;
14474     }
14475     SendToICS(ics_prefix);
14476     SendToICS("revert\n");
14477 }
14478
14479 void
14480 RetractMoveEvent ()
14481 {
14482     switch (gameMode) {
14483       case MachinePlaysWhite:
14484       case MachinePlaysBlack:
14485         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14486             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14487             return;
14488         }
14489         if (forwardMostMove < 2) return;
14490         currentMove = forwardMostMove = forwardMostMove - 2;
14491         whiteTimeRemaining = timeRemaining[0][currentMove];
14492         blackTimeRemaining = timeRemaining[1][currentMove];
14493         DisplayBothClocks();
14494         DisplayMove(currentMove - 1);
14495         ClearHighlights();/*!! could figure this out*/
14496         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14497         SendToProgram("remove\n", &first);
14498         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14499         break;
14500
14501       case BeginningOfGame:
14502       default:
14503         break;
14504
14505       case IcsPlayingWhite:
14506       case IcsPlayingBlack:
14507         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14508             SendToICS(ics_prefix);
14509             SendToICS("takeback 2\n");
14510         } else {
14511             SendToICS(ics_prefix);
14512             SendToICS("takeback 1\n");
14513         }
14514         break;
14515     }
14516 }
14517
14518 void
14519 MoveNowEvent ()
14520 {
14521     ChessProgramState *cps;
14522
14523     switch (gameMode) {
14524       case MachinePlaysWhite:
14525         if (!WhiteOnMove(forwardMostMove)) {
14526             DisplayError(_("It is your turn"), 0);
14527             return;
14528         }
14529         cps = &first;
14530         break;
14531       case MachinePlaysBlack:
14532         if (WhiteOnMove(forwardMostMove)) {
14533             DisplayError(_("It is your turn"), 0);
14534             return;
14535         }
14536         cps = &first;
14537         break;
14538       case TwoMachinesPlay:
14539         if (WhiteOnMove(forwardMostMove) ==
14540             (first.twoMachinesColor[0] == 'w')) {
14541             cps = &first;
14542         } else {
14543             cps = &second;
14544         }
14545         break;
14546       case BeginningOfGame:
14547       default:
14548         return;
14549     }
14550     SendToProgram("?\n", cps);
14551 }
14552
14553 void
14554 TruncateGameEvent ()
14555 {
14556     EditGameEvent();
14557     if (gameMode != EditGame) return;
14558     TruncateGame();
14559 }
14560
14561 void
14562 TruncateGame ()
14563 {
14564     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14565     if (forwardMostMove > currentMove) {
14566         if (gameInfo.resultDetails != NULL) {
14567             free(gameInfo.resultDetails);
14568             gameInfo.resultDetails = NULL;
14569             gameInfo.result = GameUnfinished;
14570         }
14571         forwardMostMove = currentMove;
14572         HistorySet(parseList, backwardMostMove, forwardMostMove,
14573                    currentMove-1);
14574     }
14575 }
14576
14577 void
14578 HintEvent ()
14579 {
14580     if (appData.noChessProgram) return;
14581     switch (gameMode) {
14582       case MachinePlaysWhite:
14583         if (WhiteOnMove(forwardMostMove)) {
14584             DisplayError(_("Wait until your turn"), 0);
14585             return;
14586         }
14587         break;
14588       case BeginningOfGame:
14589       case MachinePlaysBlack:
14590         if (!WhiteOnMove(forwardMostMove)) {
14591             DisplayError(_("Wait until your turn"), 0);
14592             return;
14593         }
14594         break;
14595       default:
14596         DisplayError(_("No hint available"), 0);
14597         return;
14598     }
14599     SendToProgram("hint\n", &first);
14600     hintRequested = TRUE;
14601 }
14602
14603 void
14604 BookEvent ()
14605 {
14606     if (appData.noChessProgram) return;
14607     switch (gameMode) {
14608       case MachinePlaysWhite:
14609         if (WhiteOnMove(forwardMostMove)) {
14610             DisplayError(_("Wait until your turn"), 0);
14611             return;
14612         }
14613         break;
14614       case BeginningOfGame:
14615       case MachinePlaysBlack:
14616         if (!WhiteOnMove(forwardMostMove)) {
14617             DisplayError(_("Wait until your turn"), 0);
14618             return;
14619         }
14620         break;
14621       case EditPosition:
14622         EditPositionDone(TRUE);
14623         break;
14624       case TwoMachinesPlay:
14625         return;
14626       default:
14627         break;
14628     }
14629     SendToProgram("bk\n", &first);
14630     bookOutput[0] = NULLCHAR;
14631     bookRequested = TRUE;
14632 }
14633
14634 void
14635 AboutGameEvent ()
14636 {
14637     char *tags = PGNTags(&gameInfo);
14638     TagsPopUp(tags, CmailMsg());
14639     free(tags);
14640 }
14641
14642 /* end button procedures */
14643
14644 void
14645 PrintPosition (FILE *fp, int move)
14646 {
14647     int i, j;
14648
14649     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14650         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14651             char c = PieceToChar(boards[move][i][j]);
14652             fputc(c == 'x' ? '.' : c, fp);
14653             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14654         }
14655     }
14656     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14657       fprintf(fp, "white to play\n");
14658     else
14659       fprintf(fp, "black to play\n");
14660 }
14661
14662 void
14663 PrintOpponents (FILE *fp)
14664 {
14665     if (gameInfo.white != NULL) {
14666         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14667     } else {
14668         fprintf(fp, "\n");
14669     }
14670 }
14671
14672 /* Find last component of program's own name, using some heuristics */
14673 void
14674 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14675 {
14676     char *p, *q, c;
14677     int local = (strcmp(host, "localhost") == 0);
14678     while (!local && (p = strchr(prog, ';')) != NULL) {
14679         p++;
14680         while (*p == ' ') p++;
14681         prog = p;
14682     }
14683     if (*prog == '"' || *prog == '\'') {
14684         q = strchr(prog + 1, *prog);
14685     } else {
14686         q = strchr(prog, ' ');
14687     }
14688     if (q == NULL) q = prog + strlen(prog);
14689     p = q;
14690     while (p >= prog && *p != '/' && *p != '\\') p--;
14691     p++;
14692     if(p == prog && *p == '"') p++;
14693     c = *q; *q = 0;
14694     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14695     memcpy(buf, p, q - p);
14696     buf[q - p] = NULLCHAR;
14697     if (!local) {
14698         strcat(buf, "@");
14699         strcat(buf, host);
14700     }
14701 }
14702
14703 char *
14704 TimeControlTagValue ()
14705 {
14706     char buf[MSG_SIZ];
14707     if (!appData.clockMode) {
14708       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14709     } else if (movesPerSession > 0) {
14710       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14711     } else if (timeIncrement == 0) {
14712       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14713     } else {
14714       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14715     }
14716     return StrSave(buf);
14717 }
14718
14719 void
14720 SetGameInfo ()
14721 {
14722     /* This routine is used only for certain modes */
14723     VariantClass v = gameInfo.variant;
14724     ChessMove r = GameUnfinished;
14725     char *p = NULL;
14726
14727     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14728         r = gameInfo.result;
14729         p = gameInfo.resultDetails;
14730         gameInfo.resultDetails = NULL;
14731     }
14732     ClearGameInfo(&gameInfo);
14733     gameInfo.variant = v;
14734
14735     switch (gameMode) {
14736       case MachinePlaysWhite:
14737         gameInfo.event = StrSave( appData.pgnEventHeader );
14738         gameInfo.site = StrSave(HostName());
14739         gameInfo.date = PGNDate();
14740         gameInfo.round = StrSave("-");
14741         gameInfo.white = StrSave(first.tidy);
14742         gameInfo.black = StrSave(UserName());
14743         gameInfo.timeControl = TimeControlTagValue();
14744         break;
14745
14746       case MachinePlaysBlack:
14747         gameInfo.event = StrSave( appData.pgnEventHeader );
14748         gameInfo.site = StrSave(HostName());
14749         gameInfo.date = PGNDate();
14750         gameInfo.round = StrSave("-");
14751         gameInfo.white = StrSave(UserName());
14752         gameInfo.black = StrSave(first.tidy);
14753         gameInfo.timeControl = TimeControlTagValue();
14754         break;
14755
14756       case TwoMachinesPlay:
14757         gameInfo.event = StrSave( appData.pgnEventHeader );
14758         gameInfo.site = StrSave(HostName());
14759         gameInfo.date = PGNDate();
14760         if (roundNr > 0) {
14761             char buf[MSG_SIZ];
14762             snprintf(buf, MSG_SIZ, "%d", roundNr);
14763             gameInfo.round = StrSave(buf);
14764         } else {
14765             gameInfo.round = StrSave("-");
14766         }
14767         if (first.twoMachinesColor[0] == 'w') {
14768             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14769             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14770         } else {
14771             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14772             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14773         }
14774         gameInfo.timeControl = TimeControlTagValue();
14775         break;
14776
14777       case EditGame:
14778         gameInfo.event = StrSave("Edited game");
14779         gameInfo.site = StrSave(HostName());
14780         gameInfo.date = PGNDate();
14781         gameInfo.round = StrSave("-");
14782         gameInfo.white = StrSave("-");
14783         gameInfo.black = StrSave("-");
14784         gameInfo.result = r;
14785         gameInfo.resultDetails = p;
14786         break;
14787
14788       case EditPosition:
14789         gameInfo.event = StrSave("Edited position");
14790         gameInfo.site = StrSave(HostName());
14791         gameInfo.date = PGNDate();
14792         gameInfo.round = StrSave("-");
14793         gameInfo.white = StrSave("-");
14794         gameInfo.black = StrSave("-");
14795         break;
14796
14797       case IcsPlayingWhite:
14798       case IcsPlayingBlack:
14799       case IcsObserving:
14800       case IcsExamining:
14801         break;
14802
14803       case PlayFromGameFile:
14804         gameInfo.event = StrSave("Game from non-PGN file");
14805         gameInfo.site = StrSave(HostName());
14806         gameInfo.date = PGNDate();
14807         gameInfo.round = StrSave("-");
14808         gameInfo.white = StrSave("?");
14809         gameInfo.black = StrSave("?");
14810         break;
14811
14812       default:
14813         break;
14814     }
14815 }
14816
14817 void
14818 ReplaceComment (int index, char *text)
14819 {
14820     int len;
14821     char *p;
14822     float score;
14823
14824     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14825        pvInfoList[index-1].depth == len &&
14826        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14827        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14828     while (*text == '\n') text++;
14829     len = strlen(text);
14830     while (len > 0 && text[len - 1] == '\n') len--;
14831
14832     if (commentList[index] != NULL)
14833       free(commentList[index]);
14834
14835     if (len == 0) {
14836         commentList[index] = NULL;
14837         return;
14838     }
14839   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14840       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14841       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14842     commentList[index] = (char *) malloc(len + 2);
14843     strncpy(commentList[index], text, len);
14844     commentList[index][len] = '\n';
14845     commentList[index][len + 1] = NULLCHAR;
14846   } else {
14847     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14848     char *p;
14849     commentList[index] = (char *) malloc(len + 7);
14850     safeStrCpy(commentList[index], "{\n", 3);
14851     safeStrCpy(commentList[index]+2, text, len+1);
14852     commentList[index][len+2] = NULLCHAR;
14853     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14854     strcat(commentList[index], "\n}\n");
14855   }
14856 }
14857
14858 void
14859 CrushCRs (char *text)
14860 {
14861   char *p = text;
14862   char *q = text;
14863   char ch;
14864
14865   do {
14866     ch = *p++;
14867     if (ch == '\r') continue;
14868     *q++ = ch;
14869   } while (ch != '\0');
14870 }
14871
14872 void
14873 AppendComment (int index, char *text, Boolean addBraces)
14874 /* addBraces  tells if we should add {} */
14875 {
14876     int oldlen, len;
14877     char *old;
14878
14879 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14880     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14881
14882     CrushCRs(text);
14883     while (*text == '\n') text++;
14884     len = strlen(text);
14885     while (len > 0 && text[len - 1] == '\n') len--;
14886     text[len] = NULLCHAR;
14887
14888     if (len == 0) return;
14889
14890     if (commentList[index] != NULL) {
14891       Boolean addClosingBrace = addBraces;
14892         old = commentList[index];
14893         oldlen = strlen(old);
14894         while(commentList[index][oldlen-1] ==  '\n')
14895           commentList[index][--oldlen] = NULLCHAR;
14896         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14897         safeStrCpy(commentList[index], old, oldlen + len + 6);
14898         free(old);
14899         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14900         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14901           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14902           while (*text == '\n') { text++; len--; }
14903           commentList[index][--oldlen] = NULLCHAR;
14904       }
14905         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14906         else          strcat(commentList[index], "\n");
14907         strcat(commentList[index], text);
14908         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14909         else          strcat(commentList[index], "\n");
14910     } else {
14911         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14912         if(addBraces)
14913           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14914         else commentList[index][0] = NULLCHAR;
14915         strcat(commentList[index], text);
14916         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14917         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14918     }
14919 }
14920
14921 static char *
14922 FindStr (char * text, char * sub_text)
14923 {
14924     char * result = strstr( text, sub_text );
14925
14926     if( result != NULL ) {
14927         result += strlen( sub_text );
14928     }
14929
14930     return result;
14931 }
14932
14933 /* [AS] Try to extract PV info from PGN comment */
14934 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14935 char *
14936 GetInfoFromComment (int index, char * text)
14937 {
14938     char * sep = text, *p;
14939
14940     if( text != NULL && index > 0 ) {
14941         int score = 0;
14942         int depth = 0;
14943         int time = -1, sec = 0, deci;
14944         char * s_eval = FindStr( text, "[%eval " );
14945         char * s_emt = FindStr( text, "[%emt " );
14946
14947         if( s_eval != NULL || s_emt != NULL ) {
14948             /* New style */
14949             char delim;
14950
14951             if( s_eval != NULL ) {
14952                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14953                     return text;
14954                 }
14955
14956                 if( delim != ']' ) {
14957                     return text;
14958                 }
14959             }
14960
14961             if( s_emt != NULL ) {
14962             }
14963                 return text;
14964         }
14965         else {
14966             /* We expect something like: [+|-]nnn.nn/dd */
14967             int score_lo = 0;
14968
14969             if(*text != '{') return text; // [HGM] braces: must be normal comment
14970
14971             sep = strchr( text, '/' );
14972             if( sep == NULL || sep < (text+4) ) {
14973                 return text;
14974             }
14975
14976             p = text;
14977             if(p[1] == '(') { // comment starts with PV
14978                p = strchr(p, ')'); // locate end of PV
14979                if(p == NULL || sep < p+5) return text;
14980                // at this point we have something like "{(.*) +0.23/6 ..."
14981                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14982                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14983                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14984             }
14985             time = -1; sec = -1; deci = -1;
14986             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14987                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14988                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14989                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14990                 return text;
14991             }
14992
14993             if( score_lo < 0 || score_lo >= 100 ) {
14994                 return text;
14995             }
14996
14997             if(sec >= 0) time = 600*time + 10*sec; else
14998             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14999
15000             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15001
15002             /* [HGM] PV time: now locate end of PV info */
15003             while( *++sep >= '0' && *sep <= '9'); // strip depth
15004             if(time >= 0)
15005             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15006             if(sec >= 0)
15007             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15008             if(deci >= 0)
15009             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15010             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15011         }
15012
15013         if( depth <= 0 ) {
15014             return text;
15015         }
15016
15017         if( time < 0 ) {
15018             time = -1;
15019         }
15020
15021         pvInfoList[index-1].depth = depth;
15022         pvInfoList[index-1].score = score;
15023         pvInfoList[index-1].time  = 10*time; // centi-sec
15024         if(*sep == '}') *sep = 0; else *--sep = '{';
15025         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15026     }
15027     return sep;
15028 }
15029
15030 void
15031 SendToProgram (char *message, ChessProgramState *cps)
15032 {
15033     int count, outCount, error;
15034     char buf[MSG_SIZ];
15035
15036     if (cps->pr == NoProc) return;
15037     Attention(cps);
15038
15039     if (appData.debugMode) {
15040         TimeMark now;
15041         GetTimeMark(&now);
15042         fprintf(debugFP, "%ld >%-6s: %s",
15043                 SubtractTimeMarks(&now, &programStartTime),
15044                 cps->which, message);
15045     }
15046
15047     count = strlen(message);
15048     outCount = OutputToProcess(cps->pr, message, count, &error);
15049     if (outCount < count && !exiting
15050                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15051       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15052       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15053         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15054             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15055                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15056                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15057                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15058             } else {
15059                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15060                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15061                 gameInfo.result = res;
15062             }
15063             gameInfo.resultDetails = StrSave(buf);
15064         }
15065         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15066         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15067     }
15068 }
15069
15070 void
15071 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15072 {
15073     char *end_str;
15074     char buf[MSG_SIZ];
15075     ChessProgramState *cps = (ChessProgramState *)closure;
15076
15077     if (isr != cps->isr) return; /* Killed intentionally */
15078     if (count <= 0) {
15079         if (count == 0) {
15080             RemoveInputSource(cps->isr);
15081             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15082             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15083                     _(cps->which), cps->program);
15084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15085                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15086                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15087                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15088                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15089                 } else {
15090                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15091                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15092                     gameInfo.result = res;
15093                 }
15094                 gameInfo.resultDetails = StrSave(buf);
15095             }
15096             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15097             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15098         } else {
15099             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15100                     _(cps->which), cps->program);
15101             RemoveInputSource(cps->isr);
15102
15103             /* [AS] Program is misbehaving badly... kill it */
15104             if( count == -2 ) {
15105                 DestroyChildProcess( cps->pr, 9 );
15106                 cps->pr = NoProc;
15107             }
15108
15109             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15110         }
15111         return;
15112     }
15113
15114     if ((end_str = strchr(message, '\r')) != NULL)
15115       *end_str = NULLCHAR;
15116     if ((end_str = strchr(message, '\n')) != NULL)
15117       *end_str = NULLCHAR;
15118
15119     if (appData.debugMode) {
15120         TimeMark now; int print = 1;
15121         char *quote = ""; char c; int i;
15122
15123         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15124                 char start = message[0];
15125                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15126                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15127                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15128                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15129                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15130                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15131                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15132                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15133                    sscanf(message, "hint: %c", &c)!=1 && 
15134                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15135                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15136                     print = (appData.engineComments >= 2);
15137                 }
15138                 message[0] = start; // restore original message
15139         }
15140         if(print) {
15141                 GetTimeMark(&now);
15142                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15143                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15144                         quote,
15145                         message);
15146         }
15147     }
15148
15149     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15150     if (appData.icsEngineAnalyze) {
15151         if (strstr(message, "whisper") != NULL ||
15152              strstr(message, "kibitz") != NULL ||
15153             strstr(message, "tellics") != NULL) return;
15154     }
15155
15156     HandleMachineMove(message, cps);
15157 }
15158
15159
15160 void
15161 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15162 {
15163     char buf[MSG_SIZ];
15164     int seconds;
15165
15166     if( timeControl_2 > 0 ) {
15167         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15168             tc = timeControl_2;
15169         }
15170     }
15171     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15172     inc /= cps->timeOdds;
15173     st  /= cps->timeOdds;
15174
15175     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15176
15177     if (st > 0) {
15178       /* Set exact time per move, normally using st command */
15179       if (cps->stKludge) {
15180         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15181         seconds = st % 60;
15182         if (seconds == 0) {
15183           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15184         } else {
15185           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15186         }
15187       } else {
15188         snprintf(buf, MSG_SIZ, "st %d\n", st);
15189       }
15190     } else {
15191       /* Set conventional or incremental time control, using level command */
15192       if (seconds == 0) {
15193         /* Note old gnuchess bug -- minutes:seconds used to not work.
15194            Fixed in later versions, but still avoid :seconds
15195            when seconds is 0. */
15196         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15197       } else {
15198         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15199                  seconds, inc/1000.);
15200       }
15201     }
15202     SendToProgram(buf, cps);
15203
15204     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15205     /* Orthogonally, limit search to given depth */
15206     if (sd > 0) {
15207       if (cps->sdKludge) {
15208         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15209       } else {
15210         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15211       }
15212       SendToProgram(buf, cps);
15213     }
15214
15215     if(cps->nps >= 0) { /* [HGM] nps */
15216         if(cps->supportsNPS == FALSE)
15217           cps->nps = -1; // don't use if engine explicitly says not supported!
15218         else {
15219           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15220           SendToProgram(buf, cps);
15221         }
15222     }
15223 }
15224
15225 ChessProgramState *
15226 WhitePlayer ()
15227 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15228 {
15229     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15230        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15231         return &second;
15232     return &first;
15233 }
15234
15235 void
15236 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15237 {
15238     char message[MSG_SIZ];
15239     long time, otime;
15240
15241     /* Note: this routine must be called when the clocks are stopped
15242        or when they have *just* been set or switched; otherwise
15243        it will be off by the time since the current tick started.
15244     */
15245     if (machineWhite) {
15246         time = whiteTimeRemaining / 10;
15247         otime = blackTimeRemaining / 10;
15248     } else {
15249         time = blackTimeRemaining / 10;
15250         otime = whiteTimeRemaining / 10;
15251     }
15252     /* [HGM] translate opponent's time by time-odds factor */
15253     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15254     if (appData.debugMode) {
15255         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15256     }
15257
15258     if (time <= 0) time = 1;
15259     if (otime <= 0) otime = 1;
15260
15261     snprintf(message, MSG_SIZ, "time %ld\n", time);
15262     SendToProgram(message, cps);
15263
15264     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15265     SendToProgram(message, cps);
15266 }
15267
15268 int
15269 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15270 {
15271   char buf[MSG_SIZ];
15272   int len = strlen(name);
15273   int val;
15274
15275   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15276     (*p) += len + 1;
15277     sscanf(*p, "%d", &val);
15278     *loc = (val != 0);
15279     while (**p && **p != ' ')
15280       (*p)++;
15281     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15282     SendToProgram(buf, cps);
15283     return TRUE;
15284   }
15285   return FALSE;
15286 }
15287
15288 int
15289 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15290 {
15291   char buf[MSG_SIZ];
15292   int len = strlen(name);
15293   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15294     (*p) += len + 1;
15295     sscanf(*p, "%d", loc);
15296     while (**p && **p != ' ') (*p)++;
15297     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15298     SendToProgram(buf, cps);
15299     return TRUE;
15300   }
15301   return FALSE;
15302 }
15303
15304 int
15305 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15306 {
15307   char buf[MSG_SIZ];
15308   int len = strlen(name);
15309   if (strncmp((*p), name, len) == 0
15310       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15311     (*p) += len + 2;
15312     sscanf(*p, "%[^\"]", loc);
15313     while (**p && **p != '\"') (*p)++;
15314     if (**p == '\"') (*p)++;
15315     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15316     SendToProgram(buf, cps);
15317     return TRUE;
15318   }
15319   return FALSE;
15320 }
15321
15322 int
15323 ParseOption (Option *opt, ChessProgramState *cps)
15324 // [HGM] options: process the string that defines an engine option, and determine
15325 // name, type, default value, and allowed value range
15326 {
15327         char *p, *q, buf[MSG_SIZ];
15328         int n, min = (-1)<<31, max = 1<<31, def;
15329
15330         if(p = strstr(opt->name, " -spin ")) {
15331             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15332             if(max < min) max = min; // enforce consistency
15333             if(def < min) def = min;
15334             if(def > max) def = max;
15335             opt->value = def;
15336             opt->min = min;
15337             opt->max = max;
15338             opt->type = Spin;
15339         } else if((p = strstr(opt->name, " -slider "))) {
15340             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15341             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15342             if(max < min) max = min; // enforce consistency
15343             if(def < min) def = min;
15344             if(def > max) def = max;
15345             opt->value = def;
15346             opt->min = min;
15347             opt->max = max;
15348             opt->type = Spin; // Slider;
15349         } else if((p = strstr(opt->name, " -string "))) {
15350             opt->textValue = p+9;
15351             opt->type = TextBox;
15352         } else if((p = strstr(opt->name, " -file "))) {
15353             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15354             opt->textValue = p+7;
15355             opt->type = FileName; // FileName;
15356         } else if((p = strstr(opt->name, " -path "))) {
15357             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15358             opt->textValue = p+7;
15359             opt->type = PathName; // PathName;
15360         } else if(p = strstr(opt->name, " -check ")) {
15361             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15362             opt->value = (def != 0);
15363             opt->type = CheckBox;
15364         } else if(p = strstr(opt->name, " -combo ")) {
15365             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15366             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15367             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15368             opt->value = n = 0;
15369             while(q = StrStr(q, " /// ")) {
15370                 n++; *q = 0;    // count choices, and null-terminate each of them
15371                 q += 5;
15372                 if(*q == '*') { // remember default, which is marked with * prefix
15373                     q++;
15374                     opt->value = n;
15375                 }
15376                 cps->comboList[cps->comboCnt++] = q;
15377             }
15378             cps->comboList[cps->comboCnt++] = NULL;
15379             opt->max = n + 1;
15380             opt->type = ComboBox;
15381         } else if(p = strstr(opt->name, " -button")) {
15382             opt->type = Button;
15383         } else if(p = strstr(opt->name, " -save")) {
15384             opt->type = SaveButton;
15385         } else return FALSE;
15386         *p = 0; // terminate option name
15387         // now look if the command-line options define a setting for this engine option.
15388         if(cps->optionSettings && cps->optionSettings[0])
15389             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15390         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15391           snprintf(buf, MSG_SIZ, "option %s", p);
15392                 if(p = strstr(buf, ",")) *p = 0;
15393                 if(q = strchr(buf, '=')) switch(opt->type) {
15394                     case ComboBox:
15395                         for(n=0; n<opt->max; n++)
15396                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15397                         break;
15398                     case TextBox:
15399                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15400                         break;
15401                     case Spin:
15402                     case CheckBox:
15403                         opt->value = atoi(q+1);
15404                     default:
15405                         break;
15406                 }
15407                 strcat(buf, "\n");
15408                 SendToProgram(buf, cps);
15409         }
15410         return TRUE;
15411 }
15412
15413 void
15414 FeatureDone (ChessProgramState *cps, int val)
15415 {
15416   DelayedEventCallback cb = GetDelayedEvent();
15417   if ((cb == InitBackEnd3 && cps == &first) ||
15418       (cb == SettingsMenuIfReady && cps == &second) ||
15419       (cb == LoadEngine) ||
15420       (cb == TwoMachinesEventIfReady)) {
15421     CancelDelayedEvent();
15422     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15423   }
15424   cps->initDone = val;
15425 }
15426
15427 /* Parse feature command from engine */
15428 void
15429 ParseFeatures (char *args, ChessProgramState *cps)
15430 {
15431   char *p = args;
15432   char *q;
15433   int val;
15434   char buf[MSG_SIZ];
15435
15436   for (;;) {
15437     while (*p == ' ') p++;
15438     if (*p == NULLCHAR) return;
15439
15440     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15441     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15442     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15443     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15444     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15445     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15446     if (BoolFeature(&p, "reuse", &val, cps)) {
15447       /* Engine can disable reuse, but can't enable it if user said no */
15448       if (!val) cps->reuse = FALSE;
15449       continue;
15450     }
15451     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15452     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15453       if (gameMode == TwoMachinesPlay) {
15454         DisplayTwoMachinesTitle();
15455       } else {
15456         DisplayTitle("");
15457       }
15458       continue;
15459     }
15460     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15461     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15462     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15463     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15464     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15465     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15466     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15467     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15468     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15469     if (IntFeature(&p, "done", &val, cps)) {
15470       FeatureDone(cps, val);
15471       continue;
15472     }
15473     /* Added by Tord: */
15474     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15475     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15476     /* End of additions by Tord */
15477
15478     /* [HGM] added features: */
15479     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15480     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15481     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15482     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15483     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15484     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15485     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15486         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15487           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15488             SendToProgram(buf, cps);
15489             continue;
15490         }
15491         if(cps->nrOptions >= MAX_OPTIONS) {
15492             cps->nrOptions--;
15493             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15494             DisplayError(buf, 0);
15495         }
15496         continue;
15497     }
15498     /* End of additions by HGM */
15499
15500     /* unknown feature: complain and skip */
15501     q = p;
15502     while (*q && *q != '=') q++;
15503     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15504     SendToProgram(buf, cps);
15505     p = q;
15506     if (*p == '=') {
15507       p++;
15508       if (*p == '\"') {
15509         p++;
15510         while (*p && *p != '\"') p++;
15511         if (*p == '\"') p++;
15512       } else {
15513         while (*p && *p != ' ') p++;
15514       }
15515     }
15516   }
15517
15518 }
15519
15520 void
15521 PeriodicUpdatesEvent (int newState)
15522 {
15523     if (newState == appData.periodicUpdates)
15524       return;
15525
15526     appData.periodicUpdates=newState;
15527
15528     /* Display type changes, so update it now */
15529 //    DisplayAnalysis();
15530
15531     /* Get the ball rolling again... */
15532     if (newState) {
15533         AnalysisPeriodicEvent(1);
15534         StartAnalysisClock();
15535     }
15536 }
15537
15538 void
15539 PonderNextMoveEvent (int newState)
15540 {
15541     if (newState == appData.ponderNextMove) return;
15542     if (gameMode == EditPosition) EditPositionDone(TRUE);
15543     if (newState) {
15544         SendToProgram("hard\n", &first);
15545         if (gameMode == TwoMachinesPlay) {
15546             SendToProgram("hard\n", &second);
15547         }
15548     } else {
15549         SendToProgram("easy\n", &first);
15550         thinkOutput[0] = NULLCHAR;
15551         if (gameMode == TwoMachinesPlay) {
15552             SendToProgram("easy\n", &second);
15553         }
15554     }
15555     appData.ponderNextMove = newState;
15556 }
15557
15558 void
15559 NewSettingEvent (int option, int *feature, char *command, int value)
15560 {
15561     char buf[MSG_SIZ];
15562
15563     if (gameMode == EditPosition) EditPositionDone(TRUE);
15564     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15565     if(feature == NULL || *feature) SendToProgram(buf, &first);
15566     if (gameMode == TwoMachinesPlay) {
15567         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15568     }
15569 }
15570
15571 void
15572 ShowThinkingEvent ()
15573 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15574 {
15575     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15576     int newState = appData.showThinking
15577         // [HGM] thinking: other features now need thinking output as well
15578         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15579
15580     if (oldState == newState) return;
15581     oldState = newState;
15582     if (gameMode == EditPosition) EditPositionDone(TRUE);
15583     if (oldState) {
15584         SendToProgram("post\n", &first);
15585         if (gameMode == TwoMachinesPlay) {
15586             SendToProgram("post\n", &second);
15587         }
15588     } else {
15589         SendToProgram("nopost\n", &first);
15590         thinkOutput[0] = NULLCHAR;
15591         if (gameMode == TwoMachinesPlay) {
15592             SendToProgram("nopost\n", &second);
15593         }
15594     }
15595 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15596 }
15597
15598 void
15599 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15600 {
15601   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15602   if (pr == NoProc) return;
15603   AskQuestion(title, question, replyPrefix, pr);
15604 }
15605
15606 void
15607 TypeInEvent (char firstChar)
15608 {
15609     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15610         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15611         gameMode == AnalyzeMode || gameMode == EditGame || 
15612         gameMode == EditPosition || gameMode == IcsExamining ||
15613         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15614         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15615                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15616                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15617         gameMode == Training) PopUpMoveDialog(firstChar);
15618 }
15619
15620 void
15621 TypeInDoneEvent (char *move)
15622 {
15623         Board board;
15624         int n, fromX, fromY, toX, toY;
15625         char promoChar;
15626         ChessMove moveType;
15627
15628         // [HGM] FENedit
15629         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15630                 EditPositionPasteFEN(move);
15631                 return;
15632         }
15633         // [HGM] movenum: allow move number to be typed in any mode
15634         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15635           ToNrEvent(2*n-1);
15636           return;
15637         }
15638
15639       if (gameMode != EditGame && currentMove != forwardMostMove && 
15640         gameMode != Training) {
15641         DisplayMoveError(_("Displayed move is not current"));
15642       } else {
15643         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15644           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15645         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15646         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15647           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15648           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15649         } else {
15650           DisplayMoveError(_("Could not parse move"));
15651         }
15652       }
15653 }
15654
15655 void
15656 DisplayMove (int moveNumber)
15657 {
15658     char message[MSG_SIZ];
15659     char res[MSG_SIZ];
15660     char cpThinkOutput[MSG_SIZ];
15661
15662     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15663
15664     if (moveNumber == forwardMostMove - 1 ||
15665         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15666
15667         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15668
15669         if (strchr(cpThinkOutput, '\n')) {
15670             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15671         }
15672     } else {
15673         *cpThinkOutput = NULLCHAR;
15674     }
15675
15676     /* [AS] Hide thinking from human user */
15677     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15678         *cpThinkOutput = NULLCHAR;
15679         if( thinkOutput[0] != NULLCHAR ) {
15680             int i;
15681
15682             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15683                 cpThinkOutput[i] = '.';
15684             }
15685             cpThinkOutput[i] = NULLCHAR;
15686             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15687         }
15688     }
15689
15690     if (moveNumber == forwardMostMove - 1 &&
15691         gameInfo.resultDetails != NULL) {
15692         if (gameInfo.resultDetails[0] == NULLCHAR) {
15693           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15694         } else {
15695           snprintf(res, MSG_SIZ, " {%s} %s",
15696                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15697         }
15698     } else {
15699         res[0] = NULLCHAR;
15700     }
15701
15702     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15703         DisplayMessage(res, cpThinkOutput);
15704     } else {
15705       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15706                 WhiteOnMove(moveNumber) ? " " : ".. ",
15707                 parseList[moveNumber], res);
15708         DisplayMessage(message, cpThinkOutput);
15709     }
15710 }
15711
15712 void
15713 DisplayComment (int moveNumber, char *text)
15714 {
15715     char title[MSG_SIZ];
15716
15717     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15718       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15719     } else {
15720       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15721               WhiteOnMove(moveNumber) ? " " : ".. ",
15722               parseList[moveNumber]);
15723     }
15724     if (text != NULL && (appData.autoDisplayComment || commentUp))
15725         CommentPopUp(title, text);
15726 }
15727
15728 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15729  * might be busy thinking or pondering.  It can be omitted if your
15730  * gnuchess is configured to stop thinking immediately on any user
15731  * input.  However, that gnuchess feature depends on the FIONREAD
15732  * ioctl, which does not work properly on some flavors of Unix.
15733  */
15734 void
15735 Attention (ChessProgramState *cps)
15736 {
15737 #if ATTENTION
15738     if (!cps->useSigint) return;
15739     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15740     switch (gameMode) {
15741       case MachinePlaysWhite:
15742       case MachinePlaysBlack:
15743       case TwoMachinesPlay:
15744       case IcsPlayingWhite:
15745       case IcsPlayingBlack:
15746       case AnalyzeMode:
15747       case AnalyzeFile:
15748         /* Skip if we know it isn't thinking */
15749         if (!cps->maybeThinking) return;
15750         if (appData.debugMode)
15751           fprintf(debugFP, "Interrupting %s\n", cps->which);
15752         InterruptChildProcess(cps->pr);
15753         cps->maybeThinking = FALSE;
15754         break;
15755       default:
15756         break;
15757     }
15758 #endif /*ATTENTION*/
15759 }
15760
15761 int
15762 CheckFlags ()
15763 {
15764     if (whiteTimeRemaining <= 0) {
15765         if (!whiteFlag) {
15766             whiteFlag = TRUE;
15767             if (appData.icsActive) {
15768                 if (appData.autoCallFlag &&
15769                     gameMode == IcsPlayingBlack && !blackFlag) {
15770                   SendToICS(ics_prefix);
15771                   SendToICS("flag\n");
15772                 }
15773             } else {
15774                 if (blackFlag) {
15775                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15776                 } else {
15777                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15778                     if (appData.autoCallFlag) {
15779                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15780                         return TRUE;
15781                     }
15782                 }
15783             }
15784         }
15785     }
15786     if (blackTimeRemaining <= 0) {
15787         if (!blackFlag) {
15788             blackFlag = TRUE;
15789             if (appData.icsActive) {
15790                 if (appData.autoCallFlag &&
15791                     gameMode == IcsPlayingWhite && !whiteFlag) {
15792                   SendToICS(ics_prefix);
15793                   SendToICS("flag\n");
15794                 }
15795             } else {
15796                 if (whiteFlag) {
15797                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15798                 } else {
15799                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15800                     if (appData.autoCallFlag) {
15801                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15802                         return TRUE;
15803                     }
15804                 }
15805             }
15806         }
15807     }
15808     return FALSE;
15809 }
15810
15811 void
15812 CheckTimeControl ()
15813 {
15814     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15815         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15816
15817     /*
15818      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15819      */
15820     if ( !WhiteOnMove(forwardMostMove) ) {
15821         /* White made time control */
15822         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15823         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15824         /* [HGM] time odds: correct new time quota for time odds! */
15825                                             / WhitePlayer()->timeOdds;
15826         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15827     } else {
15828         lastBlack -= blackTimeRemaining;
15829         /* Black made time control */
15830         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15831                                             / WhitePlayer()->other->timeOdds;
15832         lastWhite = whiteTimeRemaining;
15833     }
15834 }
15835
15836 void
15837 DisplayBothClocks ()
15838 {
15839     int wom = gameMode == EditPosition ?
15840       !blackPlaysFirst : WhiteOnMove(currentMove);
15841     DisplayWhiteClock(whiteTimeRemaining, wom);
15842     DisplayBlackClock(blackTimeRemaining, !wom);
15843 }
15844
15845
15846 /* Timekeeping seems to be a portability nightmare.  I think everyone
15847    has ftime(), but I'm really not sure, so I'm including some ifdefs
15848    to use other calls if you don't.  Clocks will be less accurate if
15849    you have neither ftime nor gettimeofday.
15850 */
15851
15852 /* VS 2008 requires the #include outside of the function */
15853 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15854 #include <sys/timeb.h>
15855 #endif
15856
15857 /* Get the current time as a TimeMark */
15858 void
15859 GetTimeMark (TimeMark *tm)
15860 {
15861 #if HAVE_GETTIMEOFDAY
15862
15863     struct timeval timeVal;
15864     struct timezone timeZone;
15865
15866     gettimeofday(&timeVal, &timeZone);
15867     tm->sec = (long) timeVal.tv_sec;
15868     tm->ms = (int) (timeVal.tv_usec / 1000L);
15869
15870 #else /*!HAVE_GETTIMEOFDAY*/
15871 #if HAVE_FTIME
15872
15873 // include <sys/timeb.h> / moved to just above start of function
15874     struct timeb timeB;
15875
15876     ftime(&timeB);
15877     tm->sec = (long) timeB.time;
15878     tm->ms = (int) timeB.millitm;
15879
15880 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15881     tm->sec = (long) time(NULL);
15882     tm->ms = 0;
15883 #endif
15884 #endif
15885 }
15886
15887 /* Return the difference in milliseconds between two
15888    time marks.  We assume the difference will fit in a long!
15889 */
15890 long
15891 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15892 {
15893     return 1000L*(tm2->sec - tm1->sec) +
15894            (long) (tm2->ms - tm1->ms);
15895 }
15896
15897
15898 /*
15899  * Code to manage the game clocks.
15900  *
15901  * In tournament play, black starts the clock and then white makes a move.
15902  * We give the human user a slight advantage if he is playing white---the
15903  * clocks don't run until he makes his first move, so it takes zero time.
15904  * Also, we don't account for network lag, so we could get out of sync
15905  * with GNU Chess's clock -- but then, referees are always right.
15906  */
15907
15908 static TimeMark tickStartTM;
15909 static long intendedTickLength;
15910
15911 long
15912 NextTickLength (long timeRemaining)
15913 {
15914     long nominalTickLength, nextTickLength;
15915
15916     if (timeRemaining > 0L && timeRemaining <= 10000L)
15917       nominalTickLength = 100L;
15918     else
15919       nominalTickLength = 1000L;
15920     nextTickLength = timeRemaining % nominalTickLength;
15921     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15922
15923     return nextTickLength;
15924 }
15925
15926 /* Adjust clock one minute up or down */
15927 void
15928 AdjustClock (Boolean which, int dir)
15929 {
15930     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15931     if(which) blackTimeRemaining += 60000*dir;
15932     else      whiteTimeRemaining += 60000*dir;
15933     DisplayBothClocks();
15934     adjustedClock = TRUE;
15935 }
15936
15937 /* Stop clocks and reset to a fresh time control */
15938 void
15939 ResetClocks ()
15940 {
15941     (void) StopClockTimer();
15942     if (appData.icsActive) {
15943         whiteTimeRemaining = blackTimeRemaining = 0;
15944     } else if (searchTime) {
15945         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15946         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15947     } else { /* [HGM] correct new time quote for time odds */
15948         whiteTC = blackTC = fullTimeControlString;
15949         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15950         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15951     }
15952     if (whiteFlag || blackFlag) {
15953         DisplayTitle("");
15954         whiteFlag = blackFlag = FALSE;
15955     }
15956     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15957     DisplayBothClocks();
15958     adjustedClock = FALSE;
15959 }
15960
15961 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15962
15963 /* Decrement running clock by amount of time that has passed */
15964 void
15965 DecrementClocks ()
15966 {
15967     long timeRemaining;
15968     long lastTickLength, fudge;
15969     TimeMark now;
15970
15971     if (!appData.clockMode) return;
15972     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15973
15974     GetTimeMark(&now);
15975
15976     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15977
15978     /* Fudge if we woke up a little too soon */
15979     fudge = intendedTickLength - lastTickLength;
15980     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15981
15982     if (WhiteOnMove(forwardMostMove)) {
15983         if(whiteNPS >= 0) lastTickLength = 0;
15984         timeRemaining = whiteTimeRemaining -= lastTickLength;
15985         if(timeRemaining < 0 && !appData.icsActive) {
15986             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15987             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15988                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15989                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15990             }
15991         }
15992         DisplayWhiteClock(whiteTimeRemaining - fudge,
15993                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15994     } else {
15995         if(blackNPS >= 0) lastTickLength = 0;
15996         timeRemaining = blackTimeRemaining -= lastTickLength;
15997         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15998             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15999             if(suddenDeath) {
16000                 blackStartMove = forwardMostMove;
16001                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16002             }
16003         }
16004         DisplayBlackClock(blackTimeRemaining - fudge,
16005                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16006     }
16007     if (CheckFlags()) return;
16008
16009     tickStartTM = now;
16010     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16011     StartClockTimer(intendedTickLength);
16012
16013     /* if the time remaining has fallen below the alarm threshold, sound the
16014      * alarm. if the alarm has sounded and (due to a takeback or time control
16015      * with increment) the time remaining has increased to a level above the
16016      * threshold, reset the alarm so it can sound again.
16017      */
16018
16019     if (appData.icsActive && appData.icsAlarm) {
16020
16021         /* make sure we are dealing with the user's clock */
16022         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16023                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16024            )) return;
16025
16026         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16027             alarmSounded = FALSE;
16028         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16029             PlayAlarmSound();
16030             alarmSounded = TRUE;
16031         }
16032     }
16033 }
16034
16035
16036 /* A player has just moved, so stop the previously running
16037    clock and (if in clock mode) start the other one.
16038    We redisplay both clocks in case we're in ICS mode, because
16039    ICS gives us an update to both clocks after every move.
16040    Note that this routine is called *after* forwardMostMove
16041    is updated, so the last fractional tick must be subtracted
16042    from the color that is *not* on move now.
16043 */
16044 void
16045 SwitchClocks (int newMoveNr)
16046 {
16047     long lastTickLength;
16048     TimeMark now;
16049     int flagged = FALSE;
16050
16051     GetTimeMark(&now);
16052
16053     if (StopClockTimer() && appData.clockMode) {
16054         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16055         if (!WhiteOnMove(forwardMostMove)) {
16056             if(blackNPS >= 0) lastTickLength = 0;
16057             blackTimeRemaining -= lastTickLength;
16058            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16059 //         if(pvInfoList[forwardMostMove].time == -1)
16060                  pvInfoList[forwardMostMove].time =               // use GUI time
16061                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16062         } else {
16063            if(whiteNPS >= 0) lastTickLength = 0;
16064            whiteTimeRemaining -= lastTickLength;
16065            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16066 //         if(pvInfoList[forwardMostMove].time == -1)
16067                  pvInfoList[forwardMostMove].time =
16068                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16069         }
16070         flagged = CheckFlags();
16071     }
16072     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16073     CheckTimeControl();
16074
16075     if (flagged || !appData.clockMode) return;
16076
16077     switch (gameMode) {
16078       case MachinePlaysBlack:
16079       case MachinePlaysWhite:
16080       case BeginningOfGame:
16081         if (pausing) return;
16082         break;
16083
16084       case EditGame:
16085       case PlayFromGameFile:
16086       case IcsExamining:
16087         return;
16088
16089       default:
16090         break;
16091     }
16092
16093     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16094         if(WhiteOnMove(forwardMostMove))
16095              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16096         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16097     }
16098
16099     tickStartTM = now;
16100     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16101       whiteTimeRemaining : blackTimeRemaining);
16102     StartClockTimer(intendedTickLength);
16103 }
16104
16105
16106 /* Stop both clocks */
16107 void
16108 StopClocks ()
16109 {
16110     long lastTickLength;
16111     TimeMark now;
16112
16113     if (!StopClockTimer()) return;
16114     if (!appData.clockMode) return;
16115
16116     GetTimeMark(&now);
16117
16118     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16119     if (WhiteOnMove(forwardMostMove)) {
16120         if(whiteNPS >= 0) lastTickLength = 0;
16121         whiteTimeRemaining -= lastTickLength;
16122         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16123     } else {
16124         if(blackNPS >= 0) lastTickLength = 0;
16125         blackTimeRemaining -= lastTickLength;
16126         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16127     }
16128     CheckFlags();
16129 }
16130
16131 /* Start clock of player on move.  Time may have been reset, so
16132    if clock is already running, stop and restart it. */
16133 void
16134 StartClocks ()
16135 {
16136     (void) StopClockTimer(); /* in case it was running already */
16137     DisplayBothClocks();
16138     if (CheckFlags()) return;
16139
16140     if (!appData.clockMode) return;
16141     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16142
16143     GetTimeMark(&tickStartTM);
16144     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16145       whiteTimeRemaining : blackTimeRemaining);
16146
16147    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16148     whiteNPS = blackNPS = -1;
16149     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16150        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16151         whiteNPS = first.nps;
16152     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16153        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16154         blackNPS = first.nps;
16155     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16156         whiteNPS = second.nps;
16157     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16158         blackNPS = second.nps;
16159     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16160
16161     StartClockTimer(intendedTickLength);
16162 }
16163
16164 char *
16165 TimeString (long ms)
16166 {
16167     long second, minute, hour, day;
16168     char *sign = "";
16169     static char buf[32];
16170
16171     if (ms > 0 && ms <= 9900) {
16172       /* convert milliseconds to tenths, rounding up */
16173       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16174
16175       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16176       return buf;
16177     }
16178
16179     /* convert milliseconds to seconds, rounding up */
16180     /* use floating point to avoid strangeness of integer division
16181        with negative dividends on many machines */
16182     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16183
16184     if (second < 0) {
16185         sign = "-";
16186         second = -second;
16187     }
16188
16189     day = second / (60 * 60 * 24);
16190     second = second % (60 * 60 * 24);
16191     hour = second / (60 * 60);
16192     second = second % (60 * 60);
16193     minute = second / 60;
16194     second = second % 60;
16195
16196     if (day > 0)
16197       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16198               sign, day, hour, minute, second);
16199     else if (hour > 0)
16200       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16201     else
16202       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16203
16204     return buf;
16205 }
16206
16207
16208 /*
16209  * This is necessary because some C libraries aren't ANSI C compliant yet.
16210  */
16211 char *
16212 StrStr (char *string, char *match)
16213 {
16214     int i, length;
16215
16216     length = strlen(match);
16217
16218     for (i = strlen(string) - length; i >= 0; i--, string++)
16219       if (!strncmp(match, string, length))
16220         return string;
16221
16222     return NULL;
16223 }
16224
16225 char *
16226 StrCaseStr (char *string, char *match)
16227 {
16228     int i, j, length;
16229
16230     length = strlen(match);
16231
16232     for (i = strlen(string) - length; i >= 0; i--, string++) {
16233         for (j = 0; j < length; j++) {
16234             if (ToLower(match[j]) != ToLower(string[j]))
16235               break;
16236         }
16237         if (j == length) return string;
16238     }
16239
16240     return NULL;
16241 }
16242
16243 #ifndef _amigados
16244 int
16245 StrCaseCmp (char *s1, char *s2)
16246 {
16247     char c1, c2;
16248
16249     for (;;) {
16250         c1 = ToLower(*s1++);
16251         c2 = ToLower(*s2++);
16252         if (c1 > c2) return 1;
16253         if (c1 < c2) return -1;
16254         if (c1 == NULLCHAR) return 0;
16255     }
16256 }
16257
16258
16259 int
16260 ToLower (int c)
16261 {
16262     return isupper(c) ? tolower(c) : c;
16263 }
16264
16265
16266 int
16267 ToUpper (int c)
16268 {
16269     return islower(c) ? toupper(c) : c;
16270 }
16271 #endif /* !_amigados    */
16272
16273 char *
16274 StrSave (char *s)
16275 {
16276   char *ret;
16277
16278   if ((ret = (char *) malloc(strlen(s) + 1)))
16279     {
16280       safeStrCpy(ret, s, strlen(s)+1);
16281     }
16282   return ret;
16283 }
16284
16285 char *
16286 StrSavePtr (char *s, char **savePtr)
16287 {
16288     if (*savePtr) {
16289         free(*savePtr);
16290     }
16291     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16292       safeStrCpy(*savePtr, s, strlen(s)+1);
16293     }
16294     return(*savePtr);
16295 }
16296
16297 char *
16298 PGNDate ()
16299 {
16300     time_t clock;
16301     struct tm *tm;
16302     char buf[MSG_SIZ];
16303
16304     clock = time((time_t *)NULL);
16305     tm = localtime(&clock);
16306     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16307             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16308     return StrSave(buf);
16309 }
16310
16311
16312 char *
16313 PositionToFEN (int move, char *overrideCastling)
16314 {
16315     int i, j, fromX, fromY, toX, toY;
16316     int whiteToPlay;
16317     char buf[MSG_SIZ];
16318     char *p, *q;
16319     int emptycount;
16320     ChessSquare piece;
16321
16322     whiteToPlay = (gameMode == EditPosition) ?
16323       !blackPlaysFirst : (move % 2 == 0);
16324     p = buf;
16325
16326     /* Piece placement data */
16327     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16328         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16329         emptycount = 0;
16330         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16331             if (boards[move][i][j] == EmptySquare) {
16332                 emptycount++;
16333             } else { ChessSquare piece = boards[move][i][j];
16334                 if (emptycount > 0) {
16335                     if(emptycount<10) /* [HGM] can be >= 10 */
16336                         *p++ = '0' + emptycount;
16337                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16338                     emptycount = 0;
16339                 }
16340                 if(PieceToChar(piece) == '+') {
16341                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16342                     *p++ = '+';
16343                     piece = (ChessSquare)(DEMOTED piece);
16344                 }
16345                 *p++ = PieceToChar(piece);
16346                 if(p[-1] == '~') {
16347                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16348                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16349                     *p++ = '~';
16350                 }
16351             }
16352         }
16353         if (emptycount > 0) {
16354             if(emptycount<10) /* [HGM] can be >= 10 */
16355                 *p++ = '0' + emptycount;
16356             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16357             emptycount = 0;
16358         }
16359         *p++ = '/';
16360     }
16361     *(p - 1) = ' ';
16362
16363     /* [HGM] print Crazyhouse or Shogi holdings */
16364     if( gameInfo.holdingsWidth ) {
16365         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16366         q = p;
16367         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16368             piece = boards[move][i][BOARD_WIDTH-1];
16369             if( piece != EmptySquare )
16370               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16371                   *p++ = PieceToChar(piece);
16372         }
16373         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16374             piece = boards[move][BOARD_HEIGHT-i-1][0];
16375             if( piece != EmptySquare )
16376               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16377                   *p++ = PieceToChar(piece);
16378         }
16379
16380         if( q == p ) *p++ = '-';
16381         *p++ = ']';
16382         *p++ = ' ';
16383     }
16384
16385     /* Active color */
16386     *p++ = whiteToPlay ? 'w' : 'b';
16387     *p++ = ' ';
16388
16389   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16390     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16391   } else {
16392   if(nrCastlingRights) {
16393      q = p;
16394      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16395        /* [HGM] write directly from rights */
16396            if(boards[move][CASTLING][2] != NoRights &&
16397               boards[move][CASTLING][0] != NoRights   )
16398                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16399            if(boards[move][CASTLING][2] != NoRights &&
16400               boards[move][CASTLING][1] != NoRights   )
16401                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16402            if(boards[move][CASTLING][5] != NoRights &&
16403               boards[move][CASTLING][3] != NoRights   )
16404                 *p++ = boards[move][CASTLING][3] + AAA;
16405            if(boards[move][CASTLING][5] != NoRights &&
16406               boards[move][CASTLING][4] != NoRights   )
16407                 *p++ = boards[move][CASTLING][4] + AAA;
16408      } else {
16409
16410         /* [HGM] write true castling rights */
16411         if( nrCastlingRights == 6 ) {
16412             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16413                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16414             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16415                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16416             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16417                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16418             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16419                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16420         }
16421      }
16422      if (q == p) *p++ = '-'; /* No castling rights */
16423      *p++ = ' ';
16424   }
16425
16426   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16427      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16428     /* En passant target square */
16429     if (move > backwardMostMove) {
16430         fromX = moveList[move - 1][0] - AAA;
16431         fromY = moveList[move - 1][1] - ONE;
16432         toX = moveList[move - 1][2] - AAA;
16433         toY = moveList[move - 1][3] - ONE;
16434         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16435             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16436             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16437             fromX == toX) {
16438             /* 2-square pawn move just happened */
16439             *p++ = toX + AAA;
16440             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16441         } else {
16442             *p++ = '-';
16443         }
16444     } else if(move == backwardMostMove) {
16445         // [HGM] perhaps we should always do it like this, and forget the above?
16446         if((signed char)boards[move][EP_STATUS] >= 0) {
16447             *p++ = boards[move][EP_STATUS] + AAA;
16448             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16449         } else {
16450             *p++ = '-';
16451         }
16452     } else {
16453         *p++ = '-';
16454     }
16455     *p++ = ' ';
16456   }
16457   }
16458
16459     /* [HGM] find reversible plies */
16460     {   int i = 0, j=move;
16461
16462         if (appData.debugMode) { int k;
16463             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16464             for(k=backwardMostMove; k<=forwardMostMove; k++)
16465                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16466
16467         }
16468
16469         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16470         if( j == backwardMostMove ) i += initialRulePlies;
16471         sprintf(p, "%d ", i);
16472         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16473     }
16474     /* Fullmove number */
16475     sprintf(p, "%d", (move / 2) + 1);
16476
16477     return StrSave(buf);
16478 }
16479
16480 Boolean
16481 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16482 {
16483     int i, j;
16484     char *p, c;
16485     int emptycount;
16486     ChessSquare piece;
16487
16488     p = fen;
16489
16490     /* [HGM] by default clear Crazyhouse holdings, if present */
16491     if(gameInfo.holdingsWidth) {
16492        for(i=0; i<BOARD_HEIGHT; i++) {
16493            board[i][0]             = EmptySquare; /* black holdings */
16494            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16495            board[i][1]             = (ChessSquare) 0; /* black counts */
16496            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16497        }
16498     }
16499
16500     /* Piece placement data */
16501     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16502         j = 0;
16503         for (;;) {
16504             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16505                 if (*p == '/') p++;
16506                 emptycount = gameInfo.boardWidth - j;
16507                 while (emptycount--)
16508                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16509                 break;
16510 #if(BOARD_FILES >= 10)
16511             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16512                 p++; emptycount=10;
16513                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16514                 while (emptycount--)
16515                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16516 #endif
16517             } else if (isdigit(*p)) {
16518                 emptycount = *p++ - '0';
16519                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16520                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16521                 while (emptycount--)
16522                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16523             } else if (*p == '+' || isalpha(*p)) {
16524                 if (j >= gameInfo.boardWidth) return FALSE;
16525                 if(*p=='+') {
16526                     piece = CharToPiece(*++p);
16527                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16528                     piece = (ChessSquare) (PROMOTED piece ); p++;
16529                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16530                 } else piece = CharToPiece(*p++);
16531
16532                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16533                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16534                     piece = (ChessSquare) (PROMOTED piece);
16535                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16536                     p++;
16537                 }
16538                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16539             } else {
16540                 return FALSE;
16541             }
16542         }
16543     }
16544     while (*p == '/' || *p == ' ') p++;
16545
16546     /* [HGM] look for Crazyhouse holdings here */
16547     while(*p==' ') p++;
16548     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16549         if(*p == '[') p++;
16550         if(*p == '-' ) p++; /* empty holdings */ else {
16551             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16552             /* if we would allow FEN reading to set board size, we would   */
16553             /* have to add holdings and shift the board read so far here   */
16554             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16555                 p++;
16556                 if((int) piece >= (int) BlackPawn ) {
16557                     i = (int)piece - (int)BlackPawn;
16558                     i = PieceToNumber((ChessSquare)i);
16559                     if( i >= gameInfo.holdingsSize ) return FALSE;
16560                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16561                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16562                 } else {
16563                     i = (int)piece - (int)WhitePawn;
16564                     i = PieceToNumber((ChessSquare)i);
16565                     if( i >= gameInfo.holdingsSize ) return FALSE;
16566                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16567                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16568                 }
16569             }
16570         }
16571         if(*p == ']') p++;
16572     }
16573
16574     while(*p == ' ') p++;
16575
16576     /* Active color */
16577     c = *p++;
16578     if(appData.colorNickNames) {
16579       if( c == appData.colorNickNames[0] ) c = 'w'; else
16580       if( c == appData.colorNickNames[1] ) c = 'b';
16581     }
16582     switch (c) {
16583       case 'w':
16584         *blackPlaysFirst = FALSE;
16585         break;
16586       case 'b':
16587         *blackPlaysFirst = TRUE;
16588         break;
16589       default:
16590         return FALSE;
16591     }
16592
16593     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16594     /* return the extra info in global variiables             */
16595
16596     /* set defaults in case FEN is incomplete */
16597     board[EP_STATUS] = EP_UNKNOWN;
16598     for(i=0; i<nrCastlingRights; i++ ) {
16599         board[CASTLING][i] =
16600             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16601     }   /* assume possible unless obviously impossible */
16602     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16603     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16604     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16605                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16606     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16607     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16608     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16609                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16610     FENrulePlies = 0;
16611
16612     while(*p==' ') p++;
16613     if(nrCastlingRights) {
16614       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16615           /* castling indicator present, so default becomes no castlings */
16616           for(i=0; i<nrCastlingRights; i++ ) {
16617                  board[CASTLING][i] = NoRights;
16618           }
16619       }
16620       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16621              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16622              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16623              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16624         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16625
16626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16627             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16628             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16629         }
16630         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16631             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16632         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16633                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16634         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16635                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16636         switch(c) {
16637           case'K':
16638               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16639               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16640               board[CASTLING][2] = whiteKingFile;
16641               break;
16642           case'Q':
16643               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16644               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16645               board[CASTLING][2] = whiteKingFile;
16646               break;
16647           case'k':
16648               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16649               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16650               board[CASTLING][5] = blackKingFile;
16651               break;
16652           case'q':
16653               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16654               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16655               board[CASTLING][5] = blackKingFile;
16656           case '-':
16657               break;
16658           default: /* FRC castlings */
16659               if(c >= 'a') { /* black rights */
16660                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16661                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16662                   if(i == BOARD_RGHT) break;
16663                   board[CASTLING][5] = i;
16664                   c -= AAA;
16665                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16666                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16667                   if(c > i)
16668                       board[CASTLING][3] = c;
16669                   else
16670                       board[CASTLING][4] = c;
16671               } else { /* white rights */
16672                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16673                     if(board[0][i] == WhiteKing) break;
16674                   if(i == BOARD_RGHT) break;
16675                   board[CASTLING][2] = i;
16676                   c -= AAA - 'a' + 'A';
16677                   if(board[0][c] >= WhiteKing) break;
16678                   if(c > i)
16679                       board[CASTLING][0] = c;
16680                   else
16681                       board[CASTLING][1] = c;
16682               }
16683         }
16684       }
16685       for(i=0; i<nrCastlingRights; i++)
16686         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16687     if (appData.debugMode) {
16688         fprintf(debugFP, "FEN castling rights:");
16689         for(i=0; i<nrCastlingRights; i++)
16690         fprintf(debugFP, " %d", board[CASTLING][i]);
16691         fprintf(debugFP, "\n");
16692     }
16693
16694       while(*p==' ') p++;
16695     }
16696
16697     /* read e.p. field in games that know e.p. capture */
16698     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16699        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16700       if(*p=='-') {
16701         p++; board[EP_STATUS] = EP_NONE;
16702       } else {
16703          char c = *p++ - AAA;
16704
16705          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16706          if(*p >= '0' && *p <='9') p++;
16707          board[EP_STATUS] = c;
16708       }
16709     }
16710
16711
16712     if(sscanf(p, "%d", &i) == 1) {
16713         FENrulePlies = i; /* 50-move ply counter */
16714         /* (The move number is still ignored)    */
16715     }
16716
16717     return TRUE;
16718 }
16719
16720 void
16721 EditPositionPasteFEN (char *fen)
16722 {
16723   if (fen != NULL) {
16724     Board initial_position;
16725
16726     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16727       DisplayError(_("Bad FEN position in clipboard"), 0);
16728       return ;
16729     } else {
16730       int savedBlackPlaysFirst = blackPlaysFirst;
16731       EditPositionEvent();
16732       blackPlaysFirst = savedBlackPlaysFirst;
16733       CopyBoard(boards[0], initial_position);
16734       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16735       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16736       DisplayBothClocks();
16737       DrawPosition(FALSE, boards[currentMove]);
16738     }
16739   }
16740 }
16741
16742 static char cseq[12] = "\\   ";
16743
16744 Boolean
16745 set_cont_sequence (char *new_seq)
16746 {
16747     int len;
16748     Boolean ret;
16749
16750     // handle bad attempts to set the sequence
16751         if (!new_seq)
16752                 return 0; // acceptable error - no debug
16753
16754     len = strlen(new_seq);
16755     ret = (len > 0) && (len < sizeof(cseq));
16756     if (ret)
16757       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16758     else if (appData.debugMode)
16759       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16760     return ret;
16761 }
16762
16763 /*
16764     reformat a source message so words don't cross the width boundary.  internal
16765     newlines are not removed.  returns the wrapped size (no null character unless
16766     included in source message).  If dest is NULL, only calculate the size required
16767     for the dest buffer.  lp argument indicats line position upon entry, and it's
16768     passed back upon exit.
16769 */
16770 int
16771 wrap (char *dest, char *src, int count, int width, int *lp)
16772 {
16773     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16774
16775     cseq_len = strlen(cseq);
16776     old_line = line = *lp;
16777     ansi = len = clen = 0;
16778
16779     for (i=0; i < count; i++)
16780     {
16781         if (src[i] == '\033')
16782             ansi = 1;
16783
16784         // if we hit the width, back up
16785         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16786         {
16787             // store i & len in case the word is too long
16788             old_i = i, old_len = len;
16789
16790             // find the end of the last word
16791             while (i && src[i] != ' ' && src[i] != '\n')
16792             {
16793                 i--;
16794                 len--;
16795             }
16796
16797             // word too long?  restore i & len before splitting it
16798             if ((old_i-i+clen) >= width)
16799             {
16800                 i = old_i;
16801                 len = old_len;
16802             }
16803
16804             // extra space?
16805             if (i && src[i-1] == ' ')
16806                 len--;
16807
16808             if (src[i] != ' ' && src[i] != '\n')
16809             {
16810                 i--;
16811                 if (len)
16812                     len--;
16813             }
16814
16815             // now append the newline and continuation sequence
16816             if (dest)
16817                 dest[len] = '\n';
16818             len++;
16819             if (dest)
16820                 strncpy(dest+len, cseq, cseq_len);
16821             len += cseq_len;
16822             line = cseq_len;
16823             clen = cseq_len;
16824             continue;
16825         }
16826
16827         if (dest)
16828             dest[len] = src[i];
16829         len++;
16830         if (!ansi)
16831             line++;
16832         if (src[i] == '\n')
16833             line = 0;
16834         if (src[i] == 'm')
16835             ansi = 0;
16836     }
16837     if (dest && appData.debugMode)
16838     {
16839         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16840             count, width, line, len, *lp);
16841         show_bytes(debugFP, src, count);
16842         fprintf(debugFP, "\ndest: ");
16843         show_bytes(debugFP, dest, len);
16844         fprintf(debugFP, "\n");
16845     }
16846     *lp = dest ? line : old_line;
16847
16848     return len;
16849 }
16850
16851 // [HGM] vari: routines for shelving variations
16852 Boolean modeRestore = FALSE;
16853
16854 void
16855 PushInner (int firstMove, int lastMove)
16856 {
16857         int i, j, nrMoves = lastMove - firstMove;
16858
16859         // push current tail of game on stack
16860         savedResult[storedGames] = gameInfo.result;
16861         savedDetails[storedGames] = gameInfo.resultDetails;
16862         gameInfo.resultDetails = NULL;
16863         savedFirst[storedGames] = firstMove;
16864         savedLast [storedGames] = lastMove;
16865         savedFramePtr[storedGames] = framePtr;
16866         framePtr -= nrMoves; // reserve space for the boards
16867         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16868             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16869             for(j=0; j<MOVE_LEN; j++)
16870                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16871             for(j=0; j<2*MOVE_LEN; j++)
16872                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16873             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16874             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16875             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16876             pvInfoList[firstMove+i-1].depth = 0;
16877             commentList[framePtr+i] = commentList[firstMove+i];
16878             commentList[firstMove+i] = NULL;
16879         }
16880
16881         storedGames++;
16882         forwardMostMove = firstMove; // truncate game so we can start variation
16883 }
16884
16885 void
16886 PushTail (int firstMove, int lastMove)
16887 {
16888         if(appData.icsActive) { // only in local mode
16889                 forwardMostMove = currentMove; // mimic old ICS behavior
16890                 return;
16891         }
16892         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16893
16894         PushInner(firstMove, lastMove);
16895         if(storedGames == 1) GreyRevert(FALSE);
16896         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16897 }
16898
16899 void
16900 PopInner (Boolean annotate)
16901 {
16902         int i, j, nrMoves;
16903         char buf[8000], moveBuf[20];
16904
16905         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16906         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16907         nrMoves = savedLast[storedGames] - currentMove;
16908         if(annotate) {
16909                 int cnt = 10;
16910                 if(!WhiteOnMove(currentMove))
16911                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16912                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16913                 for(i=currentMove; i<forwardMostMove; i++) {
16914                         if(WhiteOnMove(i))
16915                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16916                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16917                         strcat(buf, moveBuf);
16918                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16919                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16920                 }
16921                 strcat(buf, ")");
16922         }
16923         for(i=1; i<=nrMoves; i++) { // copy last variation back
16924             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16925             for(j=0; j<MOVE_LEN; j++)
16926                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16927             for(j=0; j<2*MOVE_LEN; j++)
16928                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16929             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16930             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16931             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16932             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16933             commentList[currentMove+i] = commentList[framePtr+i];
16934             commentList[framePtr+i] = NULL;
16935         }
16936         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16937         framePtr = savedFramePtr[storedGames];
16938         gameInfo.result = savedResult[storedGames];
16939         if(gameInfo.resultDetails != NULL) {
16940             free(gameInfo.resultDetails);
16941       }
16942         gameInfo.resultDetails = savedDetails[storedGames];
16943         forwardMostMove = currentMove + nrMoves;
16944 }
16945
16946 Boolean
16947 PopTail (Boolean annotate)
16948 {
16949         if(appData.icsActive) return FALSE; // only in local mode
16950         if(!storedGames) return FALSE; // sanity
16951         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16952
16953         PopInner(annotate);
16954         if(currentMove < forwardMostMove) ForwardEvent(); else
16955         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16956
16957         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16958         return TRUE;
16959 }
16960
16961 void
16962 CleanupTail ()
16963 {       // remove all shelved variations
16964         int i;
16965         for(i=0; i<storedGames; i++) {
16966             if(savedDetails[i])
16967                 free(savedDetails[i]);
16968             savedDetails[i] = NULL;
16969         }
16970         for(i=framePtr; i<MAX_MOVES; i++) {
16971                 if(commentList[i]) free(commentList[i]);
16972                 commentList[i] = NULL;
16973         }
16974         framePtr = MAX_MOVES-1;
16975         storedGames = 0;
16976 }
16977
16978 void
16979 LoadVariation (int index, char *text)
16980 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16981         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16982         int level = 0, move;
16983
16984         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16985         // first find outermost bracketing variation
16986         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16987             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16988                 if(*p == '{') wait = '}'; else
16989                 if(*p == '[') wait = ']'; else
16990                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16991                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16992             }
16993             if(*p == wait) wait = NULLCHAR; // closing ]} found
16994             p++;
16995         }
16996         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16997         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16998         end[1] = NULLCHAR; // clip off comment beyond variation
16999         ToNrEvent(currentMove-1);
17000         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17001         // kludge: use ParsePV() to append variation to game
17002         move = currentMove;
17003         ParsePV(start, TRUE, TRUE);
17004         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17005         ClearPremoveHighlights();
17006         CommentPopDown();
17007         ToNrEvent(currentMove+1);
17008 }
17009