Remove some of the most verbose debug output
[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     do {
1246         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1247         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1248         if(movenr == -1) return time;    /* last move before new session     */
1249         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1250         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1251         if(!moves) return increment;     /* current session is incremental   */
1252         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1253     } while(movenr >= -1);               /* try again for next session       */
1254
1255     return 0; // no new time quota on this move
1256 }
1257
1258 int
1259 ParseTimeControl (char *tc, float ti, int mps)
1260 {
1261   long tc1;
1262   long tc2;
1263   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1264   int min, sec=0;
1265
1266   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1267   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1268       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1269   if(ti > 0) {
1270
1271     if(mps)
1272       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1273     else 
1274       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1275   } else {
1276     if(mps)
1277       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1278     else 
1279       snprintf(buf, MSG_SIZ, ":%s", mytc);
1280   }
1281   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1282   
1283   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1284     return FALSE;
1285   }
1286
1287   if( *tc == '/' ) {
1288     /* Parse second time control */
1289     tc++;
1290
1291     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1292       return FALSE;
1293     }
1294
1295     if( tc2 == 0 ) {
1296       return FALSE;
1297     }
1298
1299     timeControl_2 = tc2 * 1000;
1300   }
1301   else {
1302     timeControl_2 = 0;
1303   }
1304
1305   if( tc1 == 0 ) {
1306     return FALSE;
1307   }
1308
1309   timeControl = tc1 * 1000;
1310
1311   if (ti >= 0) {
1312     timeIncrement = ti * 1000;  /* convert to ms */
1313     movesPerSession = 0;
1314   } else {
1315     timeIncrement = 0;
1316     movesPerSession = mps;
1317   }
1318   return TRUE;
1319 }
1320
1321 void
1322 InitBackEnd2 ()
1323 {
1324     if (appData.debugMode) {
1325         fprintf(debugFP, "%s\n", programVersion);
1326     }
1327     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1328
1329     set_cont_sequence(appData.wrapContSeq);
1330     if (appData.matchGames > 0) {
1331         appData.matchMode = TRUE;
1332     } else if (appData.matchMode) {
1333         appData.matchGames = 1;
1334     }
1335     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1336         appData.matchGames = appData.sameColorGames;
1337     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1338         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1339         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1340     }
1341     Reset(TRUE, FALSE);
1342     if (appData.noChessProgram || first.protocolVersion == 1) {
1343       InitBackEnd3();
1344     } else {
1345       /* kludge: allow timeout for initial "feature" commands */
1346       FreezeUI();
1347       DisplayMessage("", _("Starting chess program"));
1348       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1349     }
1350 }
1351
1352 int
1353 CalculateIndex (int index, int gameNr)
1354 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1355     int res;
1356     if(index > 0) return index; // fixed nmber
1357     if(index == 0) return 1;
1358     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1359     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1360     return res;
1361 }
1362
1363 int
1364 LoadGameOrPosition (int gameNr)
1365 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1366     if (*appData.loadGameFile != NULLCHAR) {
1367         if (!LoadGameFromFile(appData.loadGameFile,
1368                 CalculateIndex(appData.loadGameIndex, gameNr),
1369                               appData.loadGameFile, FALSE)) {
1370             DisplayFatalError(_("Bad game file"), 0, 1);
1371             return 0;
1372         }
1373     } else if (*appData.loadPositionFile != NULLCHAR) {
1374         if (!LoadPositionFromFile(appData.loadPositionFile,
1375                 CalculateIndex(appData.loadPositionIndex, gameNr),
1376                                   appData.loadPositionFile)) {
1377             DisplayFatalError(_("Bad position file"), 0, 1);
1378             return 0;
1379         }
1380     }
1381     return 1;
1382 }
1383
1384 void
1385 ReserveGame (int gameNr, char resChar)
1386 {
1387     FILE *tf = fopen(appData.tourneyFile, "r+");
1388     char *p, *q, c, buf[MSG_SIZ];
1389     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1390     safeStrCpy(buf, lastMsg, MSG_SIZ);
1391     DisplayMessage(_("Pick new game"), "");
1392     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1393     ParseArgsFromFile(tf);
1394     p = q = appData.results;
1395     if(appData.debugMode) {
1396       char *r = appData.participants;
1397       fprintf(debugFP, "results = '%s'\n", p);
1398       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1399       fprintf(debugFP, "\n");
1400     }
1401     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1402     nextGame = q - p;
1403     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1404     safeStrCpy(q, p, strlen(p) + 2);
1405     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1406     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1408         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1409         q[nextGame] = '*';
1410     }
1411     fseek(tf, -(strlen(p)+4), SEEK_END);
1412     c = fgetc(tf);
1413     if(c != '"') // depending on DOS or Unix line endings we can be one off
1414          fseek(tf, -(strlen(p)+2), SEEK_END);
1415     else fseek(tf, -(strlen(p)+3), SEEK_END);
1416     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1417     DisplayMessage(buf, "");
1418     free(p); appData.results = q;
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1420        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1421       int round = appData.defaultMatchGames * appData.tourneyType;
1422       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1423          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1424         UnloadEngine(&first);  // next game belongs to other pairing;
1425         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1426     }
1427     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1428 }
1429
1430 void
1431 MatchEvent (int mode)
1432 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1433         int dummy;
1434         if(matchMode) { // already in match mode: switch it off
1435             abortMatch = TRUE;
1436             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1437             return;
1438         }
1439 //      if(gameMode != BeginningOfGame) {
1440 //          DisplayError(_("You can only start a match from the initial position."), 0);
1441 //          return;
1442 //      }
1443         abortMatch = FALSE;
1444         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1445         /* Set up machine vs. machine match */
1446         nextGame = 0;
1447         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1448         if(appData.tourneyFile[0]) {
1449             ReserveGame(-1, 0);
1450             if(nextGame > appData.matchGames) {
1451                 char buf[MSG_SIZ];
1452                 if(strchr(appData.results, '*') == NULL) {
1453                     FILE *f;
1454                     appData.tourneyCycles++;
1455                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1456                         fclose(f);
1457                         NextTourneyGame(-1, &dummy);
1458                         ReserveGame(-1, 0);
1459                         if(nextGame <= appData.matchGames) {
1460                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1461                             matchMode = mode;
1462                             ScheduleDelayedEvent(NextMatchGame, 10000);
1463                             return;
1464                         }
1465                     }
1466                 }
1467                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1468                 DisplayError(buf, 0);
1469                 appData.tourneyFile[0] = 0;
1470                 return;
1471             }
1472         } else
1473         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1474             DisplayFatalError(_("Can't have a match with no chess programs"),
1475                               0, 2);
1476             return;
1477         }
1478         matchMode = mode;
1479         matchGame = roundNr = 1;
1480         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1481         NextMatchGame();
1482 }
1483
1484 void
1485 InitBackEnd3 P((void))
1486 {
1487     GameMode initialMode;
1488     char buf[MSG_SIZ];
1489     int err, len;
1490
1491     InitChessProgram(&first, startedFromSetupPosition);
1492
1493     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1494         free(programVersion);
1495         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1496         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1497         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1498     }
1499
1500     if (appData.icsActive) {
1501 #ifdef WIN32
1502         /* [DM] Make a console window if needed [HGM] merged ifs */
1503         ConsoleCreate();
1504 #endif
1505         err = establish();
1506         if (err != 0)
1507           {
1508             if (*appData.icsCommPort != NULLCHAR)
1509               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1510                              appData.icsCommPort);
1511             else
1512               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1513                         appData.icsHost, appData.icsPort);
1514
1515             if( (len >= MSG_SIZ) && appData.debugMode )
1516               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1517
1518             DisplayFatalError(buf, err, 1);
1519             return;
1520         }
1521         SetICSMode();
1522         telnetISR =
1523           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1524         fromUserISR =
1525           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1526         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1527             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1528     } else if (appData.noChessProgram) {
1529         SetNCPMode();
1530     } else {
1531         SetGNUMode();
1532     }
1533
1534     if (*appData.cmailGameName != NULLCHAR) {
1535         SetCmailMode();
1536         OpenLoopback(&cmailPR);
1537         cmailISR =
1538           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1539     }
1540
1541     ThawUI();
1542     DisplayMessage("", "");
1543     if (StrCaseCmp(appData.initialMode, "") == 0) {
1544       initialMode = BeginningOfGame;
1545       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1546         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1547         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1548         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1549         ModeHighlight();
1550       }
1551     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1552       initialMode = TwoMachinesPlay;
1553     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1554       initialMode = AnalyzeFile;
1555     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1556       initialMode = AnalyzeMode;
1557     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1558       initialMode = MachinePlaysWhite;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1560       initialMode = MachinePlaysBlack;
1561     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1562       initialMode = EditGame;
1563     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1564       initialMode = EditPosition;
1565     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1566       initialMode = Training;
1567     } else {
1568       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1569       if( (len >= MSG_SIZ) && appData.debugMode )
1570         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1571
1572       DisplayFatalError(buf, 0, 2);
1573       return;
1574     }
1575
1576     if (appData.matchMode) {
1577         if(appData.tourneyFile[0]) { // start tourney from command line
1578             FILE *f;
1579             if(f = fopen(appData.tourneyFile, "r")) {
1580                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1581                 fclose(f);
1582                 appData.clockMode = TRUE;
1583                 SetGNUMode();
1584             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1585         }
1586         MatchEvent(TRUE);
1587     } else if (*appData.cmailGameName != NULLCHAR) {
1588         /* Set up cmail mode */
1589         ReloadCmailMsgEvent(TRUE);
1590     } else {
1591         /* Set up other modes */
1592         if (initialMode == AnalyzeFile) {
1593           if (*appData.loadGameFile == NULLCHAR) {
1594             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1595             return;
1596           }
1597         }
1598         if (*appData.loadGameFile != NULLCHAR) {
1599             (void) LoadGameFromFile(appData.loadGameFile,
1600                                     appData.loadGameIndex,
1601                                     appData.loadGameFile, TRUE);
1602         } else if (*appData.loadPositionFile != NULLCHAR) {
1603             (void) LoadPositionFromFile(appData.loadPositionFile,
1604                                         appData.loadPositionIndex,
1605                                         appData.loadPositionFile);
1606             /* [HGM] try to make self-starting even after FEN load */
1607             /* to allow automatic setup of fairy variants with wtm */
1608             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1609                 gameMode = BeginningOfGame;
1610                 setboardSpoiledMachineBlack = 1;
1611             }
1612             /* [HGM] loadPos: make that every new game uses the setup */
1613             /* from file as long as we do not switch variant          */
1614             if(!blackPlaysFirst) {
1615                 startedFromPositionFile = TRUE;
1616                 CopyBoard(filePosition, boards[0]);
1617             }
1618         }
1619         if (initialMode == AnalyzeMode) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1626             return;
1627           }
1628           AnalyzeModeEvent();
1629         } else if (initialMode == AnalyzeFile) {
1630           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1631           ShowThinkingEvent();
1632           AnalyzeFileEvent();
1633           AnalysisPeriodicEvent(1);
1634         } else if (initialMode == MachinePlaysWhite) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1637                               0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1642                               0, 2);
1643             return;
1644           }
1645           MachineWhiteEvent();
1646         } else if (initialMode == MachinePlaysBlack) {
1647           if (appData.noChessProgram) {
1648             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1649                               0, 2);
1650             return;
1651           }
1652           if (appData.icsActive) {
1653             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1654                               0, 2);
1655             return;
1656           }
1657           MachineBlackEvent();
1658         } else if (initialMode == TwoMachinesPlay) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           TwoMachinesEvent();
1670         } else if (initialMode == EditGame) {
1671           EditGameEvent();
1672         } else if (initialMode == EditPosition) {
1673           EditPositionEvent();
1674         } else if (initialMode == Training) {
1675           if (*appData.loadGameFile == NULLCHAR) {
1676             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1677             return;
1678           }
1679           TrainingEvent();
1680         }
1681     }
1682 }
1683
1684 void
1685 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1686 {
1687     DisplayBook(current+1);
1688
1689     MoveHistorySet( movelist, first, last, current, pvInfoList );
1690
1691     EvalGraphSet( first, last, current, pvInfoList );
1692
1693     MakeEngineOutputTitle();
1694 }
1695
1696 /*
1697  * Establish will establish a contact to a remote host.port.
1698  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1699  *  used to talk to the host.
1700  * Returns 0 if okay, error code if not.
1701  */
1702 int
1703 establish ()
1704 {
1705     char buf[MSG_SIZ];
1706
1707     if (*appData.icsCommPort != NULLCHAR) {
1708         /* Talk to the host through a serial comm port */
1709         return OpenCommPort(appData.icsCommPort, &icsPR);
1710
1711     } else if (*appData.gateway != NULLCHAR) {
1712         if (*appData.remoteShell == NULLCHAR) {
1713             /* Use the rcmd protocol to run telnet program on a gateway host */
1714             snprintf(buf, sizeof(buf), "%s %s %s",
1715                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1716             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1717
1718         } else {
1719             /* Use the rsh program to run telnet program on a gateway host */
1720             if (*appData.remoteUser == NULLCHAR) {
1721                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1722                         appData.gateway, appData.telnetProgram,
1723                         appData.icsHost, appData.icsPort);
1724             } else {
1725                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1726                         appData.remoteShell, appData.gateway,
1727                         appData.remoteUser, appData.telnetProgram,
1728                         appData.icsHost, appData.icsPort);
1729             }
1730             return StartChildProcess(buf, "", &icsPR);
1731
1732         }
1733     } else if (appData.useTelnet) {
1734         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1735
1736     } else {
1737         /* TCP socket interface differs somewhat between
1738            Unix and NT; handle details in the front end.
1739            */
1740         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1741     }
1742 }
1743
1744 void
1745 EscapeExpand (char *p, char *q)
1746 {       // [HGM] initstring: routine to shape up string arguments
1747         while(*p++ = *q++) if(p[-1] == '\\')
1748             switch(*q++) {
1749                 case 'n': p[-1] = '\n'; break;
1750                 case 'r': p[-1] = '\r'; break;
1751                 case 't': p[-1] = '\t'; break;
1752                 case '\\': p[-1] = '\\'; break;
1753                 case 0: *p = 0; return;
1754                 default: p[-1] = q[-1]; break;
1755             }
1756 }
1757
1758 void
1759 show_bytes (FILE *fp, char *buf, int count)
1760 {
1761     while (count--) {
1762         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1763             fprintf(fp, "\\%03o", *buf & 0xff);
1764         } else {
1765             putc(*buf, fp);
1766         }
1767         buf++;
1768     }
1769     fflush(fp);
1770 }
1771
1772 /* Returns an errno value */
1773 int
1774 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1775 {
1776     char buf[8192], *p, *q, *buflim;
1777     int left, newcount, outcount;
1778
1779     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1780         *appData.gateway != NULLCHAR) {
1781         if (appData.debugMode) {
1782             fprintf(debugFP, ">ICS: ");
1783             show_bytes(debugFP, message, count);
1784             fprintf(debugFP, "\n");
1785         }
1786         return OutputToProcess(pr, message, count, outError);
1787     }
1788
1789     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1790     p = message;
1791     q = buf;
1792     left = count;
1793     newcount = 0;
1794     while (left) {
1795         if (q >= buflim) {
1796             if (appData.debugMode) {
1797                 fprintf(debugFP, ">ICS: ");
1798                 show_bytes(debugFP, buf, newcount);
1799                 fprintf(debugFP, "\n");
1800             }
1801             outcount = OutputToProcess(pr, buf, newcount, outError);
1802             if (outcount < newcount) return -1; /* to be sure */
1803             q = buf;
1804             newcount = 0;
1805         }
1806         if (*p == '\n') {
1807             *q++ = '\r';
1808             newcount++;
1809         } else if (((unsigned char) *p) == TN_IAC) {
1810             *q++ = (char) TN_IAC;
1811             newcount ++;
1812         }
1813         *q++ = *p++;
1814         newcount++;
1815         left--;
1816     }
1817     if (appData.debugMode) {
1818         fprintf(debugFP, ">ICS: ");
1819         show_bytes(debugFP, buf, newcount);
1820         fprintf(debugFP, "\n");
1821     }
1822     outcount = OutputToProcess(pr, buf, newcount, outError);
1823     if (outcount < newcount) return -1; /* to be sure */
1824     return count;
1825 }
1826
1827 void
1828 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1829 {
1830     int outError, outCount;
1831     static int gotEof = 0;
1832
1833     /* Pass data read from player on to ICS */
1834     if (count > 0) {
1835         gotEof = 0;
1836         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1837         if (outCount < count) {
1838             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1839         }
1840     } else if (count < 0) {
1841         RemoveInputSource(isr);
1842         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1843     } else if (gotEof++ > 0) {
1844         RemoveInputSource(isr);
1845         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1846     }
1847 }
1848
1849 void
1850 KeepAlive ()
1851 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1852     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1853     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1854     SendToICS("date\n");
1855     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1856 }
1857
1858 /* added routine for printf style output to ics */
1859 void
1860 ics_printf (char *format, ...)
1861 {
1862     char buffer[MSG_SIZ];
1863     va_list args;
1864
1865     va_start(args, format);
1866     vsnprintf(buffer, sizeof(buffer), format, args);
1867     buffer[sizeof(buffer)-1] = '\0';
1868     SendToICS(buffer);
1869     va_end(args);
1870 }
1871
1872 void
1873 SendToICS (char *s)
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NoProc) return;
1878
1879     count = strlen(s);
1880     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1881     if (outCount < count) {
1882         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883     }
1884 }
1885
1886 /* This is used for sending logon scripts to the ICS. Sending
1887    without a delay causes problems when using timestamp on ICC
1888    (at least on my machine). */
1889 void
1890 SendToICSDelayed (char *s, long msdelay)
1891 {
1892     int count, outCount, outError;
1893
1894     if (icsPR == NoProc) return;
1895
1896     count = strlen(s);
1897     if (appData.debugMode) {
1898         fprintf(debugFP, ">ICS: ");
1899         show_bytes(debugFP, s, count);
1900         fprintf(debugFP, "\n");
1901     }
1902     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1903                                       msdelay);
1904     if (outCount < count) {
1905         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1906     }
1907 }
1908
1909
1910 /* Remove all highlighting escape sequences in s
1911    Also deletes any suffix starting with '('
1912    */
1913 char *
1914 StripHighlightAndTitle (char *s)
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             if (*s == '(' || *s == '[') {
1926                 *p = NULLCHAR;
1927                 return retbuf;
1928             }
1929             *p++ = *s++;
1930         }
1931     }
1932     *p = NULLCHAR;
1933     return retbuf;
1934 }
1935
1936 /* Remove all highlighting escape sequences in s */
1937 char *
1938 StripHighlight (char *s)
1939 {
1940     static char retbuf[MSG_SIZ];
1941     char *p = retbuf;
1942
1943     while (*s != NULLCHAR) {
1944         while (*s == '\033') {
1945             while (*s != NULLCHAR && !isalpha(*s)) s++;
1946             if (*s != NULLCHAR) s++;
1947         }
1948         while (*s != NULLCHAR && *s != '\033') {
1949             *p++ = *s++;
1950         }
1951     }
1952     *p = NULLCHAR;
1953     return retbuf;
1954 }
1955
1956 char *variantNames[] = VARIANT_NAMES;
1957 char *
1958 VariantName (VariantClass v)
1959 {
1960     return variantNames[v];
1961 }
1962
1963
1964 /* Identify a variant from the strings the chess servers use or the
1965    PGN Variant tag names we use. */
1966 VariantClass
1967 StringToVariant (char *e)
1968 {
1969     char *p;
1970     int wnum = -1;
1971     VariantClass v = VariantNormal;
1972     int i, found = FALSE;
1973     char buf[MSG_SIZ];
1974     int len;
1975
1976     if (!e) return v;
1977
1978     /* [HGM] skip over optional board-size prefixes */
1979     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981         while( *e++ != '_');
1982     }
1983
1984     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1985         v = VariantNormal;
1986         found = TRUE;
1987     } else
1988     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989       if (StrCaseStr(e, variantNames[i])) {
1990         v = (VariantClass) i;
1991         found = TRUE;
1992         break;
1993       }
1994     }
1995
1996     if (!found) {
1997       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998           || StrCaseStr(e, "wild/fr")
1999           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000         v = VariantFischeRandom;
2001       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002                  (i = 1, p = StrCaseStr(e, "w"))) {
2003         p += i;
2004         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2005         if (isdigit(*p)) {
2006           wnum = atoi(p);
2007         } else {
2008           wnum = -1;
2009         }
2010         switch (wnum) {
2011         case 0: /* FICS only, actually */
2012         case 1:
2013           /* Castling legal even if K starts on d-file */
2014           v = VariantWildCastle;
2015           break;
2016         case 2:
2017         case 3:
2018         case 4:
2019           /* Castling illegal even if K & R happen to start in
2020              normal positions. */
2021           v = VariantNoCastle;
2022           break;
2023         case 5:
2024         case 7:
2025         case 8:
2026         case 10:
2027         case 11:
2028         case 12:
2029         case 13:
2030         case 14:
2031         case 15:
2032         case 18:
2033         case 19:
2034           /* Castling legal iff K & R start in normal positions */
2035           v = VariantNormal;
2036           break;
2037         case 6:
2038         case 20:
2039         case 21:
2040           /* Special wilds for position setup; unclear what to do here */
2041           v = VariantLoadable;
2042           break;
2043         case 9:
2044           /* Bizarre ICC game */
2045           v = VariantTwoKings;
2046           break;
2047         case 16:
2048           v = VariantKriegspiel;
2049           break;
2050         case 17:
2051           v = VariantLosers;
2052           break;
2053         case 22:
2054           v = VariantFischeRandom;
2055           break;
2056         case 23:
2057           v = VariantCrazyhouse;
2058           break;
2059         case 24:
2060           v = VariantBughouse;
2061           break;
2062         case 25:
2063           v = Variant3Check;
2064           break;
2065         case 26:
2066           /* Not quite the same as FICS suicide! */
2067           v = VariantGiveaway;
2068           break;
2069         case 27:
2070           v = VariantAtomic;
2071           break;
2072         case 28:
2073           v = VariantShatranj;
2074           break;
2075
2076         /* Temporary names for future ICC types.  The name *will* change in
2077            the next xboard/WinBoard release after ICC defines it. */
2078         case 29:
2079           v = Variant29;
2080           break;
2081         case 30:
2082           v = Variant30;
2083           break;
2084         case 31:
2085           v = Variant31;
2086           break;
2087         case 32:
2088           v = Variant32;
2089           break;
2090         case 33:
2091           v = Variant33;
2092           break;
2093         case 34:
2094           v = Variant34;
2095           break;
2096         case 35:
2097           v = Variant35;
2098           break;
2099         case 36:
2100           v = Variant36;
2101           break;
2102         case 37:
2103           v = VariantShogi;
2104           break;
2105         case 38:
2106           v = VariantXiangqi;
2107           break;
2108         case 39:
2109           v = VariantCourier;
2110           break;
2111         case 40:
2112           v = VariantGothic;
2113           break;
2114         case 41:
2115           v = VariantCapablanca;
2116           break;
2117         case 42:
2118           v = VariantKnightmate;
2119           break;
2120         case 43:
2121           v = VariantFairy;
2122           break;
2123         case 44:
2124           v = VariantCylinder;
2125           break;
2126         case 45:
2127           v = VariantFalcon;
2128           break;
2129         case 46:
2130           v = VariantCapaRandom;
2131           break;
2132         case 47:
2133           v = VariantBerolina;
2134           break;
2135         case 48:
2136           v = VariantJanus;
2137           break;
2138         case 49:
2139           v = VariantSuper;
2140           break;
2141         case 50:
2142           v = VariantGreat;
2143           break;
2144         case -1:
2145           /* Found "wild" or "w" in the string but no number;
2146              must assume it's normal chess. */
2147           v = VariantNormal;
2148           break;
2149         default:
2150           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151           if( (len >= MSG_SIZ) && appData.debugMode )
2152             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2153
2154           DisplayError(buf, 0);
2155           v = VariantUnknown;
2156           break;
2157         }
2158       }
2159     }
2160     if (appData.debugMode) {
2161       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162               e, wnum, VariantName(v));
2163     }
2164     return v;
2165 }
2166
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2169
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171    advance *index beyond it, and set leftover_start to the new value of
2172    *index; else return FALSE.  If pattern contains the character '*', it
2173    matches any sequence of characters not containing '\r', '\n', or the
2174    character following the '*' (if any), and the matched sequence(s) are
2175    copied into star_match.
2176    */
2177 int
2178 looking_at ( char *buf, int *index, char *pattern)
2179 {
2180     char *bufp = &buf[*index], *patternp = pattern;
2181     int star_count = 0;
2182     char *matchp = star_match[0];
2183
2184     for (;;) {
2185         if (*patternp == NULLCHAR) {
2186             *index = leftover_start = bufp - buf;
2187             *matchp = NULLCHAR;
2188             return TRUE;
2189         }
2190         if (*bufp == NULLCHAR) return FALSE;
2191         if (*patternp == '*') {
2192             if (*bufp == *(patternp + 1)) {
2193                 *matchp = NULLCHAR;
2194                 matchp = star_match[++star_count];
2195                 patternp += 2;
2196                 bufp++;
2197                 continue;
2198             } else if (*bufp == '\n' || *bufp == '\r') {
2199                 patternp++;
2200                 if (*patternp == NULLCHAR)
2201                   continue;
2202                 else
2203                   return FALSE;
2204             } else {
2205                 *matchp++ = *bufp++;
2206                 continue;
2207             }
2208         }
2209         if (*patternp != *bufp) return FALSE;
2210         patternp++;
2211         bufp++;
2212     }
2213 }
2214
2215 void
2216 SendToPlayer (char *data, int length)
2217 {
2218     int error, outCount;
2219     outCount = OutputToProcess(NoProc, data, length, &error);
2220     if (outCount < length) {
2221         DisplayFatalError(_("Error writing to display"), error, 1);
2222     }
2223 }
2224
2225 void
2226 PackHolding (char packed[], char *holding)
2227 {
2228     char *p = holding;
2229     char *q = packed;
2230     int runlength = 0;
2231     int curr = 9999;
2232     do {
2233         if (*p == curr) {
2234             runlength++;
2235         } else {
2236             switch (runlength) {
2237               case 0:
2238                 break;
2239               case 1:
2240                 *q++ = curr;
2241                 break;
2242               case 2:
2243                 *q++ = curr;
2244                 *q++ = curr;
2245                 break;
2246               default:
2247                 sprintf(q, "%d", runlength);
2248                 while (*q) q++;
2249                 *q++ = curr;
2250                 break;
2251             }
2252             runlength = 1;
2253             curr = *p;
2254         }
2255     } while (*p++);
2256     *q = NULLCHAR;
2257 }
2258
2259 /* Telnet protocol requests from the front end */
2260 void
2261 TelnetRequest (unsigned char ddww, unsigned char option)
2262 {
2263     unsigned char msg[3];
2264     int outCount, outError;
2265
2266     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2267
2268     if (appData.debugMode) {
2269         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2270         switch (ddww) {
2271           case TN_DO:
2272             ddwwStr = "DO";
2273             break;
2274           case TN_DONT:
2275             ddwwStr = "DONT";
2276             break;
2277           case TN_WILL:
2278             ddwwStr = "WILL";
2279             break;
2280           case TN_WONT:
2281             ddwwStr = "WONT";
2282             break;
2283           default:
2284             ddwwStr = buf1;
2285             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2286             break;
2287         }
2288         switch (option) {
2289           case TN_ECHO:
2290             optionStr = "ECHO";
2291             break;
2292           default:
2293             optionStr = buf2;
2294             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2295             break;
2296         }
2297         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2298     }
2299     msg[0] = TN_IAC;
2300     msg[1] = ddww;
2301     msg[2] = option;
2302     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2303     if (outCount < 3) {
2304         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2305     }
2306 }
2307
2308 void
2309 DoEcho ()
2310 {
2311     if (!appData.icsActive) return;
2312     TelnetRequest(TN_DO, TN_ECHO);
2313 }
2314
2315 void
2316 DontEcho ()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DONT, TN_ECHO);
2320 }
2321
2322 void
2323 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2324 {
2325     /* put the holdings sent to us by the server on the board holdings area */
2326     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2327     char p;
2328     ChessSquare piece;
2329
2330     if(gameInfo.holdingsWidth < 2)  return;
2331     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2332         return; // prevent overwriting by pre-board holdings
2333
2334     if( (int)lowestPiece >= BlackPawn ) {
2335         holdingsColumn = 0;
2336         countsColumn = 1;
2337         holdingsStartRow = BOARD_HEIGHT-1;
2338         direction = -1;
2339     } else {
2340         holdingsColumn = BOARD_WIDTH-1;
2341         countsColumn = BOARD_WIDTH-2;
2342         holdingsStartRow = 0;
2343         direction = 1;
2344     }
2345
2346     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2347         board[i][holdingsColumn] = EmptySquare;
2348         board[i][countsColumn]   = (ChessSquare) 0;
2349     }
2350     while( (p=*holdings++) != NULLCHAR ) {
2351         piece = CharToPiece( ToUpper(p) );
2352         if(piece == EmptySquare) continue;
2353         /*j = (int) piece - (int) WhitePawn;*/
2354         j = PieceToNumber(piece);
2355         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2356         if(j < 0) continue;               /* should not happen */
2357         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2358         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2359         board[holdingsStartRow+j*direction][countsColumn]++;
2360     }
2361 }
2362
2363
2364 void
2365 VariantSwitch (Board board, VariantClass newVariant)
2366 {
2367    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2368    static Board oldBoard;
2369
2370    startedFromPositionFile = FALSE;
2371    if(gameInfo.variant == newVariant) return;
2372
2373    /* [HGM] This routine is called each time an assignment is made to
2374     * gameInfo.variant during a game, to make sure the board sizes
2375     * are set to match the new variant. If that means adding or deleting
2376     * holdings, we shift the playing board accordingly
2377     * This kludge is needed because in ICS observe mode, we get boards
2378     * of an ongoing game without knowing the variant, and learn about the
2379     * latter only later. This can be because of the move list we requested,
2380     * in which case the game history is refilled from the beginning anyway,
2381     * but also when receiving holdings of a crazyhouse game. In the latter
2382     * case we want to add those holdings to the already received position.
2383     */
2384
2385
2386    if (appData.debugMode) {
2387      fprintf(debugFP, "Switch board from %s to %s\n",
2388              VariantName(gameInfo.variant), VariantName(newVariant));
2389      setbuf(debugFP, NULL);
2390    }
2391    shuffleOpenings = 0;       /* [HGM] shuffle */
2392    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2393    switch(newVariant)
2394      {
2395      case VariantShogi:
2396        newWidth = 9;  newHeight = 9;
2397        gameInfo.holdingsSize = 7;
2398      case VariantBughouse:
2399      case VariantCrazyhouse:
2400        newHoldingsWidth = 2; break;
2401      case VariantGreat:
2402        newWidth = 10;
2403      case VariantSuper:
2404        newHoldingsWidth = 2;
2405        gameInfo.holdingsSize = 8;
2406        break;
2407      case VariantGothic:
2408      case VariantCapablanca:
2409      case VariantCapaRandom:
2410        newWidth = 10;
2411      default:
2412        newHoldingsWidth = gameInfo.holdingsSize = 0;
2413      };
2414
2415    if(newWidth  != gameInfo.boardWidth  ||
2416       newHeight != gameInfo.boardHeight ||
2417       newHoldingsWidth != gameInfo.holdingsWidth ) {
2418
2419      /* shift position to new playing area, if needed */
2420      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2421        for(i=0; i<BOARD_HEIGHT; i++)
2422          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2423            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2424              board[i][j];
2425        for(i=0; i<newHeight; i++) {
2426          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2427          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2428        }
2429      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2430        for(i=0; i<BOARD_HEIGHT; i++)
2431          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2432            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433              board[i][j];
2434      }
2435      gameInfo.boardWidth  = newWidth;
2436      gameInfo.boardHeight = newHeight;
2437      gameInfo.holdingsWidth = newHoldingsWidth;
2438      gameInfo.variant = newVariant;
2439      InitDrawingSizes(-2, 0);
2440    } else gameInfo.variant = newVariant;
2441    CopyBoard(oldBoard, board);   // remember correctly formatted board
2442      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2443    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2444 }
2445
2446 static int loggedOn = FALSE;
2447
2448 /*-- Game start info cache: --*/
2449 int gs_gamenum;
2450 char gs_kind[MSG_SIZ];
2451 static char player1Name[128] = "";
2452 static char player2Name[128] = "";
2453 static char cont_seq[] = "\n\\   ";
2454 static int player1Rating = -1;
2455 static int player2Rating = -1;
2456 /*----------------------------*/
2457
2458 ColorClass curColor = ColorNormal;
2459 int suppressKibitz = 0;
2460
2461 // [HGM] seekgraph
2462 Boolean soughtPending = FALSE;
2463 Boolean seekGraphUp;
2464 #define MAX_SEEK_ADS 200
2465 #define SQUARE 0x80
2466 char *seekAdList[MAX_SEEK_ADS];
2467 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2468 float tcList[MAX_SEEK_ADS];
2469 char colorList[MAX_SEEK_ADS];
2470 int nrOfSeekAds = 0;
2471 int minRating = 1010, maxRating = 2800;
2472 int hMargin = 10, vMargin = 20, h, w;
2473 extern int squareSize, lineGap;
2474
2475 void
2476 PlotSeekAd (int i)
2477 {
2478         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2479         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2480         if(r < minRating+100 && r >=0 ) r = minRating+100;
2481         if(r > maxRating) r = maxRating;
2482         if(tc < 1.) tc = 1.;
2483         if(tc > 95.) tc = 95.;
2484         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2485         y = ((double)r - minRating)/(maxRating - minRating)
2486             * (h-vMargin-squareSize/8-1) + vMargin;
2487         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2488         if(strstr(seekAdList[i], " u ")) color = 1;
2489         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2490            !strstr(seekAdList[i], "bullet") &&
2491            !strstr(seekAdList[i], "blitz") &&
2492            !strstr(seekAdList[i], "standard") ) color = 2;
2493         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2494         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2495 }
2496
2497 void
2498 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2499 {
2500         char buf[MSG_SIZ], *ext = "";
2501         VariantClass v = StringToVariant(type);
2502         if(strstr(type, "wild")) {
2503             ext = type + 4; // append wild number
2504             if(v == VariantFischeRandom) type = "chess960"; else
2505             if(v == VariantLoadable) type = "setup"; else
2506             type = VariantName(v);
2507         }
2508         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2509         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2510             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2511             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2512             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2513             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2514             seekNrList[nrOfSeekAds] = nr;
2515             zList[nrOfSeekAds] = 0;
2516             seekAdList[nrOfSeekAds++] = StrSave(buf);
2517             if(plot) PlotSeekAd(nrOfSeekAds-1);
2518         }
2519 }
2520
2521 void
2522 EraseSeekDot (int i)
2523 {
2524     int x = xList[i], y = yList[i], d=squareSize/4, k;
2525     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2526     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2527     // now replot every dot that overlapped
2528     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2529         int xx = xList[k], yy = yList[k];
2530         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2531             DrawSeekDot(xx, yy, colorList[k]);
2532     }
2533 }
2534
2535 void
2536 RemoveSeekAd (int nr)
2537 {
2538         int i;
2539         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2540             EraseSeekDot(i);
2541             if(seekAdList[i]) free(seekAdList[i]);
2542             seekAdList[i] = seekAdList[--nrOfSeekAds];
2543             seekNrList[i] = seekNrList[nrOfSeekAds];
2544             ratingList[i] = ratingList[nrOfSeekAds];
2545             colorList[i]  = colorList[nrOfSeekAds];
2546             tcList[i] = tcList[nrOfSeekAds];
2547             xList[i]  = xList[nrOfSeekAds];
2548             yList[i]  = yList[nrOfSeekAds];
2549             zList[i]  = zList[nrOfSeekAds];
2550             seekAdList[nrOfSeekAds] = NULL;
2551             break;
2552         }
2553 }
2554
2555 Boolean
2556 MatchSoughtLine (char *line)
2557 {
2558     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2559     int nr, base, inc, u=0; char dummy;
2560
2561     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2563        (u=1) &&
2564        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2565         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2566         // match: compact and save the line
2567         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2568         return TRUE;
2569     }
2570     return FALSE;
2571 }
2572
2573 int
2574 DrawSeekGraph ()
2575 {
2576     int i;
2577     if(!seekGraphUp) return FALSE;
2578     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2579     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2580
2581     DrawSeekBackground(0, 0, w, h);
2582     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2583     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2584     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2585         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2586         yy = h-1-yy;
2587         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2588         if(i%500 == 0) {
2589             char buf[MSG_SIZ];
2590             snprintf(buf, MSG_SIZ, "%d", i);
2591             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2592         }
2593     }
2594     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2595     for(i=1; i<100; i+=(i<10?1:5)) {
2596         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2597         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2598         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2599             char buf[MSG_SIZ];
2600             snprintf(buf, MSG_SIZ, "%d", i);
2601             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2602         }
2603     }
2604     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2605     return TRUE;
2606 }
2607
2608 int
2609 SeekGraphClick (ClickType click, int x, int y, int moving)
2610 {
2611     static int lastDown = 0, displayed = 0, lastSecond;
2612     if(y < 0) return FALSE;
2613     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2614         if(click == Release || moving) return FALSE;
2615         nrOfSeekAds = 0;
2616         soughtPending = TRUE;
2617         SendToICS(ics_prefix);
2618         SendToICS("sought\n"); // should this be "sought all"?
2619     } else { // issue challenge based on clicked ad
2620         int dist = 10000; int i, closest = 0, second = 0;
2621         for(i=0; i<nrOfSeekAds; i++) {
2622             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2623             if(d < dist) { dist = d; closest = i; }
2624             second += (d - zList[i] < 120); // count in-range ads
2625             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2626         }
2627         if(dist < 120) {
2628             char buf[MSG_SIZ];
2629             second = (second > 1);
2630             if(displayed != closest || second != lastSecond) {
2631                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2632                 lastSecond = second; displayed = closest;
2633             }
2634             if(click == Press) {
2635                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2636                 lastDown = closest;
2637                 return TRUE;
2638             } // on press 'hit', only show info
2639             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2640             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2641             SendToICS(ics_prefix);
2642             SendToICS(buf);
2643             return TRUE; // let incoming board of started game pop down the graph
2644         } else if(click == Release) { // release 'miss' is ignored
2645             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2646             if(moving == 2) { // right up-click
2647                 nrOfSeekAds = 0; // refresh graph
2648                 soughtPending = TRUE;
2649                 SendToICS(ics_prefix);
2650                 SendToICS("sought\n"); // should this be "sought all"?
2651             }
2652             return TRUE;
2653         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2654         // press miss or release hit 'pop down' seek graph
2655         seekGraphUp = FALSE;
2656         DrawPosition(TRUE, NULL);
2657     }
2658     return TRUE;
2659 }
2660
2661 void
2662 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             if(gameMode == IcsObserving) // restore original ICS messages
2961                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2962                             else
2963                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2964                             SendToPlayer(tmp, strlen(tmp));
2965                         }
2966                         next_out = i+1; // [HGM] suppress printing in ICS window
2967                     }
2968                     started = STARTED_NONE;
2969                 } else {
2970                     /* Don't match patterns against characters in comment */
2971                     i++;
2972                     continue;
2973                 }
2974             }
2975             if (started == STARTED_CHATTER) {
2976                 if (buf[i] != '\n') {
2977                     /* Don't match patterns against characters in chatter */
2978                     i++;
2979                     continue;
2980                 }
2981                 started = STARTED_NONE;
2982                 if(suppressKibitz) next_out = i+1;
2983             }
2984
2985             /* Kludge to deal with rcmd protocol */
2986             if (firstTime && looking_at(buf, &i, "\001*")) {
2987                 DisplayFatalError(&buf[1], 0, 1);
2988                 continue;
2989             } else {
2990                 firstTime = FALSE;
2991             }
2992
2993             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2994                 ics_type = ICS_ICC;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3001                 ics_type = ICS_FICS;
3002                 ics_prefix = "$";
3003                 if (appData.debugMode)
3004                   fprintf(debugFP, "ics_type %d\n", ics_type);
3005                 continue;
3006             }
3007             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3008                 ics_type = ICS_CHESSNET;
3009                 ics_prefix = "/";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014
3015             if (!loggedOn &&
3016                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3017                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3018                  looking_at(buf, &i, "will be \"*\""))) {
3019               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3020               continue;
3021             }
3022
3023             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3024               char buf[MSG_SIZ];
3025               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3026               DisplayIcsInteractionTitle(buf);
3027               have_set_title = TRUE;
3028             }
3029
3030             /* skip finger notes */
3031             if (started == STARTED_NONE &&
3032                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3033                  (buf[i] == '1' && buf[i+1] == '0')) &&
3034                 buf[i+2] == ':' && buf[i+3] == ' ') {
3035               started = STARTED_CHATTER;
3036               i += 3;
3037               continue;
3038             }
3039
3040             oldi = i;
3041             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3042             if(appData.seekGraph) {
3043                 if(soughtPending && MatchSoughtLine(buf+i)) {
3044                     i = strstr(buf+i, "rated") - buf;
3045                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046                     next_out = leftover_start = i;
3047                     started = STARTED_CHATTER;
3048                     suppressKibitz = TRUE;
3049                     continue;
3050                 }
3051                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3052                         && looking_at(buf, &i, "* ads displayed")) {
3053                     soughtPending = FALSE;
3054                     seekGraphUp = TRUE;
3055                     DrawSeekGraph();
3056                     continue;
3057                 }
3058                 if(appData.autoRefresh) {
3059                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3060                         int s = (ics_type == ICS_ICC); // ICC format differs
3061                         if(seekGraphUp)
3062                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3063                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3064                         looking_at(buf, &i, "*% "); // eat prompt
3065                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3066                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                         next_out = i; // suppress
3068                         continue;
3069                     }
3070                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3071                         char *p = star_match[0];
3072                         while(*p) {
3073                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3074                             while(*p && *p++ != ' '); // next
3075                         }
3076                         looking_at(buf, &i, "*% "); // eat prompt
3077                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078                         next_out = i;
3079                         continue;
3080                     }
3081                 }
3082             }
3083
3084             /* skip formula vars */
3085             if (started == STARTED_NONE &&
3086                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3087               started = STARTED_CHATTER;
3088               i += 3;
3089               continue;
3090             }
3091
3092             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3093             if (appData.autoKibitz && started == STARTED_NONE &&
3094                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3095                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3096                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3097                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3098                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3099                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3100                         suppressKibitz = TRUE;
3101                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3102                         next_out = i;
3103                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3104                                 && (gameMode == IcsPlayingWhite)) ||
3105                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3106                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3107                             started = STARTED_CHATTER; // own kibitz we simply discard
3108                         else {
3109                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3110                             parse_pos = 0; parse[0] = NULLCHAR;
3111                             savingComment = TRUE;
3112                             suppressKibitz = gameMode != IcsObserving ? 2 :
3113                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3114                         }
3115                         continue;
3116                 } else
3117                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3118                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3119                          && atoi(star_match[0])) {
3120                     // suppress the acknowledgements of our own autoKibitz
3121                     char *p;
3122                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3124                     SendToPlayer(star_match[0], strlen(star_match[0]));
3125                     if(looking_at(buf, &i, "*% ")) // eat prompt
3126                         suppressKibitz = FALSE;
3127                     next_out = i;
3128                     continue;
3129                 }
3130             } // [HGM] kibitz: end of patch
3131
3132             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3133
3134             // [HGM] chat: intercept tells by users for which we have an open chat window
3135             channel = -1;
3136             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3137                                            looking_at(buf, &i, "* whispers:") ||
3138                                            looking_at(buf, &i, "* kibitzes:") ||
3139                                            looking_at(buf, &i, "* shouts:") ||
3140                                            looking_at(buf, &i, "* c-shouts:") ||
3141                                            looking_at(buf, &i, "--> * ") ||
3142                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3143                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3144                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3146                 int p;
3147                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3148                 chattingPartner = -1;
3149
3150                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3153                     talker[0] = '['; strcat(talker, "] ");
3154                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3155                     chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("kibitzes", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3166                 for(p=0; p<MAX_CHAT; p++) {
3167                     if(!strcmp("whispers", chatPartner[p])) {
3168                         talker[0] = '['; strcat(talker, "] ");
3169                         chattingPartner = p; break;
3170                     }
3171                 } else
3172                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3173                   if(buf[i-8] == '-' && buf[i-3] == 't')
3174                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3175                     if(!strcmp("c-shouts", chatPartner[p])) {
3176                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3177                         chattingPartner = p; break;
3178                     }
3179                   }
3180                   if(chattingPartner < 0)
3181                   for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("shouts", chatPartner[p])) {
3183                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3184                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3185                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3186                         chattingPartner = p; break;
3187                     }
3188                   }
3189                 }
3190                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3191                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3192                     talker[0] = 0; Colorize(ColorTell, FALSE);
3193                     chattingPartner = p; break;
3194                 }
3195                 if(chattingPartner<0) i = oldi; else {
3196                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3197                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3198                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                     started = STARTED_COMMENT;
3200                     parse_pos = 0; parse[0] = NULLCHAR;
3201                     savingComment = 3 + chattingPartner; // counts as TRUE
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205             } // [HGM] chat: end of patch
3206
3207           backup = i;
3208             if (appData.zippyTalk || appData.zippyPlay) {
3209                 /* [DM] Backup address for color zippy lines */
3210 #if ZIPPY
3211                if (loggedOn == TRUE)
3212                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3213                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3214 #endif
3215             } // [DM] 'else { ' deleted
3216                 if (
3217                     /* Regular tells and says */
3218                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3219                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3220                     looking_at(buf, &i, "* says: ") ||
3221                     /* Don't color "message" or "messages" output */
3222                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3223                     looking_at(buf, &i, "*. * at *:*: ") ||
3224                     looking_at(buf, &i, "--* (*:*): ") ||
3225                     /* Message notifications (same color as tells) */
3226                     looking_at(buf, &i, "* has left a message ") ||
3227                     looking_at(buf, &i, "* just sent you a message:\n") ||
3228                     /* Whispers and kibitzes */
3229                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3230                     looking_at(buf, &i, "* kibitzes: ") ||
3231                     /* Channel tells */
3232                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3233
3234                   if (tkind == 1 && strchr(star_match[0], ':')) {
3235                       /* Avoid "tells you:" spoofs in channels */
3236                      tkind = 3;
3237                   }
3238                   if (star_match[0][0] == NULLCHAR ||
3239                       strchr(star_match[0], ' ') ||
3240                       (tkind == 3 && strchr(star_match[1], ' '))) {
3241                     /* Reject bogus matches */
3242                     i = oldi;
3243                   } else {
3244                     if (appData.colorize) {
3245                       if (oldi > next_out) {
3246                         SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = oldi;
3248                       }
3249                       switch (tkind) {
3250                       case 1:
3251                         Colorize(ColorTell, FALSE);
3252                         curColor = ColorTell;
3253                         break;
3254                       case 2:
3255                         Colorize(ColorKibitz, FALSE);
3256                         curColor = ColorKibitz;
3257                         break;
3258                       case 3:
3259                         p = strrchr(star_match[1], '(');
3260                         if (p == NULL) {
3261                           p = star_match[1];
3262                         } else {
3263                           p++;
3264                         }
3265                         if (atoi(p) == 1) {
3266                           Colorize(ColorChannel1, FALSE);
3267                           curColor = ColorChannel1;
3268                         } else {
3269                           Colorize(ColorChannel, FALSE);
3270                           curColor = ColorChannel;
3271                         }
3272                         break;
3273                       case 5:
3274                         curColor = ColorNormal;
3275                         break;
3276                       }
3277                     }
3278                     if (started == STARTED_NONE && appData.autoComment &&
3279                         (gameMode == IcsObserving ||
3280                          gameMode == IcsPlayingWhite ||
3281                          gameMode == IcsPlayingBlack)) {
3282                       parse_pos = i - oldi;
3283                       memcpy(parse, &buf[oldi], parse_pos);
3284                       parse[parse_pos] = NULLCHAR;
3285                       started = STARTED_COMMENT;
3286                       savingComment = TRUE;
3287                     } else {
3288                       started = STARTED_CHATTER;
3289                       savingComment = FALSE;
3290                     }
3291                     loggedOn = TRUE;
3292                     continue;
3293                   }
3294                 }
3295
3296                 if (looking_at(buf, &i, "* s-shouts: ") ||
3297                     looking_at(buf, &i, "* c-shouts: ")) {
3298                     if (appData.colorize) {
3299                         if (oldi > next_out) {
3300                             SendToPlayer(&buf[next_out], oldi - next_out);
3301                             next_out = oldi;
3302                         }
3303                         Colorize(ColorSShout, FALSE);
3304                         curColor = ColorSShout;
3305                     }
3306                     loggedOn = TRUE;
3307                     started = STARTED_CHATTER;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "--->")) {
3312                     loggedOn = TRUE;
3313                     continue;
3314                 }
3315
3316                 if (looking_at(buf, &i, "* shouts: ") ||
3317                     looking_at(buf, &i, "--> ")) {
3318                     if (appData.colorize) {
3319                         if (oldi > next_out) {
3320                             SendToPlayer(&buf[next_out], oldi - next_out);
3321                             next_out = oldi;
3322                         }
3323                         Colorize(ColorShout, FALSE);
3324                         curColor = ColorShout;
3325                     }
3326                     loggedOn = TRUE;
3327                     started = STARTED_CHATTER;
3328                     continue;
3329                 }
3330
3331                 if (looking_at( buf, &i, "Challenge:")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorChallenge, FALSE);
3338                         curColor = ColorChallenge;
3339                     }
3340                     loggedOn = TRUE;
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "* offers you") ||
3345                     looking_at(buf, &i, "* offers to be") ||
3346                     looking_at(buf, &i, "* would like to") ||
3347                     looking_at(buf, &i, "* requests to") ||
3348                     looking_at(buf, &i, "Your opponent offers") ||
3349                     looking_at(buf, &i, "Your opponent requests")) {
3350
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorRequest, FALSE);
3357                         curColor = ColorRequest;
3358                     }
3359                     continue;
3360                 }
3361
3362                 if (looking_at(buf, &i, "* (*) seeking")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSeek, FALSE);
3369                         curColor = ColorSeek;
3370                     }
3371                     continue;
3372             }
3373
3374           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3375
3376             if (looking_at(buf, &i, "\\   ")) {
3377                 if (prevColor != ColorNormal) {
3378                     if (oldi > next_out) {
3379                         SendToPlayer(&buf[next_out], oldi - next_out);
3380                         next_out = oldi;
3381                     }
3382                     Colorize(prevColor, TRUE);
3383                     curColor = prevColor;
3384                 }
3385                 if (savingComment) {
3386                     parse_pos = i - oldi;
3387                     memcpy(parse, &buf[oldi], parse_pos);
3388                     parse[parse_pos] = NULLCHAR;
3389                     started = STARTED_COMMENT;
3390                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3391                         chattingPartner = savingComment - 3; // kludge to remember the box
3392                 } else {
3393                     started = STARTED_CHATTER;
3394                 }
3395                 continue;
3396             }
3397
3398             if (looking_at(buf, &i, "Black Strength :") ||
3399                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3400                 looking_at(buf, &i, "<10>") ||
3401                 looking_at(buf, &i, "#@#")) {
3402                 /* Wrong board style */
3403                 loggedOn = TRUE;
3404                 SendToICS(ics_prefix);
3405                 SendToICS("set style 12\n");
3406                 SendToICS(ics_prefix);
3407                 SendToICS("refresh\n");
3408                 continue;
3409             }
3410
3411             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3412                 ICSInitScript();
3413                 have_sent_ICS_logon = 1;
3414                 continue;
3415             }
3416
3417             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3418                 (looking_at(buf, &i, "\n<12> ") ||
3419                  looking_at(buf, &i, "<12> "))) {
3420                 loggedOn = TRUE;
3421                 if (oldi > next_out) {
3422                     SendToPlayer(&buf[next_out], oldi - next_out);
3423                 }
3424                 next_out = i;
3425                 started = STARTED_BOARD;
3426                 parse_pos = 0;
3427                 continue;
3428             }
3429
3430             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3431                 looking_at(buf, &i, "<b1> ")) {
3432                 if (oldi > next_out) {
3433                     SendToPlayer(&buf[next_out], oldi - next_out);
3434                 }
3435                 next_out = i;
3436                 started = STARTED_HOLDINGS;
3437                 parse_pos = 0;
3438                 continue;
3439             }
3440
3441             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3442                 loggedOn = TRUE;
3443                 /* Header for a move list -- first line */
3444
3445                 switch (ics_getting_history) {
3446                   case H_FALSE:
3447                     switch (gameMode) {
3448                       case IcsIdle:
3449                       case BeginningOfGame:
3450                         /* User typed "moves" or "oldmoves" while we
3451                            were idle.  Pretend we asked for these
3452                            moves and soak them up so user can step
3453                            through them and/or save them.
3454                            */
3455                         Reset(FALSE, TRUE);
3456                         gameMode = IcsObserving;
3457                         ModeHighlight();
3458                         ics_gamenum = -1;
3459                         ics_getting_history = H_GOT_UNREQ_HEADER;
3460                         break;
3461                       case EditGame: /*?*/
3462                       case EditPosition: /*?*/
3463                         /* Should above feature work in these modes too? */
3464                         /* For now it doesn't */
3465                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3466                         break;
3467                       default:
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                     }
3471                     break;
3472                   case H_REQUESTED:
3473                     /* Is this the right one? */
3474                     if (gameInfo.white && gameInfo.black &&
3475                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3476                         strcmp(gameInfo.black, star_match[2]) == 0) {
3477                         /* All is well */
3478                         ics_getting_history = H_GOT_REQ_HEADER;
3479                     }
3480                     break;
3481                   case H_GOT_REQ_HEADER:
3482                   case H_GOT_UNREQ_HEADER:
3483                   case H_GOT_UNWANTED_HEADER:
3484                   case H_GETTING_MOVES:
3485                     /* Should not happen */
3486                     DisplayError(_("Error gathering move list: two headers"), 0);
3487                     ics_getting_history = H_FALSE;
3488                     break;
3489                 }
3490
3491                 /* Save player ratings into gameInfo if needed */
3492                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3493                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3494                     (gameInfo.whiteRating == -1 ||
3495                      gameInfo.blackRating == -1)) {
3496
3497                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3498                     gameInfo.blackRating = string_to_rating(star_match[3]);
3499                     if (appData.debugMode)
3500                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3501                               gameInfo.whiteRating, gameInfo.blackRating);
3502                 }
3503                 continue;
3504             }
3505
3506             if (looking_at(buf, &i,
3507               "* * match, initial time: * minute*, increment: * second")) {
3508                 /* Header for a move list -- second line */
3509                 /* Initial board will follow if this is a wild game */
3510                 if (gameInfo.event != NULL) free(gameInfo.event);
3511                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3512                 gameInfo.event = StrSave(str);
3513                 /* [HGM] we switched variant. Translate boards if needed. */
3514                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "Move  ")) {
3519                 /* Beginning of a move list */
3520                 switch (ics_getting_history) {
3521                   case H_FALSE:
3522                     /* Normally should not happen */
3523                     /* Maybe user hit reset while we were parsing */
3524                     break;
3525                   case H_REQUESTED:
3526                     /* Happens if we are ignoring a move list that is not
3527                      * the one we just requested.  Common if the user
3528                      * tries to observe two games without turning off
3529                      * getMoveList */
3530                     break;
3531                   case H_GETTING_MOVES:
3532                     /* Should not happen */
3533                     DisplayError(_("Error gathering move list: nested"), 0);
3534                     ics_getting_history = H_FALSE;
3535                     break;
3536                   case H_GOT_REQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES;
3539                     parse_pos = 0;
3540                     if (oldi > next_out) {
3541                         SendToPlayer(&buf[next_out], oldi - next_out);
3542                     }
3543                     break;
3544                   case H_GOT_UNREQ_HEADER:
3545                     ics_getting_history = H_GETTING_MOVES;
3546                     started = STARTED_MOVES_NOHIDE;
3547                     parse_pos = 0;
3548                     break;
3549                   case H_GOT_UNWANTED_HEADER:
3550                     ics_getting_history = H_FALSE;
3551                     break;
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i, "% ") ||
3557                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3558                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3559                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3560                     soughtPending = FALSE;
3561                     seekGraphUp = TRUE;
3562                     DrawSeekGraph();
3563                 }
3564                 if(suppressKibitz) next_out = i;
3565                 savingComment = FALSE;
3566                 suppressKibitz = 0;
3567                 switch (started) {
3568                   case STARTED_MOVES:
3569                   case STARTED_MOVES_NOHIDE:
3570                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3571                     parse[parse_pos + i - oldi] = NULLCHAR;
3572                     ParseGameHistory(parse);
3573 #if ZIPPY
3574                     if (appData.zippyPlay && first.initDone) {
3575                         FeedMovesToProgram(&first, forwardMostMove);
3576                         if (gameMode == IcsPlayingWhite) {
3577                             if (WhiteOnMove(forwardMostMove)) {
3578                                 if (first.sendTime) {
3579                                   if (first.useColors) {
3580                                     SendToProgram("black\n", &first);
3581                                   }
3582                                   SendTimeRemaining(&first, TRUE);
3583                                 }
3584                                 if (first.useColors) {
3585                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3586                                 }
3587                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3588                                 first.maybeThinking = TRUE;
3589                             } else {
3590                                 if (first.usePlayother) {
3591                                   if (first.sendTime) {
3592                                     SendTimeRemaining(&first, TRUE);
3593                                   }
3594                                   SendToProgram("playother\n", &first);
3595                                   firstMove = FALSE;
3596                                 } else {
3597                                   firstMove = TRUE;
3598                                 }
3599                             }
3600                         } else if (gameMode == IcsPlayingBlack) {
3601                             if (!WhiteOnMove(forwardMostMove)) {
3602                                 if (first.sendTime) {
3603                                   if (first.useColors) {
3604                                     SendToProgram("white\n", &first);
3605                                   }
3606                                   SendTimeRemaining(&first, FALSE);
3607                                 }
3608                                 if (first.useColors) {
3609                                   SendToProgram("black\n", &first);
3610                                 }
3611                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3612                                 first.maybeThinking = TRUE;
3613                             } else {
3614                                 if (first.usePlayother) {
3615                                   if (first.sendTime) {
3616                                     SendTimeRemaining(&first, FALSE);
3617                                   }
3618                                   SendToProgram("playother\n", &first);
3619                                   firstMove = FALSE;
3620                                 } else {
3621                                   firstMove = TRUE;
3622                                 }
3623                             }
3624                         }
3625                     }
3626 #endif
3627                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3628                         /* Moves came from oldmoves or moves command
3629                            while we weren't doing anything else.
3630                            */
3631                         currentMove = forwardMostMove;
3632                         ClearHighlights();/*!!could figure this out*/
3633                         flipView = appData.flipView;
3634                         DrawPosition(TRUE, boards[currentMove]);
3635                         DisplayBothClocks();
3636                         snprintf(str, MSG_SIZ, "%s %s %s",
3637                                 gameInfo.white, _("vs."),  gameInfo.black);
3638                         DisplayTitle(str);
3639                         gameMode = IcsIdle;
3640                     } else {
3641                         /* Moves were history of an active game */
3642                         if (gameInfo.resultDetails != NULL) {
3643                             free(gameInfo.resultDetails);
3644                             gameInfo.resultDetails = NULL;
3645                         }
3646                     }
3647                     HistorySet(parseList, backwardMostMove,
3648                                forwardMostMove, currentMove-1);
3649                     DisplayMove(currentMove - 1);
3650                     if (started == STARTED_MOVES) next_out = i;
3651                     started = STARTED_NONE;
3652                     ics_getting_history = H_FALSE;
3653                     break;
3654
3655                   case STARTED_OBSERVE:
3656                     started = STARTED_NONE;
3657                     SendToICS(ics_prefix);
3658                     SendToICS("refresh\n");
3659                     break;
3660
3661                   default:
3662                     break;
3663                 }
3664                 if(bookHit) { // [HGM] book: simulate book reply
3665                     static char bookMove[MSG_SIZ]; // a bit generous?
3666
3667                     programStats.nodes = programStats.depth = programStats.time =
3668                     programStats.score = programStats.got_only_move = 0;
3669                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3670
3671                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3672                     strcat(bookMove, bookHit);
3673                     HandleMachineMove(bookMove, &first);
3674                 }
3675                 continue;
3676             }
3677
3678             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3679                  started == STARTED_HOLDINGS ||
3680                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3681                 /* Accumulate characters in move list or board */
3682                 parse[parse_pos++] = buf[i];
3683             }
3684
3685             /* Start of game messages.  Mostly we detect start of game
3686                when the first board image arrives.  On some versions
3687                of the ICS, though, we need to do a "refresh" after starting
3688                to observe in order to get the current board right away. */
3689             if (looking_at(buf, &i, "Adding game * to observation list")) {
3690                 started = STARTED_OBSERVE;
3691                 continue;
3692             }
3693
3694             /* Handle auto-observe */
3695             if (appData.autoObserve &&
3696                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3697                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3698                 char *player;
3699                 /* Choose the player that was highlighted, if any. */
3700                 if (star_match[0][0] == '\033' ||
3701                     star_match[1][0] != '\033') {
3702                     player = star_match[0];
3703                 } else {
3704                     player = star_match[2];
3705                 }
3706                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3707                         ics_prefix, StripHighlightAndTitle(player));
3708                 SendToICS(str);
3709
3710                 /* Save ratings from notify string */
3711                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3712                 player1Rating = string_to_rating(star_match[1]);
3713                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3714                 player2Rating = string_to_rating(star_match[3]);
3715
3716                 if (appData.debugMode)
3717                   fprintf(debugFP,
3718                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3719                           player1Name, player1Rating,
3720                           player2Name, player2Rating);
3721
3722                 continue;
3723             }
3724
3725             /* Deal with automatic examine mode after a game,
3726                and with IcsObserving -> IcsExamining transition */
3727             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3728                 looking_at(buf, &i, "has made you an examiner of game *")) {
3729
3730                 int gamenum = atoi(star_match[0]);
3731                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3732                     gamenum == ics_gamenum) {
3733                     /* We were already playing or observing this game;
3734                        no need to refetch history */
3735                     gameMode = IcsExamining;
3736                     if (pausing) {
3737                         pauseExamForwardMostMove = forwardMostMove;
3738                     } else if (currentMove < forwardMostMove) {
3739                         ForwardInner(forwardMostMove);
3740                     }
3741                 } else {
3742                     /* I don't think this case really can happen */
3743                     SendToICS(ics_prefix);
3744                     SendToICS("refresh\n");
3745                 }
3746                 continue;
3747             }
3748
3749             /* Error messages */
3750 //          if (ics_user_moved) {
3751             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3752                 if (looking_at(buf, &i, "Illegal move") ||
3753                     looking_at(buf, &i, "Not a legal move") ||
3754                     looking_at(buf, &i, "Your king is in check") ||
3755                     looking_at(buf, &i, "It isn't your turn") ||
3756                     looking_at(buf, &i, "It is not your move")) {
3757                     /* Illegal move */
3758                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3759                         currentMove = forwardMostMove-1;
3760                         DisplayMove(currentMove - 1); /* before DMError */
3761                         DrawPosition(FALSE, boards[currentMove]);
3762                         SwitchClocks(forwardMostMove-1); // [HGM] race
3763                         DisplayBothClocks();
3764                     }
3765                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3766                     ics_user_moved = 0;
3767                     continue;
3768                 }
3769             }
3770
3771             if (looking_at(buf, &i, "still have time") ||
3772                 looking_at(buf, &i, "not out of time") ||
3773                 looking_at(buf, &i, "either player is out of time") ||
3774                 looking_at(buf, &i, "has timeseal; checking")) {
3775                 /* We must have called his flag a little too soon */
3776                 whiteFlag = blackFlag = FALSE;
3777                 continue;
3778             }
3779
3780             if (looking_at(buf, &i, "added * seconds to") ||
3781                 looking_at(buf, &i, "seconds were added to")) {
3782                 /* Update the clocks */
3783                 SendToICS(ics_prefix);
3784                 SendToICS("refresh\n");
3785                 continue;
3786             }
3787
3788             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3789                 ics_clock_paused = TRUE;
3790                 StopClocks();
3791                 continue;
3792             }
3793
3794             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3795                 ics_clock_paused = FALSE;
3796                 StartClocks();
3797                 continue;
3798             }
3799
3800             /* Grab player ratings from the Creating: message.
3801                Note we have to check for the special case when
3802                the ICS inserts things like [white] or [black]. */
3803             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3804                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3805                 /* star_matches:
3806                    0    player 1 name (not necessarily white)
3807                    1    player 1 rating
3808                    2    empty, white, or black (IGNORED)
3809                    3    player 2 name (not necessarily black)
3810                    4    player 2 rating
3811
3812                    The names/ratings are sorted out when the game
3813                    actually starts (below).
3814                 */
3815                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3816                 player1Rating = string_to_rating(star_match[1]);
3817                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3818                 player2Rating = string_to_rating(star_match[4]);
3819
3820                 if (appData.debugMode)
3821                   fprintf(debugFP,
3822                           "Ratings from 'Creating:' %s %d, %s %d\n",
3823                           player1Name, player1Rating,
3824                           player2Name, player2Rating);
3825
3826                 continue;
3827             }
3828
3829             /* Improved generic start/end-of-game messages */
3830             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3831                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3832                 /* If tkind == 0: */
3833                 /* star_match[0] is the game number */
3834                 /*           [1] is the white player's name */
3835                 /*           [2] is the black player's name */
3836                 /* For end-of-game: */
3837                 /*           [3] is the reason for the game end */
3838                 /*           [4] is a PGN end game-token, preceded by " " */
3839                 /* For start-of-game: */
3840                 /*           [3] begins with "Creating" or "Continuing" */
3841                 /*           [4] is " *" or empty (don't care). */
3842                 int gamenum = atoi(star_match[0]);
3843                 char *whitename, *blackname, *why, *endtoken;
3844                 ChessMove endtype = EndOfFile;
3845
3846                 if (tkind == 0) {
3847                   whitename = star_match[1];
3848                   blackname = star_match[2];
3849                   why = star_match[3];
3850                   endtoken = star_match[4];
3851                 } else {
3852                   whitename = star_match[1];
3853                   blackname = star_match[3];
3854                   why = star_match[5];
3855                   endtoken = star_match[6];
3856                 }
3857
3858                 /* Game start messages */
3859                 if (strncmp(why, "Creating ", 9) == 0 ||
3860                     strncmp(why, "Continuing ", 11) == 0) {
3861                     gs_gamenum = gamenum;
3862                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3863                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3864                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3865 #if ZIPPY
3866                     if (appData.zippyPlay) {
3867                         ZippyGameStart(whitename, blackname);
3868                     }
3869 #endif /*ZIPPY*/
3870                     partnerBoardValid = FALSE; // [HGM] bughouse
3871                     continue;
3872                 }
3873
3874                 /* Game end messages */
3875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3876                     ics_gamenum != gamenum) {
3877                     continue;
3878                 }
3879                 while (endtoken[0] == ' ') endtoken++;
3880                 switch (endtoken[0]) {
3881                   case '*':
3882                   default:
3883                     endtype = GameUnfinished;
3884                     break;
3885                   case '0':
3886                     endtype = BlackWins;
3887                     break;
3888                   case '1':
3889                     if (endtoken[1] == '/')
3890                       endtype = GameIsDrawn;
3891                     else
3892                       endtype = WhiteWins;
3893                     break;
3894                 }
3895                 GameEnds(endtype, why, GE_ICS);
3896 #if ZIPPY
3897                 if (appData.zippyPlay && first.initDone) {
3898                     ZippyGameEnd(endtype, why);
3899                     if (first.pr == NoProc) {
3900                       /* Start the next process early so that we'll
3901                          be ready for the next challenge */
3902                       StartChessProgram(&first);
3903                     }
3904                     /* Send "new" early, in case this command takes
3905                        a long time to finish, so that we'll be ready
3906                        for the next challenge. */
3907                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3908                     Reset(TRUE, TRUE);
3909                 }
3910 #endif /*ZIPPY*/
3911                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "Removing game * from observation") ||
3916                 looking_at(buf, &i, "no longer observing game *") ||
3917                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3918                 if (gameMode == IcsObserving &&
3919                     atoi(star_match[0]) == ics_gamenum)
3920                   {
3921                       /* icsEngineAnalyze */
3922                       if (appData.icsEngineAnalyze) {
3923                             ExitAnalyzeMode();
3924                             ModeHighlight();
3925                       }
3926                       StopClocks();
3927                       gameMode = IcsIdle;
3928                       ics_gamenum = -1;
3929                       ics_user_moved = FALSE;
3930                   }
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "no longer examining game *")) {
3935                 if (gameMode == IcsExamining &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       gameMode = IcsIdle;
3939                       ics_gamenum = -1;
3940                       ics_user_moved = FALSE;
3941                   }
3942                 continue;
3943             }
3944
3945             /* Advance leftover_start past any newlines we find,
3946                so only partial lines can get reparsed */
3947             if (looking_at(buf, &i, "\n")) {
3948                 prevColor = curColor;
3949                 if (curColor != ColorNormal) {
3950                     if (oldi > next_out) {
3951                         SendToPlayer(&buf[next_out], oldi - next_out);
3952                         next_out = oldi;
3953                     }
3954                     Colorize(ColorNormal, FALSE);
3955                     curColor = ColorNormal;
3956                 }
3957                 if (started == STARTED_BOARD) {
3958                     started = STARTED_NONE;
3959                     parse[parse_pos] = NULLCHAR;
3960                     ParseBoard12(parse);
3961                     ics_user_moved = 0;
3962
3963                     /* Send premove here */
3964                     if (appData.premove) {
3965                       char str[MSG_SIZ];
3966                       if (currentMove == 0 &&
3967                           gameMode == IcsPlayingWhite &&
3968                           appData.premoveWhite) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (currentMove == 1 &&
3974                                  gameMode == IcsPlayingBlack &&
3975                                  appData.premoveBlack) {
3976                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3977                         if (appData.debugMode)
3978                           fprintf(debugFP, "Sending premove:\n");
3979                         SendToICS(str);
3980                       } else if (gotPremove) {
3981                         gotPremove = 0;
3982                         ClearPremoveHighlights();
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                           UserMoveEvent(premoveFromX, premoveFromY,
3986                                         premoveToX, premoveToY,
3987                                         premovePromoChar);
3988                       }
3989                     }
3990
3991                     /* Usually suppress following prompt */
3992                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3993                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3994                         if (looking_at(buf, &i, "*% ")) {
3995                             savingComment = FALSE;
3996                             suppressKibitz = 0;
3997                         }
3998                     }
3999                     next_out = i;
4000                 } else if (started == STARTED_HOLDINGS) {
4001                     int gamenum;
4002                     char new_piece[MSG_SIZ];
4003                     started = STARTED_NONE;
4004                     parse[parse_pos] = NULLCHAR;
4005                     if (appData.debugMode)
4006                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4007                                                         parse, currentMove);
4008                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4009                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4010                         if (gameInfo.variant == VariantNormal) {
4011                           /* [HGM] We seem to switch variant during a game!
4012                            * Presumably no holdings were displayed, so we have
4013                            * to move the position two files to the right to
4014                            * create room for them!
4015                            */
4016                           VariantClass newVariant;
4017                           switch(gameInfo.boardWidth) { // base guess on board width
4018                                 case 9:  newVariant = VariantShogi; break;
4019                                 case 10: newVariant = VariantGreat; break;
4020                                 default: newVariant = VariantCrazyhouse; break;
4021                           }
4022                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4023                           /* Get a move list just to see the header, which
4024                              will tell us whether this is really bug or zh */
4025                           if (ics_getting_history == H_FALSE) {
4026                             ics_getting_history = H_REQUESTED;
4027                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4028                             SendToICS(str);
4029                           }
4030                         }
4031                         new_piece[0] = NULLCHAR;
4032                         sscanf(parse, "game %d white [%s black [%s <- %s",
4033                                &gamenum, white_holding, black_holding,
4034                                new_piece);
4035                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4036                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4037                         /* [HGM] copy holdings to board holdings area */
4038                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4039                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4040                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4041 #if ZIPPY
4042                         if (appData.zippyPlay && first.initDone) {
4043                             ZippyHoldings(white_holding, black_holding,
4044                                           new_piece);
4045                         }
4046 #endif /*ZIPPY*/
4047                         if (tinyLayout || smallLayout) {
4048                             char wh[16], bh[16];
4049                             PackHolding(wh, white_holding);
4050                             PackHolding(bh, black_holding);
4051                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4052                                     gameInfo.white, gameInfo.black);
4053                         } else {
4054                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4055                                     gameInfo.white, white_holding, _("vs."),
4056                                     gameInfo.black, black_holding);
4057                         }
4058                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4059                         DrawPosition(FALSE, boards[currentMove]);
4060                         DisplayTitle(str);
4061                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to partner-board holdings area */
4068                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4069                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4070                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4071                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4072                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4073                       }
4074                     }
4075                     /* Suppress following prompt */
4076                     if (looking_at(buf, &i, "*% ")) {
4077                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4078                         savingComment = FALSE;
4079                         suppressKibitz = 0;
4080                     }
4081                     next_out = i;
4082                 }
4083                 continue;
4084             }
4085
4086             i++;                /* skip unparsed character and loop back */
4087         }
4088
4089         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4090 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4091 //          SendToPlayer(&buf[next_out], i - next_out);
4092             started != STARTED_HOLDINGS && leftover_start > next_out) {
4093             SendToPlayer(&buf[next_out], leftover_start - next_out);
4094             next_out = i;
4095         }
4096
4097         leftover_len = buf_len - leftover_start;
4098         /* if buffer ends with something we couldn't parse,
4099            reparse it after appending the next read */
4100
4101     } else if (count == 0) {
4102         RemoveInputSource(isr);
4103         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4104     } else {
4105         DisplayFatalError(_("Error reading from ICS"), error, 1);
4106     }
4107 }
4108
4109
4110 /* Board style 12 looks like this:
4111
4112    <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
4113
4114  * The "<12> " is stripped before it gets to this routine.  The two
4115  * trailing 0's (flip state and clock ticking) are later addition, and
4116  * some chess servers may not have them, or may have only the first.
4117  * Additional trailing fields may be added in the future.
4118  */
4119
4120 #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"
4121
4122 #define RELATION_OBSERVING_PLAYED    0
4123 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4124 #define RELATION_PLAYING_MYMOVE      1
4125 #define RELATION_PLAYING_NOTMYMOVE  -1
4126 #define RELATION_EXAMINING           2
4127 #define RELATION_ISOLATED_BOARD     -3
4128 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4129
4130 void
4131 ParseBoard12 (char *string)
4132 {
4133     GameMode newGameMode;
4134     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4135     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4136     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4137     char to_play, board_chars[200];
4138     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4139     char black[32], white[32];
4140     Board board;
4141     int prevMove = currentMove;
4142     int ticking = 2;
4143     ChessMove moveType;
4144     int fromX, fromY, toX, toY;
4145     char promoChar;
4146     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4147     char *bookHit = NULL; // [HGM] book
4148     Boolean weird = FALSE, reqFlag = FALSE;
4149
4150     fromX = fromY = toX = toY = -1;
4151
4152     newGame = FALSE;
4153
4154     if (appData.debugMode)
4155       fprintf(debugFP, _("Parsing board: %s\n"), string);
4156
4157     move_str[0] = NULLCHAR;
4158     elapsed_time[0] = NULLCHAR;
4159     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4160         int  i = 0, j;
4161         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4162             if(string[i] == ' ') { ranks++; files = 0; }
4163             else files++;
4164             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4165             i++;
4166         }
4167         for(j = 0; j <i; j++) board_chars[j] = string[j];
4168         board_chars[i] = '\0';
4169         string += i + 1;
4170     }
4171     n = sscanf(string, PATTERN, &to_play, &double_push,
4172                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4173                &gamenum, white, black, &relation, &basetime, &increment,
4174                &white_stren, &black_stren, &white_time, &black_time,
4175                &moveNum, str, elapsed_time, move_str, &ics_flip,
4176                &ticking);
4177
4178     if (n < 21) {
4179         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4180         DisplayError(str, 0);
4181         return;
4182     }
4183
4184     /* Convert the move number to internal form */
4185     moveNum = (moveNum - 1) * 2;
4186     if (to_play == 'B') moveNum++;
4187     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4188       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4189                         0, 1);
4190       return;
4191     }
4192
4193     switch (relation) {
4194       case RELATION_OBSERVING_PLAYED:
4195       case RELATION_OBSERVING_STATIC:
4196         if (gamenum == -1) {
4197             /* Old ICC buglet */
4198             relation = RELATION_OBSERVING_STATIC;
4199         }
4200         newGameMode = IcsObserving;
4201         break;
4202       case RELATION_PLAYING_MYMOVE:
4203       case RELATION_PLAYING_NOTMYMOVE:
4204         newGameMode =
4205           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4206             IcsPlayingWhite : IcsPlayingBlack;
4207         break;
4208       case RELATION_EXAMINING:
4209         newGameMode = IcsExamining;
4210         break;
4211       case RELATION_ISOLATED_BOARD:
4212       default:
4213         /* Just display this board.  If user was doing something else,
4214            we will forget about it until the next board comes. */
4215         newGameMode = IcsIdle;
4216         break;
4217       case RELATION_STARTING_POSITION:
4218         newGameMode = gameMode;
4219         break;
4220     }
4221
4222     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4223          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4224       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4225       char *toSqr;
4226       for (k = 0; k < ranks; k++) {
4227         for (j = 0; j < files; j++)
4228           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4229         if(gameInfo.holdingsWidth > 1) {
4230              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4231              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4232         }
4233       }
4234       CopyBoard(partnerBoard, board);
4235       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4236         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4237         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4239       if(toSqr = strchr(str, '-')) {
4240         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4241         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4243       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4244       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4245       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4246       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4247       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4248                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4249       DisplayMessage(partnerStatus, "");
4250         partnerBoardValid = TRUE;
4251       return;
4252     }
4253
4254     /* Modify behavior for initial board display on move listing
4255        of wild games.
4256        */
4257     switch (ics_getting_history) {
4258       case H_FALSE:
4259       case H_REQUESTED:
4260         break;
4261       case H_GOT_REQ_HEADER:
4262       case H_GOT_UNREQ_HEADER:
4263         /* This is the initial position of the current game */
4264         gamenum = ics_gamenum;
4265         moveNum = 0;            /* old ICS bug workaround */
4266         if (to_play == 'B') {
4267           startedFromSetupPosition = TRUE;
4268           blackPlaysFirst = TRUE;
4269           moveNum = 1;
4270           if (forwardMostMove == 0) forwardMostMove = 1;
4271           if (backwardMostMove == 0) backwardMostMove = 1;
4272           if (currentMove == 0) currentMove = 1;
4273         }
4274         newGameMode = gameMode;
4275         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4276         break;
4277       case H_GOT_UNWANTED_HEADER:
4278         /* This is an initial board that we don't want */
4279         return;
4280       case H_GETTING_MOVES:
4281         /* Should not happen */
4282         DisplayError(_("Error gathering move list: extra board"), 0);
4283         ics_getting_history = H_FALSE;
4284         return;
4285     }
4286
4287    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4288                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4289      /* [HGM] We seem to have switched variant unexpectedly
4290       * Try to guess new variant from board size
4291       */
4292           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4293           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4294           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4295           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4296           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4297           if(!weird) newVariant = VariantNormal;
4298           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4299           /* Get a move list just to see the header, which
4300              will tell us whether this is really bug or zh */
4301           if (ics_getting_history == H_FALSE) {
4302             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4303             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4304             SendToICS(str);
4305           }
4306     }
4307
4308     /* Take action if this is the first board of a new game, or of a
4309        different game than is currently being displayed.  */
4310     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4311         relation == RELATION_ISOLATED_BOARD) {
4312
4313         /* Forget the old game and get the history (if any) of the new one */
4314         if (gameMode != BeginningOfGame) {
4315           Reset(TRUE, TRUE);
4316         }
4317         newGame = TRUE;
4318         if (appData.autoRaiseBoard) BoardToTop();
4319         prevMove = -3;
4320         if (gamenum == -1) {
4321             newGameMode = IcsIdle;
4322         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4323                    appData.getMoveList && !reqFlag) {
4324             /* Need to get game history */
4325             ics_getting_history = H_REQUESTED;
4326             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4327             SendToICS(str);
4328         }
4329
4330         /* Initially flip the board to have black on the bottom if playing
4331            black or if the ICS flip flag is set, but let the user change
4332            it with the Flip View button. */
4333         flipView = appData.autoFlipView ?
4334           (newGameMode == IcsPlayingBlack) || ics_flip :
4335           appData.flipView;
4336
4337         /* Done with values from previous mode; copy in new ones */
4338         gameMode = newGameMode;
4339         ModeHighlight();
4340         ics_gamenum = gamenum;
4341         if (gamenum == gs_gamenum) {
4342             int klen = strlen(gs_kind);
4343             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4344             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4345             gameInfo.event = StrSave(str);
4346         } else {
4347             gameInfo.event = StrSave("ICS game");
4348         }
4349         gameInfo.site = StrSave(appData.icsHost);
4350         gameInfo.date = PGNDate();
4351         gameInfo.round = StrSave("-");
4352         gameInfo.white = StrSave(white);
4353         gameInfo.black = StrSave(black);
4354         timeControl = basetime * 60 * 1000;
4355         timeControl_2 = 0;
4356         timeIncrement = increment * 1000;
4357         movesPerSession = 0;
4358         gameInfo.timeControl = TimeControlTagValue();
4359         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4360   if (appData.debugMode) {
4361     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4362     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4363     setbuf(debugFP, NULL);
4364   }
4365
4366         gameInfo.outOfBook = NULL;
4367
4368         /* Do we have the ratings? */
4369         if (strcmp(player1Name, white) == 0 &&
4370             strcmp(player2Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player1Rating, player2Rating);
4374             gameInfo.whiteRating = player1Rating;
4375             gameInfo.blackRating = player2Rating;
4376         } else if (strcmp(player2Name, white) == 0 &&
4377                    strcmp(player1Name, black) == 0) {
4378             if (appData.debugMode)
4379               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4380                       player2Rating, player1Rating);
4381             gameInfo.whiteRating = player2Rating;
4382             gameInfo.blackRating = player1Rating;
4383         }
4384         player1Name[0] = player2Name[0] = NULLCHAR;
4385
4386         /* Silence shouts if requested */
4387         if (appData.quietPlay &&
4388             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4389             SendToICS(ics_prefix);
4390             SendToICS("set shout 0\n");
4391         }
4392     }
4393
4394     /* Deal with midgame name changes */
4395     if (!newGame) {
4396         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4397             if (gameInfo.white) free(gameInfo.white);
4398             gameInfo.white = StrSave(white);
4399         }
4400         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4401             if (gameInfo.black) free(gameInfo.black);
4402             gameInfo.black = StrSave(black);
4403         }
4404     }
4405
4406     /* Throw away game result if anything actually changes in examine mode */
4407     if (gameMode == IcsExamining && !newGame) {
4408         gameInfo.result = GameUnfinished;
4409         if (gameInfo.resultDetails != NULL) {
4410             free(gameInfo.resultDetails);
4411             gameInfo.resultDetails = NULL;
4412         }
4413     }
4414
4415     /* In pausing && IcsExamining mode, we ignore boards coming
4416        in if they are in a different variation than we are. */
4417     if (pauseExamInvalid) return;
4418     if (pausing && gameMode == IcsExamining) {
4419         if (moveNum <= pauseExamForwardMostMove) {
4420             pauseExamInvalid = TRUE;
4421             forwardMostMove = pauseExamForwardMostMove;
4422             return;
4423         }
4424     }
4425
4426   if (appData.debugMode) {
4427     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4428   }
4429     /* Parse the board */
4430     for (k = 0; k < ranks; k++) {
4431       for (j = 0; j < files; j++)
4432         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4433       if(gameInfo.holdingsWidth > 1) {
4434            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4435            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4436       }
4437     }
4438     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4439       board[5][BOARD_RGHT+1] = WhiteAngel;
4440       board[6][BOARD_RGHT+1] = WhiteMarshall;
4441       board[1][0] = BlackMarshall;
4442       board[2][0] = BlackAngel;
4443       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4444     }
4445     CopyBoard(boards[moveNum], board);
4446     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4447     if (moveNum == 0) {
4448         startedFromSetupPosition =
4449           !CompareBoards(board, initialPosition);
4450         if(startedFromSetupPosition)
4451             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4452     }
4453
4454     /* [HGM] Set castling rights. Take the outermost Rooks,
4455        to make it also work for FRC opening positions. Note that board12
4456        is really defective for later FRC positions, as it has no way to
4457        indicate which Rook can castle if they are on the same side of King.
4458        For the initial position we grant rights to the outermost Rooks,
4459        and remember thos rights, and we then copy them on positions
4460        later in an FRC game. This means WB might not recognize castlings with
4461        Rooks that have moved back to their original position as illegal,
4462        but in ICS mode that is not its job anyway.
4463     */
4464     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4465     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4466
4467         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4471             if(board[0][i] == WhiteRook) j = i;
4472         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4478         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479
4480         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4481         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4482         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4483             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4484         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4485             if(board[BOARD_HEIGHT-1][k] == bKing)
4486                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4487         if(gameInfo.variant == VariantTwoKings) {
4488             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4489             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4490             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4491         }
4492     } else { int r;
4493         r = boards[moveNum][CASTLING][0] = initialRights[0];
4494         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4495         r = boards[moveNum][CASTLING][1] = initialRights[1];
4496         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4497         r = boards[moveNum][CASTLING][3] = initialRights[3];
4498         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4499         r = boards[moveNum][CASTLING][4] = initialRights[4];
4500         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4501         /* wildcastle kludge: always assume King has rights */
4502         r = boards[moveNum][CASTLING][2] = initialRights[2];
4503         r = boards[moveNum][CASTLING][5] = initialRights[5];
4504     }
4505     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4506     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4507
4508
4509     if (ics_getting_history == H_GOT_REQ_HEADER ||
4510         ics_getting_history == H_GOT_UNREQ_HEADER) {
4511         /* This was an initial position from a move list, not
4512            the current position */
4513         return;
4514     }
4515
4516     /* Update currentMove and known move number limits */
4517     newMove = newGame || moveNum > forwardMostMove;
4518
4519     if (newGame) {
4520         forwardMostMove = backwardMostMove = currentMove = moveNum;
4521         if (gameMode == IcsExamining && moveNum == 0) {
4522           /* Workaround for ICS limitation: we are not told the wild
4523              type when starting to examine a game.  But if we ask for
4524              the move list, the move list header will tell us */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4530                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4531 #if ZIPPY
4532         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4533         /* [HGM] applied this also to an engine that is silently watching        */
4534         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4535             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4536             gameInfo.variant == currentlyInitializedVariant) {
4537           takeback = forwardMostMove - moveNum;
4538           for (i = 0; i < takeback; i++) {
4539             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4540             SendToProgram("undo\n", &first);
4541           }
4542         }
4543 #endif
4544
4545         forwardMostMove = moveNum;
4546         if (!pausing || currentMove > forwardMostMove)
4547           currentMove = forwardMostMove;
4548     } else {
4549         /* New part of history that is not contiguous with old part */
4550         if (pausing && gameMode == IcsExamining) {
4551             pauseExamInvalid = TRUE;
4552             forwardMostMove = pauseExamForwardMostMove;
4553             return;
4554         }
4555         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4556 #if ZIPPY
4557             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4558                 // [HGM] when we will receive the move list we now request, it will be
4559                 // fed to the engine from the first move on. So if the engine is not
4560                 // in the initial position now, bring it there.
4561                 InitChessProgram(&first, 0);
4562             }
4563 #endif
4564             ics_getting_history = H_REQUESTED;
4565             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4566             SendToICS(str);
4567         }
4568         forwardMostMove = backwardMostMove = currentMove = moveNum;
4569     }
4570
4571     /* Update the clocks */
4572     if (strchr(elapsed_time, '.')) {
4573       /* Time is in ms */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4576     } else {
4577       /* Time is in seconds */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4580     }
4581
4582
4583 #if ZIPPY
4584     if (appData.zippyPlay && newGame &&
4585         gameMode != IcsObserving && gameMode != IcsIdle &&
4586         gameMode != IcsExamining)
4587       ZippyFirstBoard(moveNum, basetime, increment);
4588 #endif
4589
4590     /* Put the move on the move list, first converting
4591        to canonical algebraic form. */
4592     if (moveNum > 0) {
4593   if (appData.debugMode) {
4594     if (appData.debugMode) { int f = forwardMostMove;
4595         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4596                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4597                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4598     }
4599     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4600     fprintf(debugFP, "moveNum = %d\n", moveNum);
4601     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4602     setbuf(debugFP, NULL);
4603   }
4604         if (moveNum <= backwardMostMove) {
4605             /* We don't know what the board looked like before
4606                this move.  Punt. */
4607           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4608             strcat(parseList[moveNum - 1], " ");
4609             strcat(parseList[moveNum - 1], elapsed_time);
4610             moveList[moveNum - 1][0] = NULLCHAR;
4611         } else if (strcmp(move_str, "none") == 0) {
4612             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4613             /* Again, we don't know what the board looked like;
4614                this is really the start of the game. */
4615             parseList[moveNum - 1][0] = NULLCHAR;
4616             moveList[moveNum - 1][0] = NULLCHAR;
4617             backwardMostMove = moveNum;
4618             startedFromSetupPosition = TRUE;
4619             fromX = fromY = toX = toY = -1;
4620         } else {
4621           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4622           //                 So we parse the long-algebraic move string in stead of the SAN move
4623           int valid; char buf[MSG_SIZ], *prom;
4624
4625           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4626                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4627           // str looks something like "Q/a1-a2"; kill the slash
4628           if(str[1] == '/')
4629             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4630           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4631           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4632                 strcat(buf, prom); // long move lacks promo specification!
4633           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4634                 if(appData.debugMode)
4635                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4636                 safeStrCpy(move_str, buf, MSG_SIZ);
4637           }
4638           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4639                                 &fromX, &fromY, &toX, &toY, &promoChar)
4640                || ParseOneMove(buf, moveNum - 1, &moveType,
4641                                 &fromX, &fromY, &toX, &toY, &promoChar);
4642           // end of long SAN patch
4643           if (valid) {
4644             (void) CoordsToAlgebraic(boards[moveNum - 1],
4645                                      PosFlags(moveNum - 1),
4646                                      fromY, fromX, toY, toX, promoChar,
4647                                      parseList[moveNum-1]);
4648             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4649               case MT_NONE:
4650               case MT_STALEMATE:
4651               default:
4652                 break;
4653               case MT_CHECK:
4654                 if(gameInfo.variant != VariantShogi)
4655                     strcat(parseList[moveNum - 1], "+");
4656                 break;
4657               case MT_CHECKMATE:
4658               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4659                 strcat(parseList[moveNum - 1], "#");
4660                 break;
4661             }
4662             strcat(parseList[moveNum - 1], " ");
4663             strcat(parseList[moveNum - 1], elapsed_time);
4664             /* currentMoveString is set as a side-effect of ParseOneMove */
4665             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4666             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4667             strcat(moveList[moveNum - 1], "\n");
4668
4669             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4670                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4671               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4672                 ChessSquare old, new = boards[moveNum][k][j];
4673                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4674                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4675                   if(old == new) continue;
4676                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4677                   else if(new == WhiteWazir || new == BlackWazir) {
4678                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4679                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4680                       else boards[moveNum][k][j] = old; // preserve type of Gold
4681                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4682                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4683               }
4684           } else {
4685             /* Move from ICS was illegal!?  Punt. */
4686             if (appData.debugMode) {
4687               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4688               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4689             }
4690             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694             fromX = fromY = toX = toY = -1;
4695           }
4696         }
4697   if (appData.debugMode) {
4698     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4699     setbuf(debugFP, NULL);
4700   }
4701
4702 #if ZIPPY
4703         /* Send move to chess program (BEFORE animating it). */
4704         if (appData.zippyPlay && !newGame && newMove &&
4705            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4706
4707             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4708                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4709                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4710                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4711                             move_str);
4712                     DisplayError(str, 0);
4713                 } else {
4714                     if (first.sendTime) {
4715                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4716                     }
4717                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4718                     if (firstMove && !bookHit) {
4719                         firstMove = FALSE;
4720                         if (first.useColors) {
4721                           SendToProgram(gameMode == IcsPlayingWhite ?
4722                                         "white\ngo\n" :
4723                                         "black\ngo\n", &first);
4724                         } else {
4725                           SendToProgram("go\n", &first);
4726                         }
4727                         first.maybeThinking = TRUE;
4728                     }
4729                 }
4730             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4731               if (moveList[moveNum - 1][0] == NULLCHAR) {
4732                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4733                 DisplayError(str, 0);
4734               } else {
4735                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4736                 SendMoveToProgram(moveNum - 1, &first);
4737               }
4738             }
4739         }
4740 #endif
4741     }
4742
4743     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4744         /* If move comes from a remote source, animate it.  If it
4745            isn't remote, it will have already been animated. */
4746         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4747             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4748         }
4749         if (!pausing && appData.highlightLastMove) {
4750             SetHighlights(fromX, fromY, toX, toY);
4751         }
4752     }
4753
4754     /* Start the clocks */
4755     whiteFlag = blackFlag = FALSE;
4756     appData.clockMode = !(basetime == 0 && increment == 0);
4757     if (ticking == 0) {
4758       ics_clock_paused = TRUE;
4759       StopClocks();
4760     } else if (ticking == 1) {
4761       ics_clock_paused = FALSE;
4762     }
4763     if (gameMode == IcsIdle ||
4764         relation == RELATION_OBSERVING_STATIC ||
4765         relation == RELATION_EXAMINING ||
4766         ics_clock_paused)
4767       DisplayBothClocks();
4768     else
4769       StartClocks();
4770
4771     /* Display opponents and material strengths */
4772     if (gameInfo.variant != VariantBughouse &&
4773         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4774         if (tinyLayout || smallLayout) {
4775             if(gameInfo.variant == VariantNormal)
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment);
4779             else
4780               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment, (int) gameInfo.variant);
4783         } else {
4784             if(gameInfo.variant == VariantNormal)
4785               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4786                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4787                     basetime, increment);
4788             else
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment, VariantName(gameInfo.variant));
4792         }
4793         DisplayTitle(str);
4794   if (appData.debugMode) {
4795     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4796   }
4797     }
4798
4799
4800     /* Display the board */
4801     if (!pausing && !appData.noGUI) {
4802
4803       if (appData.premove)
4804           if (!gotPremove ||
4805              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4806              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4807               ClearPremoveHighlights();
4808
4809       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4810         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4811       DrawPosition(j, boards[currentMove]);
4812
4813       DisplayMove(moveNum - 1);
4814       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4815             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4816               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4817         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4818       }
4819     }
4820
4821     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4822 #if ZIPPY
4823     if(bookHit) { // [HGM] book: simulate book reply
4824         static char bookMove[MSG_SIZ]; // a bit generous?
4825
4826         programStats.nodes = programStats.depth = programStats.time =
4827         programStats.score = programStats.got_only_move = 0;
4828         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4829
4830         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4831         strcat(bookMove, bookHit);
4832         HandleMachineMove(bookMove, &first);
4833     }
4834 #endif
4835 }
4836
4837 void
4838 GetMoveListEvent ()
4839 {
4840     char buf[MSG_SIZ];
4841     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4842         ics_getting_history = H_REQUESTED;
4843         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4844         SendToICS(buf);
4845     }
4846 }
4847
4848 void
4849 AnalysisPeriodicEvent (int force)
4850 {
4851     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4852          && !force) || !appData.periodicUpdates)
4853       return;
4854
4855     /* Send . command to Crafty to collect stats */
4856     SendToProgram(".\n", &first);
4857
4858     /* Don't send another until we get a response (this makes
4859        us stop sending to old Crafty's which don't understand
4860        the "." command (sending illegal cmds resets node count & time,
4861        which looks bad)) */
4862     programStats.ok_to_send = 0;
4863 }
4864
4865 void
4866 ics_update_width (int new_width)
4867 {
4868         ics_printf("set width %d\n", new_width);
4869 }
4870
4871 void
4872 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4873 {
4874     char buf[MSG_SIZ];
4875
4876     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4877         // null move in variant where engine does not understand it (for analysis purposes)
4878         SendBoard(cps, moveNum + 1); // send position after move in stead.
4879         return;
4880     }
4881     if (cps->useUsermove) {
4882       SendToProgram("usermove ", cps);
4883     }
4884     if (cps->useSAN) {
4885       char *space;
4886       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4887         int len = space - parseList[moveNum];
4888         memcpy(buf, parseList[moveNum], len);
4889         buf[len++] = '\n';
4890         buf[len] = NULLCHAR;
4891       } else {
4892         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4893       }
4894       SendToProgram(buf, cps);
4895     } else {
4896       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4897         AlphaRank(moveList[moveNum], 4);
4898         SendToProgram(moveList[moveNum], cps);
4899         AlphaRank(moveList[moveNum], 4); // and back
4900       } else
4901       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4902        * the engine. It would be nice to have a better way to identify castle
4903        * moves here. */
4904       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4905                                                                          && cps->useOOCastle) {
4906         int fromX = moveList[moveNum][0] - AAA;
4907         int fromY = moveList[moveNum][1] - ONE;
4908         int toX = moveList[moveNum][2] - AAA;
4909         int toY = moveList[moveNum][3] - ONE;
4910         if((boards[moveNum][fromY][fromX] == WhiteKing
4911             && boards[moveNum][toY][toX] == WhiteRook)
4912            || (boards[moveNum][fromY][fromX] == BlackKing
4913                && boards[moveNum][toY][toX] == BlackRook)) {
4914           if(toX > fromX) SendToProgram("O-O\n", cps);
4915           else SendToProgram("O-O-O\n", cps);
4916         }
4917         else SendToProgram(moveList[moveNum], cps);
4918       } else
4919       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4920         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4921           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4922           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4923                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4924         } else
4925           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4926                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4927         SendToProgram(buf, cps);
4928       }
4929       else SendToProgram(moveList[moveNum], cps);
4930       /* End of additions by Tord */
4931     }
4932
4933     /* [HGM] setting up the opening has brought engine in force mode! */
4934     /*       Send 'go' if we are in a mode where machine should play. */
4935     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4936         (gameMode == TwoMachinesPlay   ||
4937 #if ZIPPY
4938          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4939 #endif
4940          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4941         SendToProgram("go\n", cps);
4942   if (appData.debugMode) {
4943     fprintf(debugFP, "(extra)\n");
4944   }
4945     }
4946     setboardSpoiledMachineBlack = 0;
4947 }
4948
4949 void
4950 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4951 {
4952     char user_move[MSG_SIZ];
4953     char suffix[4];
4954
4955     if(gameInfo.variant == VariantSChess && promoChar) {
4956         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4957         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4958     } else suffix[0] = NULLCHAR;
4959
4960     switch (moveType) {
4961       default:
4962         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4963                 (int)moveType, fromX, fromY, toX, toY);
4964         DisplayError(user_move + strlen("say "), 0);
4965         break;
4966       case WhiteKingSideCastle:
4967       case BlackKingSideCastle:
4968       case WhiteQueenSideCastleWild:
4969       case BlackQueenSideCastleWild:
4970       /* PUSH Fabien */
4971       case WhiteHSideCastleFR:
4972       case BlackHSideCastleFR:
4973       /* POP Fabien */
4974         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4975         break;
4976       case WhiteQueenSideCastle:
4977       case BlackQueenSideCastle:
4978       case WhiteKingSideCastleWild:
4979       case BlackKingSideCastleWild:
4980       /* PUSH Fabien */
4981       case WhiteASideCastleFR:
4982       case BlackASideCastleFR:
4983       /* POP Fabien */
4984         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4985         break;
4986       case WhiteNonPromotion:
4987       case BlackNonPromotion:
4988         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4989         break;
4990       case WhitePromotion:
4991       case BlackPromotion:
4992         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4993           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteFerz));
4996         else if(gameInfo.variant == VariantGreat)
4997           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteMan));
5000         else
5001           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 promoChar);
5004         break;
5005       case WhiteDrop:
5006       case BlackDrop:
5007       drop:
5008         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5009                  ToUpper(PieceToChar((ChessSquare) fromX)),
5010                  AAA + toX, ONE + toY);
5011         break;
5012       case IllegalMove:  /* could be a variant we don't quite understand */
5013         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5014       case NormalMove:
5015       case WhiteCapturesEnPassant:
5016       case BlackCapturesEnPassant:
5017         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5018                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5019         break;
5020     }
5021     SendToICS(user_move);
5022     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5023         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5024 }
5025
5026 void
5027 UploadGameEvent ()
5028 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5029     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5030     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5031     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5032       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5033       return;
5034     }
5035     if(gameMode != IcsExamining) { // is this ever not the case?
5036         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5037
5038         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5039           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5040         } else { // on FICS we must first go to general examine mode
5041           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5042         }
5043         if(gameInfo.variant != VariantNormal) {
5044             // try figure out wild number, as xboard names are not always valid on ICS
5045             for(i=1; i<=36; i++) {
5046               snprintf(buf, MSG_SIZ, "wild/%d", i);
5047                 if(StringToVariant(buf) == gameInfo.variant) break;
5048             }
5049             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5050             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5051             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5052         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5053         SendToICS(ics_prefix);
5054         SendToICS(buf);
5055         if(startedFromSetupPosition || backwardMostMove != 0) {
5056           fen = PositionToFEN(backwardMostMove, NULL);
5057           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5058             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5059             SendToICS(buf);
5060           } else { // FICS: everything has to set by separate bsetup commands
5061             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5062             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5063             SendToICS(buf);
5064             if(!WhiteOnMove(backwardMostMove)) {
5065                 SendToICS("bsetup tomove black\n");
5066             }
5067             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5068             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5069             SendToICS(buf);
5070             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5071             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5072             SendToICS(buf);
5073             i = boards[backwardMostMove][EP_STATUS];
5074             if(i >= 0) { // set e.p.
5075               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5076                 SendToICS(buf);
5077             }
5078             bsetup++;
5079           }
5080         }
5081       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5082             SendToICS("bsetup done\n"); // switch to normal examining.
5083     }
5084     for(i = backwardMostMove; i<last; i++) {
5085         char buf[20];
5086         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5087         SendToICS(buf);
5088     }
5089     SendToICS(ics_prefix);
5090     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5091 }
5092
5093 void
5094 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5095 {
5096     if (rf == DROP_RANK) {
5097       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5098       sprintf(move, "%c@%c%c\n",
5099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5100     } else {
5101         if (promoChar == 'x' || promoChar == NULLCHAR) {
5102           sprintf(move, "%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5104         } else {
5105             sprintf(move, "%c%c%c%c%c\n",
5106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5107         }
5108     }
5109 }
5110
5111 void
5112 ProcessICSInitScript (FILE *f)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep (int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = -1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int
5149 PromoScroll (int x, int y)
5150 {
5151   int step = 0;
5152
5153   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5154   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5155   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5156   if(!step) return FALSE;
5157   lastX = x; lastY = y;
5158   if((promoSweep < BlackPawn) == flipView) step = -step;
5159   if(step > 0) selectFlag = 1;
5160   if(!selectFlag) Sweep(step);
5161   return FALSE;
5162 }
5163
5164 void
5165 NextPiece (int step)
5166 {
5167     ChessSquare piece = boards[currentMove][toY][toX];
5168     do {
5169         pieceSweep -= step;
5170         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5171         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(pieceSweep) == '.');
5174     boards[currentMove][toY][toX] = pieceSweep;
5175     DrawPosition(FALSE, boards[currentMove]);
5176     boards[currentMove][toY][toX] = piece;
5177 }
5178 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5179 void
5180 AlphaRank (char *move, int n)
5181 {
5182 //    char *p = move, c; int x, y;
5183
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5186     }
5187
5188     if(move[1]=='*' &&
5189        move[2]>='0' && move[2]<='9' &&
5190        move[3]>='a' && move[3]<='x'    ) {
5191         move[1] = '@';
5192         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5193         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5194     } else
5195     if(move[0]>='0' && move[0]<='9' &&
5196        move[1]>='a' && move[1]<='x' &&
5197        move[2]>='0' && move[2]<='9' &&
5198        move[3]>='a' && move[3]<='x'    ) {
5199         /* input move, Shogi -> normal */
5200         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5201         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5202         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5203         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5204     } else
5205     if(move[1]=='@' &&
5206        move[3]>='0' && move[3]<='9' &&
5207        move[2]>='a' && move[2]<='x'    ) {
5208         move[1] = '*';
5209         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5211     } else
5212     if(
5213        move[0]>='a' && move[0]<='x' &&
5214        move[3]>='0' && move[3]<='9' &&
5215        move[2]>='a' && move[2]<='x'    ) {
5216          /* output move, normal -> Shogi */
5217         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5218         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5219         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5220         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5221         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5222     }
5223     if (appData.debugMode) {
5224         fprintf(debugFP, "   out = '%s'\n", move);
5225     }
5226 }
5227
5228 char yy_textstr[8000];
5229
5230 /* Parser for moves from gnuchess, ICS, or user typein box */
5231 Boolean
5232 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5233 {
5234     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5235
5236     switch (*moveType) {
5237       case WhitePromotion:
5238       case BlackPromotion:
5239       case WhiteNonPromotion:
5240       case BlackNonPromotion:
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244       case WhiteKingSideCastle:
5245       case WhiteQueenSideCastle:
5246       case BlackKingSideCastle:
5247       case BlackQueenSideCastle:
5248       case WhiteKingSideCastleWild:
5249       case WhiteQueenSideCastleWild:
5250       case BlackKingSideCastleWild:
5251       case BlackQueenSideCastleWild:
5252       /* Code added by Tord: */
5253       case WhiteHSideCastleFR:
5254       case WhiteASideCastleFR:
5255       case BlackHSideCastleFR:
5256       case BlackASideCastleFR:
5257       /* End of code added by Tord */
5258       case IllegalMove:         /* bug or odd chess variant */
5259         *fromX = currentMoveString[0] - AAA;
5260         *fromY = currentMoveString[1] - ONE;
5261         *toX = currentMoveString[2] - AAA;
5262         *toY = currentMoveString[3] - ONE;
5263         *promoChar = currentMoveString[4];
5264         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5265             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5266     if (appData.debugMode) {
5267         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5268     }
5269             *fromX = *fromY = *toX = *toY = 0;
5270             return FALSE;
5271         }
5272         if (appData.testLegality) {
5273           return (*moveType != IllegalMove);
5274         } else {
5275           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5276                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5277         }
5278
5279       case WhiteDrop:
5280       case BlackDrop:
5281         *fromX = *moveType == WhiteDrop ?
5282           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5283           (int) CharToPiece(ToLower(currentMoveString[0]));
5284         *fromY = DROP_RANK;
5285         *toX = currentMoveString[2] - AAA;
5286         *toY = currentMoveString[3] - ONE;
5287         *promoChar = NULLCHAR;
5288         return TRUE;
5289
5290       case AmbiguousMove:
5291       case ImpossibleMove:
5292       case EndOfFile:
5293       case ElapsedTime:
5294       case Comment:
5295       case PGNTag:
5296       case NAG:
5297       case WhiteWins:
5298       case BlackWins:
5299       case GameIsDrawn:
5300       default:
5301     if (appData.debugMode) {
5302         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5303     }
5304         /* bug? */
5305         *fromX = *fromY = *toX = *toY = 0;
5306         *promoChar = NULLCHAR;
5307         return FALSE;
5308     }
5309 }
5310
5311 Boolean pushed = FALSE;
5312 char *lastParseAttempt;
5313
5314 void
5315 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5316 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5317   int fromX, fromY, toX, toY; char promoChar;
5318   ChessMove moveType;
5319   Boolean valid;
5320   int nr = 0;
5321
5322   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5323     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5324     pushed = TRUE;
5325   }
5326   endPV = forwardMostMove;
5327   do {
5328     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5329     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5330     lastParseAttempt = pv;
5331     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5332 if(appData.debugMode){
5333 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);
5334 }
5335     if(!valid && nr == 0 &&
5336        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5337         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5338         // Hande case where played move is different from leading PV move
5339         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5340         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5341         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5342         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5343           endPV += 2; // if position different, keep this
5344           moveList[endPV-1][0] = fromX + AAA;
5345           moveList[endPV-1][1] = fromY + ONE;
5346           moveList[endPV-1][2] = toX + AAA;
5347           moveList[endPV-1][3] = toY + ONE;
5348           parseList[endPV-1][0] = NULLCHAR;
5349           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5350         }
5351       }
5352     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5353     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5354     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5355     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5356         valid++; // allow comments in PV
5357         continue;
5358     }
5359     nr++;
5360     if(endPV+1 > framePtr) break; // no space, truncate
5361     if(!valid) break;
5362     endPV++;
5363     CopyBoard(boards[endPV], boards[endPV-1]);
5364     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5365     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5366     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5367     CoordsToAlgebraic(boards[endPV - 1],
5368                              PosFlags(endPV - 1),
5369                              fromY, fromX, toY, toX, promoChar,
5370                              parseList[endPV - 1]);
5371   } while(valid);
5372   if(atEnd == 2) return; // used hidden, for PV conversion
5373   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5374   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5375   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5376                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5377   DrawPosition(TRUE, boards[currentMove]);
5378 }
5379
5380 int
5381 MultiPV (ChessProgramState *cps)
5382 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5383         int i;
5384         for(i=0; i<cps->nrOptions; i++)
5385             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5386                 return i;
5387         return -1;
5388 }
5389
5390 Boolean
5391 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5392 {
5393         int startPV, multi, lineStart, origIndex = index;
5394         char *p, buf2[MSG_SIZ];
5395
5396         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5397         lastX = x; lastY = y;
5398         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5399         lineStart = startPV = index;
5400         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5401         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5402         index = startPV;
5403         do{ while(buf[index] && buf[index] != '\n') index++;
5404         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5405         buf[index] = 0;
5406         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5407                 int n = first.option[multi].value;
5408                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5409                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5410                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5411                 first.option[multi].value = n;
5412                 *start = *end = 0;
5413                 return FALSE;
5414         }
5415         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5416         *start = startPV; *end = index-1;
5417         return TRUE;
5418 }
5419
5420 char *
5421 PvToSAN (char *pv)
5422 {
5423         static char buf[10*MSG_SIZ];
5424         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5425         *buf = NULLCHAR;
5426         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5427         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5428         for(i = forwardMostMove; i<endPV; i++){
5429             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5430             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5431             k += strlen(buf+k);
5432         }
5433         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5434         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5435         endPV = savedEnd;
5436         return buf;
5437 }
5438
5439 Boolean
5440 LoadPV (int x, int y)
5441 { // called on right mouse click to load PV
5442   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5443   lastX = x; lastY = y;
5444   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5445   return TRUE;
5446 }
5447
5448 void
5449 UnLoadPV ()
5450 {
5451   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5452   if(endPV < 0) return;
5453   endPV = -1;
5454   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5455         Boolean saveAnimate = appData.animate;
5456         if(pushed) {
5457             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5458                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5459             } else storedGames--; // abandon shelved tail of original game
5460         }
5461         pushed = FALSE;
5462         forwardMostMove = currentMove;
5463         currentMove = oldFMM;
5464         appData.animate = FALSE;
5465         ToNrEvent(forwardMostMove);
5466         appData.animate = saveAnimate;
5467   }
5468   currentMove = forwardMostMove;
5469   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5470   ClearPremoveHighlights();
5471   DrawPosition(TRUE, boards[currentMove]);
5472 }
5473
5474 void
5475 MovePV (int x, int y, int h)
5476 { // step through PV based on mouse coordinates (called on mouse move)
5477   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5478
5479   // we must somehow check if right button is still down (might be released off board!)
5480   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5481   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5482   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5483   if(!step) return;
5484   lastX = x; lastY = y;
5485
5486   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5487   if(endPV < 0) return;
5488   if(y < margin) step = 1; else
5489   if(y > h - margin) step = -1;
5490   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5491   currentMove += step;
5492   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5493   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5494                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5495   DrawPosition(FALSE, boards[currentMove]);
5496 }
5497
5498
5499 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5500 // All positions will have equal probability, but the current method will not provide a unique
5501 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5502 #define DARK 1
5503 #define LITE 2
5504 #define ANY 3
5505
5506 int squaresLeft[4];
5507 int piecesLeft[(int)BlackPawn];
5508 int seed, nrOfShuffles;
5509
5510 void
5511 GetPositionNumber ()
5512 {       // sets global variable seed
5513         int i;
5514
5515         seed = appData.defaultFrcPosition;
5516         if(seed < 0) { // randomize based on time for negative FRC position numbers
5517                 for(i=0; i<50; i++) seed += random();
5518                 seed = random() ^ random() >> 8 ^ random() << 8;
5519                 if(seed<0) seed = -seed;
5520         }
5521 }
5522
5523 int
5524 put (Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void
5543 AddOnePiece (Board board, int pieceType, int rank, int shade)
5544 // calculate where the next piece goes, (any empty square), and put it there
5545 {
5546         int i;
5547
5548         i = seed % squaresLeft[shade];
5549         nrOfShuffles *= squaresLeft[shade];
5550         seed /= squaresLeft[shade];
5551         put(board, pieceType, rank, i, shade);
5552 }
5553
5554 void
5555 AddTwoPieces (Board board, int pieceType, int rank)
5556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5557 {
5558         int i, n=squaresLeft[ANY], j=n-1, k;
5559
5560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5561         i = seed % k;  // pick one
5562         nrOfShuffles *= k;
5563         seed /= k;
5564         while(i >= j) i -= j--;
5565         j = n - 1 - j; i += j;
5566         put(board, pieceType, rank, j, ANY);
5567         put(board, pieceType, rank, i, ANY);
5568 }
5569
5570 void
5571 SetUpShuffle (Board board, int number)
5572 {
5573         int i, p, first=1;
5574
5575         GetPositionNumber(); nrOfShuffles = 1;
5576
5577         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5578         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5579         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5580
5581         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5582
5583         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5584             p = (int) board[0][i];
5585             if(p < (int) BlackPawn) piecesLeft[p] ++;
5586             board[0][i] = EmptySquare;
5587         }
5588
5589         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5590             // shuffles restricted to allow normal castling put KRR first
5591             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5592                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5593             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5594                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5596                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5598                 put(board, WhiteRook, 0, 0, ANY);
5599             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5600         }
5601
5602         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5603             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5604             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5605                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5606                 while(piecesLeft[p] >= 2) {
5607                     AddOnePiece(board, p, 0, LITE);
5608                     AddOnePiece(board, p, 0, DARK);
5609                 }
5610                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5611             }
5612
5613         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5614             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5615             // but we leave King and Rooks for last, to possibly obey FRC restriction
5616             if(p == (int)WhiteRook) continue;
5617             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5618             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5619         }
5620
5621         // now everything is placed, except perhaps King (Unicorn) and Rooks
5622
5623         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5624             // Last King gets castling rights
5625             while(piecesLeft[(int)WhiteUnicorn]) {
5626                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5627                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5628             }
5629
5630             while(piecesLeft[(int)WhiteKing]) {
5631                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5633             }
5634
5635
5636         } else {
5637             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5638             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5639         }
5640
5641         // Only Rooks can be left; simply place them all
5642         while(piecesLeft[(int)WhiteRook]) {
5643                 i = put(board, WhiteRook, 0, 0, ANY);
5644                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5645                         if(first) {
5646                                 first=0;
5647                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5648                         }
5649                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5650                 }
5651         }
5652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5653             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5654         }
5655
5656         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5657 }
5658
5659 int
5660 SetCharTable (char *table, const char * map)
5661 /* [HGM] moved here from winboard.c because of its general usefulness */
5662 /*       Basically a safe strcpy that uses the last character as King */
5663 {
5664     int result = FALSE; int NrPieces;
5665
5666     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5667                     && NrPieces >= 12 && !(NrPieces&1)) {
5668         int i; /* [HGM] Accept even length from 12 to 34 */
5669
5670         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5671         for( i=0; i<NrPieces/2-1; i++ ) {
5672             table[i] = map[i];
5673             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5674         }
5675         table[(int) WhiteKing]  = map[NrPieces/2-1];
5676         table[(int) BlackKing]  = map[NrPieces-1];
5677
5678         result = TRUE;
5679     }
5680
5681     return result;
5682 }
5683
5684 void
5685 Prelude (Board board)
5686 {       // [HGM] superchess: random selection of exo-pieces
5687         int i, j, k; ChessSquare p;
5688         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5689
5690         GetPositionNumber(); // use FRC position number
5691
5692         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5693             SetCharTable(pieceToChar, appData.pieceToCharTable);
5694             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5695                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5696         }
5697
5698         j = seed%4;                 seed /= 4;
5699         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5700         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5701         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5702         j = seed%3 + (seed%3 >= j); seed /= 3;
5703         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5704         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5705         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5706         j = seed%3;                 seed /= 3;
5707         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5710         j = seed%2 + (seed%2 >= j); seed /= 2;
5711         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5712         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5713         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5714         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5715         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5716         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5717         put(board, exoPieces[0],    0, 0, ANY);
5718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5719 }
5720
5721 void
5722 InitPosition (int redraw)
5723 {
5724     ChessSquare (* pieces)[BOARD_FILES];
5725     int i, j, pawnRow, overrule,
5726     oldx = gameInfo.boardWidth,
5727     oldy = gameInfo.boardHeight,
5728     oldh = gameInfo.holdingsWidth;
5729     static int oldv;
5730
5731     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5732
5733     /* [AS] Initialize pv info list [HGM] and game status */
5734     {
5735         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5736             pvInfoList[i].depth = 0;
5737             boards[i][EP_STATUS] = EP_NONE;
5738             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5739         }
5740
5741         initialRulePlies = 0; /* 50-move counter start */
5742
5743         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5744         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5745     }
5746
5747
5748     /* [HGM] logic here is completely changed. In stead of full positions */
5749     /* the initialized data only consist of the two backranks. The switch */
5750     /* selects which one we will use, which is than copied to the Board   */
5751     /* initialPosition, which for the rest is initialized by Pawns and    */
5752     /* empty squares. This initial position is then copied to boards[0],  */
5753     /* possibly after shuffling, so that it remains available.            */
5754
5755     gameInfo.holdingsWidth = 0; /* default board sizes */
5756     gameInfo.boardWidth    = 8;
5757     gameInfo.boardHeight   = 8;
5758     gameInfo.holdingsSize  = 0;
5759     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5760     for(i=0; i<BOARD_FILES-2; i++)
5761       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5762     initialPosition[EP_STATUS] = EP_NONE;
5763     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5764     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5765          SetCharTable(pieceNickName, appData.pieceNickNames);
5766     else SetCharTable(pieceNickName, "............");
5767     pieces = FIDEArray;
5768
5769     switch (gameInfo.variant) {
5770     case VariantFischeRandom:
5771       shuffleOpenings = TRUE;
5772     default:
5773       break;
5774     case VariantShatranj:
5775       pieces = ShatranjArray;
5776       nrCastlingRights = 0;
5777       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5778       break;
5779     case VariantMakruk:
5780       pieces = makrukArray;
5781       nrCastlingRights = 0;
5782       startedFromSetupPosition = TRUE;
5783       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5784       break;
5785     case VariantTwoKings:
5786       pieces = twoKingsArray;
5787       break;
5788     case VariantGrand:
5789       pieces = GrandArray;
5790       nrCastlingRights = 0;
5791       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5792       gameInfo.boardWidth = 10;
5793       gameInfo.boardHeight = 10;
5794       gameInfo.holdingsSize = 7;
5795       break;
5796     case VariantCapaRandom:
5797       shuffleOpenings = TRUE;
5798     case VariantCapablanca:
5799       pieces = CapablancaArray;
5800       gameInfo.boardWidth = 10;
5801       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5802       break;
5803     case VariantGothic:
5804       pieces = GothicArray;
5805       gameInfo.boardWidth = 10;
5806       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5807       break;
5808     case VariantSChess:
5809       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5810       gameInfo.holdingsSize = 7;
5811       break;
5812     case VariantJanus:
5813       pieces = JanusArray;
5814       gameInfo.boardWidth = 10;
5815       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5816       nrCastlingRights = 6;
5817         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5818         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5819         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5820         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5821         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5822         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5823       break;
5824     case VariantFalcon:
5825       pieces = FalconArray;
5826       gameInfo.boardWidth = 10;
5827       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5828       break;
5829     case VariantXiangqi:
5830       pieces = XiangqiArray;
5831       gameInfo.boardWidth  = 9;
5832       gameInfo.boardHeight = 10;
5833       nrCastlingRights = 0;
5834       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5835       break;
5836     case VariantShogi:
5837       pieces = ShogiArray;
5838       gameInfo.boardWidth  = 9;
5839       gameInfo.boardHeight = 9;
5840       gameInfo.holdingsSize = 7;
5841       nrCastlingRights = 0;
5842       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5843       break;
5844     case VariantCourier:
5845       pieces = CourierArray;
5846       gameInfo.boardWidth  = 12;
5847       nrCastlingRights = 0;
5848       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5849       break;
5850     case VariantKnightmate:
5851       pieces = KnightmateArray;
5852       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5853       break;
5854     case VariantSpartan:
5855       pieces = SpartanArray;
5856       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5857       break;
5858     case VariantFairy:
5859       pieces = fairyArray;
5860       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5861       break;
5862     case VariantGreat:
5863       pieces = GreatArray;
5864       gameInfo.boardWidth = 10;
5865       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5866       gameInfo.holdingsSize = 8;
5867       break;
5868     case VariantSuper:
5869       pieces = FIDEArray;
5870       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5871       gameInfo.holdingsSize = 8;
5872       startedFromSetupPosition = TRUE;
5873       break;
5874     case VariantCrazyhouse:
5875     case VariantBughouse:
5876       pieces = FIDEArray;
5877       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5878       gameInfo.holdingsSize = 5;
5879       break;
5880     case VariantWildCastle:
5881       pieces = FIDEArray;
5882       /* !!?shuffle with kings guaranteed to be on d or e file */
5883       shuffleOpenings = 1;
5884       break;
5885     case VariantNoCastle:
5886       pieces = FIDEArray;
5887       nrCastlingRights = 0;
5888       /* !!?unconstrained back-rank shuffle */
5889       shuffleOpenings = 1;
5890       break;
5891     }
5892
5893     overrule = 0;
5894     if(appData.NrFiles >= 0) {
5895         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5896         gameInfo.boardWidth = appData.NrFiles;
5897     }
5898     if(appData.NrRanks >= 0) {
5899         gameInfo.boardHeight = appData.NrRanks;
5900     }
5901     if(appData.holdingsSize >= 0) {
5902         i = appData.holdingsSize;
5903         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5904         gameInfo.holdingsSize = i;
5905     }
5906     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5907     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5908         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5909
5910     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5911     if(pawnRow < 1) pawnRow = 1;
5912     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5913
5914     /* User pieceToChar list overrules defaults */
5915     if(appData.pieceToCharTable != NULL)
5916         SetCharTable(pieceToChar, appData.pieceToCharTable);
5917
5918     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5919
5920         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5921             s = (ChessSquare) 0; /* account holding counts in guard band */
5922         for( i=0; i<BOARD_HEIGHT; i++ )
5923             initialPosition[i][j] = s;
5924
5925         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5926         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5927         initialPosition[pawnRow][j] = WhitePawn;
5928         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5929         if(gameInfo.variant == VariantXiangqi) {
5930             if(j&1) {
5931                 initialPosition[pawnRow][j] =
5932                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5933                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5934                    initialPosition[2][j] = WhiteCannon;
5935                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5936                 }
5937             }
5938         }
5939         if(gameInfo.variant == VariantGrand) {
5940             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5941                initialPosition[0][j] = WhiteRook;
5942                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5943             }
5944         }
5945         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5946     }
5947     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5948
5949             j=BOARD_LEFT+1;
5950             initialPosition[1][j] = WhiteBishop;
5951             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5952             j=BOARD_RGHT-2;
5953             initialPosition[1][j] = WhiteRook;
5954             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5955     }
5956
5957     if( nrCastlingRights == -1) {
5958         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5959         /*       This sets default castling rights from none to normal corners   */
5960         /* Variants with other castling rights must set them themselves above    */
5961         nrCastlingRights = 6;
5962
5963         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5964         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5965         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5966         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5967         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5968         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5969      }
5970
5971      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5972      if(gameInfo.variant == VariantGreat) { // promotion commoners
5973         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5974         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5975         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5976         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5977      }
5978      if( gameInfo.variant == VariantSChess ) {
5979       initialPosition[1][0] = BlackMarshall;
5980       initialPosition[2][0] = BlackAngel;
5981       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5982       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5983       initialPosition[1][1] = initialPosition[2][1] = 
5984       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5985      }
5986   if (appData.debugMode) {
5987     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5988   }
5989     if(shuffleOpenings) {
5990         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5991         startedFromSetupPosition = TRUE;
5992     }
5993     if(startedFromPositionFile) {
5994       /* [HGM] loadPos: use PositionFile for every new game */
5995       CopyBoard(initialPosition, filePosition);
5996       for(i=0; i<nrCastlingRights; i++)
5997           initialRights[i] = filePosition[CASTLING][i];
5998       startedFromSetupPosition = TRUE;
5999     }
6000
6001     CopyBoard(boards[0], initialPosition);
6002
6003     if(oldx != gameInfo.boardWidth ||
6004        oldy != gameInfo.boardHeight ||
6005        oldv != gameInfo.variant ||
6006        oldh != gameInfo.holdingsWidth
6007                                          )
6008             InitDrawingSizes(-2 ,0);
6009
6010     oldv = gameInfo.variant;
6011     if (redraw)
6012       DrawPosition(TRUE, boards[currentMove]);
6013 }
6014
6015 void
6016 SendBoard (ChessProgramState *cps, int moveNum)
6017 {
6018     char message[MSG_SIZ];
6019
6020     if (cps->useSetboard) {
6021       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6022       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6023       SendToProgram(message, cps);
6024       free(fen);
6025
6026     } else {
6027       ChessSquare *bp;
6028       int i, j, left=0, right=BOARD_WIDTH;
6029       /* Kludge to set black to move, avoiding the troublesome and now
6030        * deprecated "black" command.
6031        */
6032       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6033         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6034
6035       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6036
6037       SendToProgram("edit\n", cps);
6038       SendToProgram("#\n", cps);
6039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6040         bp = &boards[moveNum][i][left];
6041         for (j = left; j < right; j++, bp++) {
6042           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6043           if ((int) *bp < (int) BlackPawn) {
6044             if(j == BOARD_RGHT+1)
6045                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6046             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6047             if(message[0] == '+' || message[0] == '~') {
6048               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6049                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6050                         AAA + j, ONE + i);
6051             }
6052             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6053                 message[1] = BOARD_RGHT   - 1 - j + '1';
6054                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6055             }
6056             SendToProgram(message, cps);
6057           }
6058         }
6059       }
6060
6061       SendToProgram("c\n", cps);
6062       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6063         bp = &boards[moveNum][i][left];
6064         for (j = left; j < right; j++, bp++) {
6065           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6066           if (((int) *bp != (int) EmptySquare)
6067               && ((int) *bp >= (int) BlackPawn)) {
6068             if(j == BOARD_LEFT-2)
6069                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6070             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6071                     AAA + j, ONE + i);
6072             if(message[0] == '+' || message[0] == '~') {
6073               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6074                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6075                         AAA + j, ONE + i);
6076             }
6077             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6078                 message[1] = BOARD_RGHT   - 1 - j + '1';
6079                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6080             }
6081             SendToProgram(message, cps);
6082           }
6083         }
6084       }
6085
6086       SendToProgram(".\n", cps);
6087     }
6088     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6089 }
6090
6091 ChessSquare
6092 DefaultPromoChoice (int white)
6093 {
6094     ChessSquare result;
6095     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6096         result = WhiteFerz; // no choice
6097     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6098         result= WhiteKing; // in Suicide Q is the last thing we want
6099     else if(gameInfo.variant == VariantSpartan)
6100         result = white ? WhiteQueen : WhiteAngel;
6101     else result = WhiteQueen;
6102     if(!white) result = WHITE_TO_BLACK result;
6103     return result;
6104 }
6105
6106 static int autoQueen; // [HGM] oneclick
6107
6108 int
6109 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6110 {
6111     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6112     /* [HGM] add Shogi promotions */
6113     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6114     ChessSquare piece;
6115     ChessMove moveType;
6116     Boolean premove;
6117
6118     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6119     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6120
6121     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6122       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6123         return FALSE;
6124
6125     piece = boards[currentMove][fromY][fromX];
6126     if(gameInfo.variant == VariantShogi) {
6127         promotionZoneSize = BOARD_HEIGHT/3;
6128         highestPromotingPiece = (int)WhiteFerz;
6129     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6130         promotionZoneSize = 3;
6131     }
6132
6133     // Treat Lance as Pawn when it is not representing Amazon
6134     if(gameInfo.variant != VariantSuper) {
6135         if(piece == WhiteLance) piece = WhitePawn; else
6136         if(piece == BlackLance) piece = BlackPawn;
6137     }
6138
6139     // next weed out all moves that do not touch the promotion zone at all
6140     if((int)piece >= BlackPawn) {
6141         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6142              return FALSE;
6143         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6144     } else {
6145         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6146            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6147     }
6148
6149     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6150
6151     // weed out mandatory Shogi promotions
6152     if(gameInfo.variant == VariantShogi) {
6153         if(piece >= BlackPawn) {
6154             if(toY == 0 && piece == BlackPawn ||
6155                toY == 0 && piece == BlackQueen ||
6156                toY <= 1 && piece == BlackKnight) {
6157                 *promoChoice = '+';
6158                 return FALSE;
6159             }
6160         } else {
6161             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6162                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6163                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6164                 *promoChoice = '+';
6165                 return FALSE;
6166             }
6167         }
6168     }
6169
6170     // weed out obviously illegal Pawn moves
6171     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6172         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6173         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6174         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6175         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6176         // note we are not allowed to test for valid (non-)capture, due to premove
6177     }
6178
6179     // we either have a choice what to promote to, or (in Shogi) whether to promote
6180     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6181         *promoChoice = PieceToChar(BlackFerz);  // no choice
6182         return FALSE;
6183     }
6184     // no sense asking what we must promote to if it is going to explode...
6185     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6186         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6187         return FALSE;
6188     }
6189     // give caller the default choice even if we will not make it
6190     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6191     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6192     if(        sweepSelect && gameInfo.variant != VariantGreat
6193                            && gameInfo.variant != VariantGrand
6194                            && gameInfo.variant != VariantSuper) return FALSE;
6195     if(autoQueen) return FALSE; // predetermined
6196
6197     // suppress promotion popup on illegal moves that are not premoves
6198     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6199               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6200     if(appData.testLegality && !premove) {
6201         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6202                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6203         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6204             return FALSE;
6205     }
6206
6207     return TRUE;
6208 }
6209
6210 int
6211 InPalace (int row, int column)
6212 {   /* [HGM] for Xiangqi */
6213     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6214          column < (BOARD_WIDTH + 4)/2 &&
6215          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6216     return FALSE;
6217 }
6218
6219 int
6220 PieceForSquare (int x, int y)
6221 {
6222   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6223      return -1;
6224   else
6225      return boards[currentMove][y][x];
6226 }
6227
6228 int
6229 OKToStartUserMove (int x, int y)
6230 {
6231     ChessSquare from_piece;
6232     int white_piece;
6233
6234     if (matchMode) return FALSE;
6235     if (gameMode == EditPosition) return TRUE;
6236
6237     if (x >= 0 && y >= 0)
6238       from_piece = boards[currentMove][y][x];
6239     else
6240       from_piece = EmptySquare;
6241
6242     if (from_piece == EmptySquare) return FALSE;
6243
6244     white_piece = (int)from_piece >= (int)WhitePawn &&
6245       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6246
6247     switch (gameMode) {
6248       case AnalyzeFile:
6249       case TwoMachinesPlay:
6250       case EndOfGame:
6251         return FALSE;
6252
6253       case IcsObserving:
6254       case IcsIdle:
6255         return FALSE;
6256
6257       case MachinePlaysWhite:
6258       case IcsPlayingBlack:
6259         if (appData.zippyPlay) return FALSE;
6260         if (white_piece) {
6261             DisplayMoveError(_("You are playing Black"));
6262             return FALSE;
6263         }
6264         break;
6265
6266       case MachinePlaysBlack:
6267       case IcsPlayingWhite:
6268         if (appData.zippyPlay) return FALSE;
6269         if (!white_piece) {
6270             DisplayMoveError(_("You are playing White"));
6271             return FALSE;
6272         }
6273         break;
6274
6275       case PlayFromGameFile:
6276             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6277       case EditGame:
6278         if (!white_piece && WhiteOnMove(currentMove)) {
6279             DisplayMoveError(_("It is White's turn"));
6280             return FALSE;
6281         }
6282         if (white_piece && !WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is Black's turn"));
6284             return FALSE;
6285         }
6286         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6287             /* Editing correspondence game history */
6288             /* Could disallow this or prompt for confirmation */
6289             cmailOldMove = -1;
6290         }
6291         break;
6292
6293       case BeginningOfGame:
6294         if (appData.icsActive) return FALSE;
6295         if (!appData.noChessProgram) {
6296             if (!white_piece) {
6297                 DisplayMoveError(_("You are playing White"));
6298                 return FALSE;
6299             }
6300         }
6301         break;
6302
6303       case Training:
6304         if (!white_piece && WhiteOnMove(currentMove)) {
6305             DisplayMoveError(_("It is White's turn"));
6306             return FALSE;
6307         }
6308         if (white_piece && !WhiteOnMove(currentMove)) {
6309             DisplayMoveError(_("It is Black's turn"));
6310             return FALSE;
6311         }
6312         break;
6313
6314       default:
6315       case IcsExamining:
6316         break;
6317     }
6318     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6319         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6320         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6321         && gameMode != AnalyzeFile && gameMode != Training) {
6322         DisplayMoveError(_("Displayed position is not current"));
6323         return FALSE;
6324     }
6325     return TRUE;
6326 }
6327
6328 Boolean
6329 OnlyMove (int *x, int *y, Boolean captures) 
6330 {
6331     DisambiguateClosure cl;
6332     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6333     switch(gameMode) {
6334       case MachinePlaysBlack:
6335       case IcsPlayingWhite:
6336       case BeginningOfGame:
6337         if(!WhiteOnMove(currentMove)) return FALSE;
6338         break;
6339       case MachinePlaysWhite:
6340       case IcsPlayingBlack:
6341         if(WhiteOnMove(currentMove)) return FALSE;
6342         break;
6343       case EditGame:
6344         break;
6345       default:
6346         return FALSE;
6347     }
6348     cl.pieceIn = EmptySquare;
6349     cl.rfIn = *y;
6350     cl.ffIn = *x;
6351     cl.rtIn = -1;
6352     cl.ftIn = -1;
6353     cl.promoCharIn = NULLCHAR;
6354     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6355     if( cl.kind == NormalMove ||
6356         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6357         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6358         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6359       fromX = cl.ff;
6360       fromY = cl.rf;
6361       *x = cl.ft;
6362       *y = cl.rt;
6363       return TRUE;
6364     }
6365     if(cl.kind != ImpossibleMove) return FALSE;
6366     cl.pieceIn = EmptySquare;
6367     cl.rfIn = -1;
6368     cl.ffIn = -1;
6369     cl.rtIn = *y;
6370     cl.ftIn = *x;
6371     cl.promoCharIn = NULLCHAR;
6372     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6373     if( cl.kind == NormalMove ||
6374         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6375         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6376         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6377       fromX = cl.ff;
6378       fromY = cl.rf;
6379       *x = cl.ft;
6380       *y = cl.rt;
6381       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6382       return TRUE;
6383     }
6384     return FALSE;
6385 }
6386
6387 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6388 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6389 int lastLoadGameUseList = FALSE;
6390 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6391 ChessMove lastLoadGameStart = EndOfFile;
6392
6393 void
6394 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6395 {
6396     ChessMove moveType;
6397     ChessSquare pdown, pup;
6398
6399     /* Check if the user is playing in turn.  This is complicated because we
6400        let the user "pick up" a piece before it is his turn.  So the piece he
6401        tried to pick up may have been captured by the time he puts it down!
6402        Therefore we use the color the user is supposed to be playing in this
6403        test, not the color of the piece that is currently on the starting
6404        square---except in EditGame mode, where the user is playing both
6405        sides; fortunately there the capture race can't happen.  (It can
6406        now happen in IcsExamining mode, but that's just too bad.  The user
6407        will get a somewhat confusing message in that case.)
6408        */
6409
6410     switch (gameMode) {
6411       case AnalyzeFile:
6412       case TwoMachinesPlay:
6413       case EndOfGame:
6414       case IcsObserving:
6415       case IcsIdle:
6416         /* We switched into a game mode where moves are not accepted,
6417            perhaps while the mouse button was down. */
6418         return;
6419
6420       case MachinePlaysWhite:
6421         /* User is moving for Black */
6422         if (WhiteOnMove(currentMove)) {
6423             DisplayMoveError(_("It is White's turn"));
6424             return;
6425         }
6426         break;
6427
6428       case MachinePlaysBlack:
6429         /* User is moving for White */
6430         if (!WhiteOnMove(currentMove)) {
6431             DisplayMoveError(_("It is Black's turn"));
6432             return;
6433         }
6434         break;
6435
6436       case PlayFromGameFile:
6437             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6438       case EditGame:
6439       case IcsExamining:
6440       case BeginningOfGame:
6441       case AnalyzeMode:
6442       case Training:
6443         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6444         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6445             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6446             /* User is moving for Black */
6447             if (WhiteOnMove(currentMove)) {
6448                 DisplayMoveError(_("It is White's turn"));
6449                 return;
6450             }
6451         } else {
6452             /* User is moving for White */
6453             if (!WhiteOnMove(currentMove)) {
6454                 DisplayMoveError(_("It is Black's turn"));
6455                 return;
6456             }
6457         }
6458         break;
6459
6460       case IcsPlayingBlack:
6461         /* User is moving for Black */
6462         if (WhiteOnMove(currentMove)) {
6463             if (!appData.premove) {
6464                 DisplayMoveError(_("It is White's turn"));
6465             } else if (toX >= 0 && toY >= 0) {
6466                 premoveToX = toX;
6467                 premoveToY = toY;
6468                 premoveFromX = fromX;
6469                 premoveFromY = fromY;
6470                 premovePromoChar = promoChar;
6471                 gotPremove = 1;
6472                 if (appData.debugMode)
6473                     fprintf(debugFP, "Got premove: fromX %d,"
6474                             "fromY %d, toX %d, toY %d\n",
6475                             fromX, fromY, toX, toY);
6476             }
6477             return;
6478         }
6479         break;
6480
6481       case IcsPlayingWhite:
6482         /* User is moving for White */
6483         if (!WhiteOnMove(currentMove)) {
6484             if (!appData.premove) {
6485                 DisplayMoveError(_("It is Black's turn"));
6486             } else if (toX >= 0 && toY >= 0) {
6487                 premoveToX = toX;
6488                 premoveToY = toY;
6489                 premoveFromX = fromX;
6490                 premoveFromY = fromY;
6491                 premovePromoChar = promoChar;
6492                 gotPremove = 1;
6493                 if (appData.debugMode)
6494                     fprintf(debugFP, "Got premove: fromX %d,"
6495                             "fromY %d, toX %d, toY %d\n",
6496                             fromX, fromY, toX, toY);
6497             }
6498             return;
6499         }
6500         break;
6501
6502       default:
6503         break;
6504
6505       case EditPosition:
6506         /* EditPosition, empty square, or different color piece;
6507            click-click move is possible */
6508         if (toX == -2 || toY == -2) {
6509             boards[0][fromY][fromX] = EmptySquare;
6510             DrawPosition(FALSE, boards[currentMove]);
6511             return;
6512         } else if (toX >= 0 && toY >= 0) {
6513             boards[0][toY][toX] = boards[0][fromY][fromX];
6514             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6515                 if(boards[0][fromY][0] != EmptySquare) {
6516                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6517                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6518                 }
6519             } else
6520             if(fromX == BOARD_RGHT+1) {
6521                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6522                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6523                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6524                 }
6525             } else
6526             boards[0][fromY][fromX] = EmptySquare;
6527             DrawPosition(FALSE, boards[currentMove]);
6528             return;
6529         }
6530         return;
6531     }
6532
6533     if(toX < 0 || toY < 0) return;
6534     pdown = boards[currentMove][fromY][fromX];
6535     pup = boards[currentMove][toY][toX];
6536
6537     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6538     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6539          if( pup != EmptySquare ) return;
6540          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6541            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6542                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6543            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6544            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6545            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6546            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6547          fromY = DROP_RANK;
6548     }
6549
6550     /* [HGM] always test for legality, to get promotion info */
6551     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6552                                          fromY, fromX, toY, toX, promoChar);
6553
6554     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6555
6556     /* [HGM] but possibly ignore an IllegalMove result */
6557     if (appData.testLegality) {
6558         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6559             DisplayMoveError(_("Illegal move"));
6560             return;
6561         }
6562     }
6563
6564     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6565 }
6566
6567 /* Common tail of UserMoveEvent and DropMenuEvent */
6568 int
6569 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6570 {
6571     char *bookHit = 0;
6572
6573     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6574         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6575         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6576         if(WhiteOnMove(currentMove)) {
6577             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6578         } else {
6579             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6580         }
6581     }
6582
6583     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6584        move type in caller when we know the move is a legal promotion */
6585     if(moveType == NormalMove && promoChar)
6586         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6587
6588     /* [HGM] <popupFix> The following if has been moved here from
6589        UserMoveEvent(). Because it seemed to belong here (why not allow
6590        piece drops in training games?), and because it can only be
6591        performed after it is known to what we promote. */
6592     if (gameMode == Training) {
6593       /* compare the move played on the board to the next move in the
6594        * game. If they match, display the move and the opponent's response.
6595        * If they don't match, display an error message.
6596        */
6597       int saveAnimate;
6598       Board testBoard;
6599       CopyBoard(testBoard, boards[currentMove]);
6600       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6601
6602       if (CompareBoards(testBoard, boards[currentMove+1])) {
6603         ForwardInner(currentMove+1);
6604
6605         /* Autoplay the opponent's response.
6606          * if appData.animate was TRUE when Training mode was entered,
6607          * the response will be animated.
6608          */
6609         saveAnimate = appData.animate;
6610         appData.animate = animateTraining;
6611         ForwardInner(currentMove+1);
6612         appData.animate = saveAnimate;
6613
6614         /* check for the end of the game */
6615         if (currentMove >= forwardMostMove) {
6616           gameMode = PlayFromGameFile;
6617           ModeHighlight();
6618           SetTrainingModeOff();
6619           DisplayInformation(_("End of game"));
6620         }
6621       } else {
6622         DisplayError(_("Incorrect move"), 0);
6623       }
6624       return 1;
6625     }
6626
6627   /* Ok, now we know that the move is good, so we can kill
6628      the previous line in Analysis Mode */
6629   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6630                                 && currentMove < forwardMostMove) {
6631     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6632     else forwardMostMove = currentMove;
6633   }
6634
6635   /* If we need the chess program but it's dead, restart it */
6636   ResurrectChessProgram();
6637
6638   /* A user move restarts a paused game*/
6639   if (pausing)
6640     PauseEvent();
6641
6642   thinkOutput[0] = NULLCHAR;
6643
6644   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6645
6646   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6647     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648     return 1;
6649   }
6650
6651   if (gameMode == BeginningOfGame) {
6652     if (appData.noChessProgram) {
6653       gameMode = EditGame;
6654       SetGameInfo();
6655     } else {
6656       char buf[MSG_SIZ];
6657       gameMode = MachinePlaysBlack;
6658       StartClocks();
6659       SetGameInfo();
6660       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6661       DisplayTitle(buf);
6662       if (first.sendName) {
6663         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6664         SendToProgram(buf, &first);
6665       }
6666       StartClocks();
6667     }
6668     ModeHighlight();
6669   }
6670
6671   /* Relay move to ICS or chess engine */
6672   if (appData.icsActive) {
6673     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6674         gameMode == IcsExamining) {
6675       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6676         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6677         SendToICS("draw ");
6678         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6679       }
6680       // also send plain move, in case ICS does not understand atomic claims
6681       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6682       ics_user_moved = 1;
6683     }
6684   } else {
6685     if (first.sendTime && (gameMode == BeginningOfGame ||
6686                            gameMode == MachinePlaysWhite ||
6687                            gameMode == MachinePlaysBlack)) {
6688       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6689     }
6690     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6691          // [HGM] book: if program might be playing, let it use book
6692         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6693         first.maybeThinking = TRUE;
6694     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6695         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6696         SendBoard(&first, currentMove+1);
6697     } else SendMoveToProgram(forwardMostMove-1, &first);
6698     if (currentMove == cmailOldMove + 1) {
6699       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6700     }
6701   }
6702
6703   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6704
6705   switch (gameMode) {
6706   case EditGame:
6707     if(appData.testLegality)
6708     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6709     case MT_NONE:
6710     case MT_CHECK:
6711       break;
6712     case MT_CHECKMATE:
6713     case MT_STAINMATE:
6714       if (WhiteOnMove(currentMove)) {
6715         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6716       } else {
6717         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6718       }
6719       break;
6720     case MT_STALEMATE:
6721       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6722       break;
6723     }
6724     break;
6725
6726   case MachinePlaysBlack:
6727   case MachinePlaysWhite:
6728     /* disable certain menu options while machine is thinking */
6729     SetMachineThinkingEnables();
6730     break;
6731
6732   default:
6733     break;
6734   }
6735
6736   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6737   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6738
6739   if(bookHit) { // [HGM] book: simulate book reply
6740         static char bookMove[MSG_SIZ]; // a bit generous?
6741
6742         programStats.nodes = programStats.depth = programStats.time =
6743         programStats.score = programStats.got_only_move = 0;
6744         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6745
6746         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6747         strcat(bookMove, bookHit);
6748         HandleMachineMove(bookMove, &first);
6749   }
6750   return 1;
6751 }
6752
6753 void
6754 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6755 {
6756     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6757     Markers *m = (Markers *) closure;
6758     if(rf == fromY && ff == fromX)
6759         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6760                          || kind == WhiteCapturesEnPassant
6761                          || kind == BlackCapturesEnPassant);
6762     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6763 }
6764
6765 void
6766 MarkTargetSquares (int clear)
6767 {
6768   int x, y;
6769   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6770      !appData.testLegality || gameMode == EditPosition) return;
6771   if(clear) {
6772     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6773   } else {
6774     int capt = 0;
6775     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6776     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6777       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6778       if(capt)
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6780     }
6781   }
6782   DrawPosition(TRUE, NULL);
6783 }
6784
6785 int
6786 Explode (Board board, int fromX, int fromY, int toX, int toY)
6787 {
6788     if(gameInfo.variant == VariantAtomic &&
6789        (board[toY][toX] != EmptySquare ||                     // capture?
6790         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6791                          board[fromY][fromX] == BlackPawn   )
6792       )) {
6793         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6794         return TRUE;
6795     }
6796     return FALSE;
6797 }
6798
6799 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6800
6801 int
6802 CanPromote (ChessSquare piece, int y)
6803 {
6804         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6805         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6806         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6807            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6808            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6809                                                   gameInfo.variant == VariantMakruk) return FALSE;
6810         return (piece == BlackPawn && y == 1 ||
6811                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6812                 piece == BlackLance && y == 1 ||
6813                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6814 }
6815
6816 void
6817 LeftClick (ClickType clickType, int xPix, int yPix)
6818 {
6819     int x, y;
6820     Boolean saveAnimate;
6821     static int second = 0, promotionChoice = 0, clearFlag = 0;
6822     char promoChoice = NULLCHAR;
6823     ChessSquare piece;
6824
6825     if(appData.seekGraph && appData.icsActive && loggedOn &&
6826         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6827         SeekGraphClick(clickType, xPix, yPix, 0);
6828         return;
6829     }
6830
6831     if (clickType == Press) ErrorPopDown();
6832
6833     x = EventToSquare(xPix, BOARD_WIDTH);
6834     y = EventToSquare(yPix, BOARD_HEIGHT);
6835     if (!flipView && y >= 0) {
6836         y = BOARD_HEIGHT - 1 - y;
6837     }
6838     if (flipView && x >= 0) {
6839         x = BOARD_WIDTH - 1 - x;
6840     }
6841
6842     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6843         defaultPromoChoice = promoSweep;
6844         promoSweep = EmptySquare;   // terminate sweep
6845         promoDefaultAltered = TRUE;
6846         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6847     }
6848
6849     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6850         if(clickType == Release) return; // ignore upclick of click-click destination
6851         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6852         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6853         if(gameInfo.holdingsWidth &&
6854                 (WhiteOnMove(currentMove)
6855                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6856                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6857             // click in right holdings, for determining promotion piece
6858             ChessSquare p = boards[currentMove][y][x];
6859             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6860             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6861             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6862                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6863                 fromX = fromY = -1;
6864                 return;
6865             }
6866         }
6867         DrawPosition(FALSE, boards[currentMove]);
6868         return;
6869     }
6870
6871     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6872     if(clickType == Press
6873             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6874               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6875               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6876         return;
6877
6878     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6879         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6880
6881     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6882         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6883                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6884         defaultPromoChoice = DefaultPromoChoice(side);
6885     }
6886
6887     autoQueen = appData.alwaysPromoteToQueen;
6888
6889     if (fromX == -1) {
6890       int originalY = y;
6891       gatingPiece = EmptySquare;
6892       if (clickType != Press) {
6893         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6894             DragPieceEnd(xPix, yPix); dragging = 0;
6895             DrawPosition(FALSE, NULL);
6896         }
6897         return;
6898       }
6899       fromX = x; fromY = y; toX = toY = -1;
6900       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6901          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6902          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6903             /* First square */
6904             if (OKToStartUserMove(fromX, fromY)) {
6905                 second = 0;
6906                 MarkTargetSquares(0);
6907                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6908                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6909                     promoSweep = defaultPromoChoice;
6910                     selectFlag = 0; lastX = xPix; lastY = yPix;
6911                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6912                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6913                 }
6914                 if (appData.highlightDragging) {
6915                     SetHighlights(fromX, fromY, -1, -1);
6916                 }
6917             } else fromX = fromY = -1;
6918             return;
6919         }
6920     }
6921
6922     /* fromX != -1 */
6923     if (clickType == Press && gameMode != EditPosition) {
6924         ChessSquare fromP;
6925         ChessSquare toP;
6926         int frc;
6927
6928         // ignore off-board to clicks
6929         if(y < 0 || x < 0) return;
6930
6931         /* Check if clicking again on the same color piece */
6932         fromP = boards[currentMove][fromY][fromX];
6933         toP = boards[currentMove][y][x];
6934         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6935         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6936              WhitePawn <= toP && toP <= WhiteKing &&
6937              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6938              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6939             (BlackPawn <= fromP && fromP <= BlackKing &&
6940              BlackPawn <= toP && toP <= BlackKing &&
6941              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6942              !(fromP == BlackKing && toP == BlackRook && frc))) {
6943             /* Clicked again on same color piece -- changed his mind */
6944             second = (x == fromX && y == fromY);
6945             promoDefaultAltered = FALSE;
6946             MarkTargetSquares(1);
6947            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6948             if (appData.highlightDragging) {
6949                 SetHighlights(x, y, -1, -1);
6950             } else {
6951                 ClearHighlights();
6952             }
6953             if (OKToStartUserMove(x, y)) {
6954                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6955                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6956                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6957                  gatingPiece = boards[currentMove][fromY][fromX];
6958                 else gatingPiece = EmptySquare;
6959                 fromX = x;
6960                 fromY = y; dragging = 1;
6961                 MarkTargetSquares(0);
6962                 DragPieceBegin(xPix, yPix, FALSE);
6963                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6964                     promoSweep = defaultPromoChoice;
6965                     selectFlag = 0; lastX = xPix; lastY = yPix;
6966                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6967                 }
6968             }
6969            }
6970            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6971            second = FALSE; 
6972         }
6973         // ignore clicks on holdings
6974         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6975     }
6976
6977     if (clickType == Release && x == fromX && y == fromY) {
6978         DragPieceEnd(xPix, yPix); dragging = 0;
6979         if(clearFlag) {
6980             // a deferred attempt to click-click move an empty square on top of a piece
6981             boards[currentMove][y][x] = EmptySquare;
6982             ClearHighlights();
6983             DrawPosition(FALSE, boards[currentMove]);
6984             fromX = fromY = -1; clearFlag = 0;
6985             return;
6986         }
6987         if (appData.animateDragging) {
6988             /* Undo animation damage if any */
6989             DrawPosition(FALSE, NULL);
6990         }
6991         if (second) {
6992             /* Second up/down in same square; just abort move */
6993             second = 0;
6994             fromX = fromY = -1;
6995             gatingPiece = EmptySquare;
6996             ClearHighlights();
6997             gotPremove = 0;
6998             ClearPremoveHighlights();
6999         } else {
7000             /* First upclick in same square; start click-click mode */
7001             SetHighlights(x, y, -1, -1);
7002         }
7003         return;
7004     }
7005
7006     clearFlag = 0;
7007
7008     /* we now have a different from- and (possibly off-board) to-square */
7009     /* Completed move */
7010     toX = x;
7011     toY = y;
7012     saveAnimate = appData.animate;
7013     MarkTargetSquares(1);
7014     if (clickType == Press) {
7015         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7016             // must be Edit Position mode with empty-square selected
7017             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7018             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7019             return;
7020         }
7021         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7022             ChessSquare piece = boards[currentMove][fromY][fromX];
7023             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7024             promoSweep = defaultPromoChoice;
7025             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7026             selectFlag = 0; lastX = xPix; lastY = yPix;
7027             Sweep(0); // Pawn that is going to promote: preview promotion piece
7028             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7029             DrawPosition(FALSE, boards[currentMove]);
7030             return;
7031         }
7032         /* Finish clickclick move */
7033         if (appData.animate || appData.highlightLastMove) {
7034             SetHighlights(fromX, fromY, toX, toY);
7035         } else {
7036             ClearHighlights();
7037         }
7038     } else {
7039         /* Finish drag move */
7040         if (appData.highlightLastMove) {
7041             SetHighlights(fromX, fromY, toX, toY);
7042         } else {
7043             ClearHighlights();
7044         }
7045         DragPieceEnd(xPix, yPix); dragging = 0;
7046         /* Don't animate move and drag both */
7047         appData.animate = FALSE;
7048     }
7049
7050     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7051     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7052         ChessSquare piece = boards[currentMove][fromY][fromX];
7053         if(gameMode == EditPosition && piece != EmptySquare &&
7054            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7055             int n;
7056
7057             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7058                 n = PieceToNumber(piece - (int)BlackPawn);
7059                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7060                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7062             } else
7063             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7064                 n = PieceToNumber(piece);
7065                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7066                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7067                 boards[currentMove][n][BOARD_WIDTH-2]++;
7068             }
7069             boards[currentMove][fromY][fromX] = EmptySquare;
7070         }
7071         ClearHighlights();
7072         fromX = fromY = -1;
7073         DrawPosition(TRUE, boards[currentMove]);
7074         return;
7075     }
7076
7077     // off-board moves should not be highlighted
7078     if(x < 0 || y < 0) ClearHighlights();
7079
7080     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7081
7082     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7083         SetHighlights(fromX, fromY, toX, toY);
7084         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7085             // [HGM] super: promotion to captured piece selected from holdings
7086             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7087             promotionChoice = TRUE;
7088             // kludge follows to temporarily execute move on display, without promoting yet
7089             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7090             boards[currentMove][toY][toX] = p;
7091             DrawPosition(FALSE, boards[currentMove]);
7092             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7093             boards[currentMove][toY][toX] = q;
7094             DisplayMessage("Click in holdings to choose piece", "");
7095             return;
7096         }
7097         PromotionPopUp();
7098     } else {
7099         int oldMove = currentMove;
7100         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7101         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7102         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7103         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7104            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7105             DrawPosition(TRUE, boards[currentMove]);
7106         fromX = fromY = -1;
7107     }
7108     appData.animate = saveAnimate;
7109     if (appData.animate || appData.animateDragging) {
7110         /* Undo animation damage if needed */
7111         DrawPosition(FALSE, NULL);
7112     }
7113 }
7114
7115 int
7116 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7117 {   // front-end-free part taken out of PieceMenuPopup
7118     int whichMenu; int xSqr, ySqr;
7119
7120     if(seekGraphUp) { // [HGM] seekgraph
7121         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7122         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7123         return -2;
7124     }
7125
7126     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7127          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7128         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7129         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7130         if(action == Press)   {
7131             originalFlip = flipView;
7132             flipView = !flipView; // temporarily flip board to see game from partners perspective
7133             DrawPosition(TRUE, partnerBoard);
7134             DisplayMessage(partnerStatus, "");
7135             partnerUp = TRUE;
7136         } else if(action == Release) {
7137             flipView = originalFlip;
7138             DrawPosition(TRUE, boards[currentMove]);
7139             partnerUp = FALSE;
7140         }
7141         return -2;
7142     }
7143
7144     xSqr = EventToSquare(x, BOARD_WIDTH);
7145     ySqr = EventToSquare(y, BOARD_HEIGHT);
7146     if (action == Release) {
7147         if(pieceSweep != EmptySquare) {
7148             EditPositionMenuEvent(pieceSweep, toX, toY);
7149             pieceSweep = EmptySquare;
7150         } else UnLoadPV(); // [HGM] pv
7151     }
7152     if (action != Press) return -2; // return code to be ignored
7153     switch (gameMode) {
7154       case IcsExamining:
7155         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7156       case EditPosition:
7157         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7158         if (xSqr < 0 || ySqr < 0) return -1;
7159         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7160         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7161         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7162         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7163         NextPiece(0);
7164         return 2; // grab
7165       case IcsObserving:
7166         if(!appData.icsEngineAnalyze) return -1;
7167       case IcsPlayingWhite:
7168       case IcsPlayingBlack:
7169         if(!appData.zippyPlay) goto noZip;
7170       case AnalyzeMode:
7171       case AnalyzeFile:
7172       case MachinePlaysWhite:
7173       case MachinePlaysBlack:
7174       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7175         if (!appData.dropMenu) {
7176           LoadPV(x, y);
7177           return 2; // flag front-end to grab mouse events
7178         }
7179         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7180            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7181       case EditGame:
7182       noZip:
7183         if (xSqr < 0 || ySqr < 0) return -1;
7184         if (!appData.dropMenu || appData.testLegality &&
7185             gameInfo.variant != VariantBughouse &&
7186             gameInfo.variant != VariantCrazyhouse) return -1;
7187         whichMenu = 1; // drop menu
7188         break;
7189       default:
7190         return -1;
7191     }
7192
7193     if (((*fromX = xSqr) < 0) ||
7194         ((*fromY = ySqr) < 0)) {
7195         *fromX = *fromY = -1;
7196         return -1;
7197     }
7198     if (flipView)
7199       *fromX = BOARD_WIDTH - 1 - *fromX;
7200     else
7201       *fromY = BOARD_HEIGHT - 1 - *fromY;
7202
7203     return whichMenu;
7204 }
7205
7206 void
7207 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7208 {
7209 //    char * hint = lastHint;
7210     FrontEndProgramStats stats;
7211
7212     stats.which = cps == &first ? 0 : 1;
7213     stats.depth = cpstats->depth;
7214     stats.nodes = cpstats->nodes;
7215     stats.score = cpstats->score;
7216     stats.time = cpstats->time;
7217     stats.pv = cpstats->movelist;
7218     stats.hint = lastHint;
7219     stats.an_move_index = 0;
7220     stats.an_move_count = 0;
7221
7222     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7223         stats.hint = cpstats->move_name;
7224         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7225         stats.an_move_count = cpstats->nr_moves;
7226     }
7227
7228     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
7229
7230     SetProgramStats( &stats );
7231 }
7232
7233 void
7234 ClearEngineOutputPane (int which)
7235 {
7236     static FrontEndProgramStats dummyStats;
7237     dummyStats.which = which;
7238     dummyStats.pv = "#";
7239     SetProgramStats( &dummyStats );
7240 }
7241
7242 #define MAXPLAYERS 500
7243
7244 char *
7245 TourneyStandings (int display)
7246 {
7247     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7248     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7249     char result, *p, *names[MAXPLAYERS];
7250
7251     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7252         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7253     names[0] = p = strdup(appData.participants);
7254     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7255
7256     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7257
7258     while(result = appData.results[nr]) {
7259         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7260         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7261         wScore = bScore = 0;
7262         switch(result) {
7263           case '+': wScore = 2; break;
7264           case '-': bScore = 2; break;
7265           case '=': wScore = bScore = 1; break;
7266           case ' ':
7267           case '*': return strdup("busy"); // tourney not finished
7268         }
7269         score[w] += wScore;
7270         score[b] += bScore;
7271         games[w]++;
7272         games[b]++;
7273         nr++;
7274     }
7275     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7276     for(w=0; w<nPlayers; w++) {
7277         bScore = -1;
7278         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7279         ranking[w] = b; points[w] = bScore; score[b] = -2;
7280     }
7281     p = malloc(nPlayers*34+1);
7282     for(w=0; w<nPlayers && w<display; w++)
7283         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7284     free(names[0]);
7285     return p;
7286 }
7287
7288 void
7289 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7290 {       // count all piece types
7291         int p, f, r;
7292         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7293         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7295                 p = board[r][f];
7296                 pCnt[p]++;
7297                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7298                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7299                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7300                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7301                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7302                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7303         }
7304 }
7305
7306 int
7307 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7308 {
7309         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7310         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7311
7312         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7313         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7314         if(myPawns == 2 && nMine == 3) // KPP
7315             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7316         if(myPawns == 1 && nMine == 2) // KP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7318         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7320         if(myPawns) return FALSE;
7321         if(pCnt[WhiteRook+side])
7322             return pCnt[BlackRook-side] ||
7323                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7324                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7325                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7326         if(pCnt[WhiteCannon+side]) {
7327             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7328             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7329         }
7330         if(pCnt[WhiteKnight+side])
7331             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7332         return FALSE;
7333 }
7334
7335 int
7336 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7337 {
7338         VariantClass v = gameInfo.variant;
7339
7340         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7341         if(v == VariantShatranj) return TRUE; // always winnable through baring
7342         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7343         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7344
7345         if(v == VariantXiangqi) {
7346                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7347
7348                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7349                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7350                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7351                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7352                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7353                 if(stale) // we have at least one last-rank P plus perhaps C
7354                     return majors // KPKX
7355                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7356                 else // KCA*E*
7357                     return pCnt[WhiteFerz+side] // KCAK
7358                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7359                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7360                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7361
7362         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7363                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7364
7365                 if(nMine == 1) return FALSE; // bare King
7366                 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
7367                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7368                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7369                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7370                 if(pCnt[WhiteKnight+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7372                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7373                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7374                 if(nBishops)
7375                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7376                 if(pCnt[WhiteAlfil+side])
7377                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7378                 if(pCnt[WhiteWazir+side])
7379                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7380         }
7381
7382         return TRUE;
7383 }
7384
7385 int
7386 CompareWithRights (Board b1, Board b2)
7387 {
7388     int rights = 0;
7389     if(!CompareBoards(b1, b2)) return FALSE;
7390     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7391     /* compare castling rights */
7392     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7393            rights++; /* King lost rights, while rook still had them */
7394     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7395         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7396            rights++; /* but at least one rook lost them */
7397     }
7398     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7399            rights++;
7400     if( b1[CASTLING][5] != NoRights ) {
7401         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7402            rights++;
7403     }
7404     return rights == 0;
7405 }
7406
7407 int
7408 Adjudicate (ChessProgramState *cps)
7409 {       // [HGM] some adjudications useful with buggy engines
7410         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7411         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7412         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7413         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7414         int k, count = 0; static int bare = 1;
7415         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7416         Boolean canAdjudicate = !appData.icsActive;
7417
7418         // most tests only when we understand the game, i.e. legality-checking on
7419             if( appData.testLegality )
7420             {   /* [HGM] Some more adjudications for obstinate engines */
7421                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7422                 static int moveCount = 6;
7423                 ChessMove result;
7424                 char *reason = NULL;
7425
7426                 /* Count what is on board. */
7427                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7428
7429                 /* Some material-based adjudications that have to be made before stalemate test */
7430                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7431                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7432                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7433                      if(canAdjudicate && appData.checkMates) {
7434                          if(engineOpponent)
7435                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7436                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7437                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7438                          return 1;
7439                      }
7440                 }
7441
7442                 /* Bare King in Shatranj (loses) or Losers (wins) */
7443                 if( nrW == 1 || nrB == 1) {
7444                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7445                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7446                      if(canAdjudicate && appData.checkMates) {
7447                          if(engineOpponent)
7448                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7449                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7450                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7451                          return 1;
7452                      }
7453                   } else
7454                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7455                   {    /* bare King */
7456                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7457                         if(canAdjudicate && appData.checkMates) {
7458                             /* but only adjudicate if adjudication enabled */
7459                             if(engineOpponent)
7460                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7461                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7462                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7463                             return 1;
7464                         }
7465                   }
7466                 } else bare = 1;
7467
7468
7469             // don't wait for engine to announce game end if we can judge ourselves
7470             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7471               case MT_CHECK:
7472                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7473                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7474                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7475                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7476                             checkCnt++;
7477                         if(checkCnt >= 2) {
7478                             reason = "Xboard adjudication: 3rd check";
7479                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7480                             break;
7481                         }
7482                     }
7483                 }
7484               case MT_NONE:
7485               default:
7486                 break;
7487               case MT_STALEMATE:
7488               case MT_STAINMATE:
7489                 reason = "Xboard adjudication: Stalemate";
7490                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7491                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7492                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7493                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7494                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7495                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7496                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7497                                                                         EP_CHECKMATE : EP_WINS);
7498                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7499                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7500                 }
7501                 break;
7502               case MT_CHECKMATE:
7503                 reason = "Xboard adjudication: Checkmate";
7504                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7505                 break;
7506             }
7507
7508                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7509                     case EP_STALEMATE:
7510                         result = GameIsDrawn; break;
7511                     case EP_CHECKMATE:
7512                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7513                     case EP_WINS:
7514                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7515                     default:
7516                         result = EndOfFile;
7517                 }
7518                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7519                     if(engineOpponent)
7520                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7521                     GameEnds( result, reason, GE_XBOARD );
7522                     return 1;
7523                 }
7524
7525                 /* Next absolutely insufficient mating material. */
7526                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7527                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7528                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7529
7530                      /* always flag draws, for judging claims */
7531                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7532
7533                      if(canAdjudicate && appData.materialDraws) {
7534                          /* but only adjudicate them if adjudication enabled */
7535                          if(engineOpponent) {
7536                            SendToProgram("force\n", engineOpponent); // suppress reply
7537                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7538                          }
7539                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7540                          return 1;
7541                      }
7542                 }
7543
7544                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7545                 if(gameInfo.variant == VariantXiangqi ?
7546                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7547                  : nrW + nrB == 4 &&
7548                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7549                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7550                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7551                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7552                    ) ) {
7553                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7554                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7555                           if(engineOpponent) {
7556                             SendToProgram("force\n", engineOpponent); // suppress reply
7557                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                           }
7559                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7560                           return 1;
7561                      }
7562                 } else moveCount = 6;
7563             }
7564
7565         // Repetition draws and 50-move rule can be applied independently of legality testing
7566
7567                 /* Check for rep-draws */
7568                 count = 0;
7569                 for(k = forwardMostMove-2;
7570                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7571                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7572                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7573                     k-=2)
7574                 {   int rights=0;
7575                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7576                         /* compare castling rights */
7577                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7578                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7579                                 rights++; /* King lost rights, while rook still had them */
7580                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7581                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7582                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7583                                    rights++; /* but at least one rook lost them */
7584                         }
7585                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7586                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7587                                 rights++;
7588                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7589                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7590                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7591                                    rights++;
7592                         }
7593                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7594                             && appData.drawRepeats > 1) {
7595                              /* adjudicate after user-specified nr of repeats */
7596                              int result = GameIsDrawn;
7597                              char *details = "XBoard adjudication: repetition draw";
7598                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7599                                 // [HGM] xiangqi: check for forbidden perpetuals
7600                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7601                                 for(m=forwardMostMove; m>k; m-=2) {
7602                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7603                                         ourPerpetual = 0; // the current mover did not always check
7604                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7605                                         hisPerpetual = 0; // the opponent did not always check
7606                                 }
7607                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7608                                                                         ourPerpetual, hisPerpetual);
7609                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7610                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7611                                     details = "Xboard adjudication: perpetual checking";
7612                                 } else
7613                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7614                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7615                                 } else
7616                                 // Now check for perpetual chases
7617                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7618                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7619                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7620                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7621                                         static char resdet[MSG_SIZ];
7622                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7623                                         details = resdet;
7624                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7625                                     } else
7626                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7627                                         break; // Abort repetition-checking loop.
7628                                 }
7629                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7630                              }
7631                              if(engineOpponent) {
7632                                SendToProgram("force\n", engineOpponent); // suppress reply
7633                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7634                              }
7635                              GameEnds( result, details, GE_XBOARD );
7636                              return 1;
7637                         }
7638                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7639                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7640                     }
7641                 }
7642
7643                 /* Now we test for 50-move draws. Determine ply count */
7644                 count = forwardMostMove;
7645                 /* look for last irreversble move */
7646                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7647                     count--;
7648                 /* if we hit starting position, add initial plies */
7649                 if( count == backwardMostMove )
7650                     count -= initialRulePlies;
7651                 count = forwardMostMove - count;
7652                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7653                         // adjust reversible move counter for checks in Xiangqi
7654                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7655                         if(i < backwardMostMove) i = backwardMostMove;
7656                         while(i <= forwardMostMove) {
7657                                 lastCheck = inCheck; // check evasion does not count
7658                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7659                                 if(inCheck || lastCheck) count--; // check does not count
7660                                 i++;
7661                         }
7662                 }
7663                 if( count >= 100)
7664                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7665                          /* this is used to judge if draw claims are legal */
7666                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7667                          if(engineOpponent) {
7668                            SendToProgram("force\n", engineOpponent); // suppress reply
7669                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7670                          }
7671                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7672                          return 1;
7673                 }
7674
7675                 /* if draw offer is pending, treat it as a draw claim
7676                  * when draw condition present, to allow engines a way to
7677                  * claim draws before making their move to avoid a race
7678                  * condition occurring after their move
7679                  */
7680                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7681                          char *p = NULL;
7682                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7683                              p = "Draw claim: 50-move rule";
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7685                              p = "Draw claim: 3-fold repetition";
7686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7687                              p = "Draw claim: insufficient mating material";
7688                          if( p != NULL && canAdjudicate) {
7689                              if(engineOpponent) {
7690                                SendToProgram("force\n", engineOpponent); // suppress reply
7691                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7692                              }
7693                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7694                              return 1;
7695                          }
7696                 }
7697
7698                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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, "Xboard adjudication: long game", GE_XBOARD );
7704                     return 1;
7705                 }
7706         return 0;
7707 }
7708
7709 char *
7710 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7711 {   // [HGM] book: this routine intercepts moves to simulate book replies
7712     char *bookHit = NULL;
7713
7714     //first determine if the incoming move brings opponent into his book
7715     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7716         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7717     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7718     if(bookHit != NULL && !cps->bookSuspend) {
7719         // make sure opponent is not going to reply after receiving move to book position
7720         SendToProgram("force\n", cps);
7721         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7722     }
7723     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7724     // now arrange restart after book miss
7725     if(bookHit) {
7726         // after a book hit we never send 'go', and the code after the call to this routine
7727         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7728         char buf[MSG_SIZ], *move = bookHit;
7729         if(cps->useSAN) {
7730             int fromX, fromY, toX, toY;
7731             char promoChar;
7732             ChessMove moveType;
7733             move = buf + 30;
7734             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7735                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7736                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7737                                     PosFlags(forwardMostMove),
7738                                     fromY, fromX, toY, toX, promoChar, move);
7739             } else {
7740                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7741                 bookHit = NULL;
7742             }
7743         }
7744         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7745         SendToProgram(buf, cps);
7746         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7747     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7748         SendToProgram("go\n", cps);
7749         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7750     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7751         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7752             SendToProgram("go\n", cps);
7753         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7754     }
7755     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7756 }
7757
7758 char *savedMessage;
7759 ChessProgramState *savedState;
7760 void
7761 DeferredBookMove (void)
7762 {
7763         if(savedState->lastPing != savedState->lastPong)
7764                     ScheduleDelayedEvent(DeferredBookMove, 10);
7765         else
7766         HandleMachineMove(savedMessage, savedState);
7767 }
7768
7769 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7770
7771 void
7772 HandleMachineMove (char *message, ChessProgramState *cps)
7773 {
7774     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7775     char realname[MSG_SIZ];
7776     int fromX, fromY, toX, toY;
7777     ChessMove moveType;
7778     char promoChar;
7779     char *p, *pv=buf1;
7780     int machineWhite;
7781     char *bookHit;
7782
7783     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7784         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7785         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7786             DisplayError(_("Invalid pairing from pairing engine"), 0);
7787             return;
7788         }
7789         pairingReceived = 1;
7790         NextMatchGame();
7791         return; // Skim the pairing messages here.
7792     }
7793
7794     cps->userError = 0;
7795
7796 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7797     /*
7798      * Kludge to ignore BEL characters
7799      */
7800     while (*message == '\007') message++;
7801
7802     /*
7803      * [HGM] engine debug message: ignore lines starting with '#' character
7804      */
7805     if(cps->debug && *message == '#') return;
7806
7807     /*
7808      * Look for book output
7809      */
7810     if (cps == &first && bookRequested) {
7811         if (message[0] == '\t' || message[0] == ' ') {
7812             /* Part of the book output is here; append it */
7813             strcat(bookOutput, message);
7814             strcat(bookOutput, "  \n");
7815             return;
7816         } else if (bookOutput[0] != NULLCHAR) {
7817             /* All of book output has arrived; display it */
7818             char *p = bookOutput;
7819             while (*p != NULLCHAR) {
7820                 if (*p == '\t') *p = ' ';
7821                 p++;
7822             }
7823             DisplayInformation(bookOutput);
7824             bookRequested = FALSE;
7825             /* Fall through to parse the current output */
7826         }
7827     }
7828
7829     /*
7830      * Look for machine move.
7831      */
7832     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7833         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7834     {
7835         /* This method is only useful on engines that support ping */
7836         if (cps->lastPing != cps->lastPong) {
7837           if (gameMode == BeginningOfGame) {
7838             /* Extra move from before last new; ignore */
7839             if (appData.debugMode) {
7840                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7841             }
7842           } else {
7843             if (appData.debugMode) {
7844                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7845                         cps->which, gameMode);
7846             }
7847
7848             SendToProgram("undo\n", cps);
7849           }
7850           return;
7851         }
7852
7853         switch (gameMode) {
7854           case BeginningOfGame:
7855             /* Extra move from before last reset; ignore */
7856             if (appData.debugMode) {
7857                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7858             }
7859             return;
7860
7861           case EndOfGame:
7862           case IcsIdle:
7863           default:
7864             /* Extra move after we tried to stop.  The mode test is
7865                not a reliable way of detecting this problem, but it's
7866                the best we can do on engines that don't support ping.
7867             */
7868             if (appData.debugMode) {
7869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7870                         cps->which, gameMode);
7871             }
7872             SendToProgram("undo\n", cps);
7873             return;
7874
7875           case MachinePlaysWhite:
7876           case IcsPlayingWhite:
7877             machineWhite = TRUE;
7878             break;
7879
7880           case MachinePlaysBlack:
7881           case IcsPlayingBlack:
7882             machineWhite = FALSE;
7883             break;
7884
7885           case TwoMachinesPlay:
7886             machineWhite = (cps->twoMachinesColor[0] == 'w');
7887             break;
7888         }
7889         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7890             if (appData.debugMode) {
7891                 fprintf(debugFP,
7892                         "Ignoring move out of turn by %s, gameMode %d"
7893                         ", forwardMost %d\n",
7894                         cps->which, gameMode, forwardMostMove);
7895             }
7896             return;
7897         }
7898
7899         if(cps->alphaRank) AlphaRank(machineMove, 4);
7900         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7901                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7902             /* Machine move could not be parsed; ignore it. */
7903           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7904                     machineMove, _(cps->which));
7905             DisplayError(buf1, 0);
7906             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7907                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7908             if (gameMode == TwoMachinesPlay) {
7909               GameEnds(machineWhite ? BlackWins : WhiteWins,
7910                        buf1, GE_XBOARD);
7911             }
7912             return;
7913         }
7914
7915         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7916         /* So we have to redo legality test with true e.p. status here,  */
7917         /* to make sure an illegal e.p. capture does not slip through,   */
7918         /* to cause a forfeit on a justified illegal-move complaint      */
7919         /* of the opponent.                                              */
7920         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7921            ChessMove moveType;
7922            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7923                              fromY, fromX, toY, toX, promoChar);
7924             if(moveType == IllegalMove) {
7925               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7926                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7927                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7928                            buf1, GE_XBOARD);
7929                 return;
7930            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7931            /* [HGM] Kludge to handle engines that send FRC-style castling
7932               when they shouldn't (like TSCP-Gothic) */
7933            switch(moveType) {
7934              case WhiteASideCastleFR:
7935              case BlackASideCastleFR:
7936                toX+=2;
7937                currentMoveString[2]++;
7938                break;
7939              case WhiteHSideCastleFR:
7940              case BlackHSideCastleFR:
7941                toX--;
7942                currentMoveString[2]--;
7943                break;
7944              default: ; // nothing to do, but suppresses warning of pedantic compilers
7945            }
7946         }
7947         hintRequested = FALSE;
7948         lastHint[0] = NULLCHAR;
7949         bookRequested = FALSE;
7950         /* Program may be pondering now */
7951         cps->maybeThinking = TRUE;
7952         if (cps->sendTime == 2) cps->sendTime = 1;
7953         if (cps->offeredDraw) cps->offeredDraw--;
7954
7955         /* [AS] Save move info*/
7956         pvInfoList[ forwardMostMove ].score = programStats.score;
7957         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7958         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7959
7960         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7961
7962         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7963         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7964             int count = 0;
7965
7966             while( count < adjudicateLossPlies ) {
7967                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7968
7969                 if( count & 1 ) {
7970                     score = -score; /* Flip score for winning side */
7971                 }
7972
7973                 if( score > adjudicateLossThreshold ) {
7974                     break;
7975                 }
7976
7977                 count++;
7978             }
7979
7980             if( count >= adjudicateLossPlies ) {
7981                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7982
7983                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7984                     "Xboard adjudication",
7985                     GE_XBOARD );
7986
7987                 return;
7988             }
7989         }
7990
7991         if(Adjudicate(cps)) {
7992             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7993             return; // [HGM] adjudicate: for all automatic game ends
7994         }
7995
7996 #if ZIPPY
7997         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7998             first.initDone) {
7999           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8000                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8001                 SendToICS("draw ");
8002                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8003           }
8004           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8005           ics_user_moved = 1;
8006           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8007                 char buf[3*MSG_SIZ];
8008
8009                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8010                         programStats.score / 100.,
8011                         programStats.depth,
8012                         programStats.time / 100.,
8013                         (unsigned int)programStats.nodes,
8014                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8015                         programStats.movelist);
8016                 SendToICS(buf);
8017 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8018           }
8019         }
8020 #endif
8021
8022         /* [AS] Clear stats for next move */
8023         ClearProgramStats();
8024         thinkOutput[0] = NULLCHAR;
8025         hiddenThinkOutputState = 0;
8026
8027         bookHit = NULL;
8028         if (gameMode == TwoMachinesPlay) {
8029             /* [HGM] relaying draw offers moved to after reception of move */
8030             /* and interpreting offer as claim if it brings draw condition */
8031             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8032                 SendToProgram("draw\n", cps->other);
8033             }
8034             if (cps->other->sendTime) {
8035                 SendTimeRemaining(cps->other,
8036                                   cps->other->twoMachinesColor[0] == 'w');
8037             }
8038             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8039             if (firstMove && !bookHit) {
8040                 firstMove = FALSE;
8041                 if (cps->other->useColors) {
8042                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8043                 }
8044                 SendToProgram("go\n", cps->other);
8045             }
8046             cps->other->maybeThinking = TRUE;
8047         }
8048
8049         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8050
8051         if (!pausing && appData.ringBellAfterMoves) {
8052             RingBell();
8053         }
8054
8055         /*
8056          * Reenable menu items that were disabled while
8057          * machine was thinking
8058          */
8059         if (gameMode != TwoMachinesPlay)
8060             SetUserThinkingEnables();
8061
8062         // [HGM] book: after book hit opponent has received move and is now in force mode
8063         // force the book reply into it, and then fake that it outputted this move by jumping
8064         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8065         if(bookHit) {
8066                 static char bookMove[MSG_SIZ]; // a bit generous?
8067
8068                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8069                 strcat(bookMove, bookHit);
8070                 message = bookMove;
8071                 cps = cps->other;
8072                 programStats.nodes = programStats.depth = programStats.time =
8073                 programStats.score = programStats.got_only_move = 0;
8074                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8075
8076                 if(cps->lastPing != cps->lastPong) {
8077                     savedMessage = message; // args for deferred call
8078                     savedState = cps;
8079                     ScheduleDelayedEvent(DeferredBookMove, 10);
8080                     return;
8081                 }
8082                 goto FakeBookMove;
8083         }
8084
8085         return;
8086     }
8087
8088     /* Set special modes for chess engines.  Later something general
8089      *  could be added here; for now there is just one kludge feature,
8090      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8091      *  when "xboard" is given as an interactive command.
8092      */
8093     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8094         cps->useSigint = FALSE;
8095         cps->useSigterm = FALSE;
8096     }
8097     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8098       ParseFeatures(message+8, cps);
8099       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8100     }
8101
8102     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8103                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8104       int dummy, s=6; char buf[MSG_SIZ];
8105       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8106       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8107       if(startedFromSetupPosition) return;
8108       ParseFEN(boards[0], &dummy, message+s);
8109       DrawPosition(TRUE, boards[0]);
8110       startedFromSetupPosition = TRUE;
8111       return;
8112     }
8113     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8114      * want this, I was asked to put it in, and obliged.
8115      */
8116     if (!strncmp(message, "setboard ", 9)) {
8117         Board initial_position;
8118
8119         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8120
8121         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8122             DisplayError(_("Bad FEN received from engine"), 0);
8123             return ;
8124         } else {
8125            Reset(TRUE, FALSE);
8126            CopyBoard(boards[0], initial_position);
8127            initialRulePlies = FENrulePlies;
8128            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8129            else gameMode = MachinePlaysBlack;
8130            DrawPosition(FALSE, boards[currentMove]);
8131         }
8132         return;
8133     }
8134
8135     /*
8136      * Look for communication commands
8137      */
8138     if (!strncmp(message, "telluser ", 9)) {
8139         if(message[9] == '\\' && message[10] == '\\')
8140             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8141         PlayTellSound();
8142         DisplayNote(message + 9);
8143         return;
8144     }
8145     if (!strncmp(message, "tellusererror ", 14)) {
8146         cps->userError = 1;
8147         if(message[14] == '\\' && message[15] == '\\')
8148             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8149         PlayTellSound();
8150         DisplayError(message + 14, 0);
8151         return;
8152     }
8153     if (!strncmp(message, "tellopponent ", 13)) {
8154       if (appData.icsActive) {
8155         if (loggedOn) {
8156           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8157           SendToICS(buf1);
8158         }
8159       } else {
8160         DisplayNote(message + 13);
8161       }
8162       return;
8163     }
8164     if (!strncmp(message, "tellothers ", 11)) {
8165       if (appData.icsActive) {
8166         if (loggedOn) {
8167           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8168           SendToICS(buf1);
8169         }
8170       }
8171       return;
8172     }
8173     if (!strncmp(message, "tellall ", 8)) {
8174       if (appData.icsActive) {
8175         if (loggedOn) {
8176           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8177           SendToICS(buf1);
8178         }
8179       } else {
8180         DisplayNote(message + 8);
8181       }
8182       return;
8183     }
8184     if (strncmp(message, "warning", 7) == 0) {
8185         /* Undocumented feature, use tellusererror in new code */
8186         DisplayError(message, 0);
8187         return;
8188     }
8189     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8190         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8191         strcat(realname, " query");
8192         AskQuestion(realname, buf2, buf1, cps->pr);
8193         return;
8194     }
8195     /* Commands from the engine directly to ICS.  We don't allow these to be
8196      *  sent until we are logged on. Crafty kibitzes have been known to
8197      *  interfere with the login process.
8198      */
8199     if (loggedOn) {
8200         if (!strncmp(message, "tellics ", 8)) {
8201             SendToICS(message + 8);
8202             SendToICS("\n");
8203             return;
8204         }
8205         if (!strncmp(message, "tellicsnoalias ", 15)) {
8206             SendToICS(ics_prefix);
8207             SendToICS(message + 15);
8208             SendToICS("\n");
8209             return;
8210         }
8211         /* The following are for backward compatibility only */
8212         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8213             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8214             SendToICS(ics_prefix);
8215             SendToICS(message);
8216             SendToICS("\n");
8217             return;
8218         }
8219     }
8220     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8221         return;
8222     }
8223     /*
8224      * If the move is illegal, cancel it and redraw the board.
8225      * Also deal with other error cases.  Matching is rather loose
8226      * here to accommodate engines written before the spec.
8227      */
8228     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8229         strncmp(message, "Error", 5) == 0) {
8230         if (StrStr(message, "name") ||
8231             StrStr(message, "rating") || StrStr(message, "?") ||
8232             StrStr(message, "result") || StrStr(message, "board") ||
8233             StrStr(message, "bk") || StrStr(message, "computer") ||
8234             StrStr(message, "variant") || StrStr(message, "hint") ||
8235             StrStr(message, "random") || StrStr(message, "depth") ||
8236             StrStr(message, "accepted")) {
8237             return;
8238         }
8239         if (StrStr(message, "protover")) {
8240           /* Program is responding to input, so it's apparently done
8241              initializing, and this error message indicates it is
8242              protocol version 1.  So we don't need to wait any longer
8243              for it to initialize and send feature commands. */
8244           FeatureDone(cps, 1);
8245           cps->protocolVersion = 1;
8246           return;
8247         }
8248         cps->maybeThinking = FALSE;
8249
8250         if (StrStr(message, "draw")) {
8251             /* Program doesn't have "draw" command */
8252             cps->sendDrawOffers = 0;
8253             return;
8254         }
8255         if (cps->sendTime != 1 &&
8256             (StrStr(message, "time") || StrStr(message, "otim"))) {
8257           /* Program apparently doesn't have "time" or "otim" command */
8258           cps->sendTime = 0;
8259           return;
8260         }
8261         if (StrStr(message, "analyze")) {
8262             cps->analysisSupport = FALSE;
8263             cps->analyzing = FALSE;
8264 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8265             EditGameEvent(); // [HGM] try to preserve loaded game
8266             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8267             DisplayError(buf2, 0);
8268             return;
8269         }
8270         if (StrStr(message, "(no matching move)st")) {
8271           /* Special kludge for GNU Chess 4 only */
8272           cps->stKludge = TRUE;
8273           SendTimeControl(cps, movesPerSession, timeControl,
8274                           timeIncrement, appData.searchDepth,
8275                           searchTime);
8276           return;
8277         }
8278         if (StrStr(message, "(no matching move)sd")) {
8279           /* Special kludge for GNU Chess 4 only */
8280           cps->sdKludge = TRUE;
8281           SendTimeControl(cps, movesPerSession, timeControl,
8282                           timeIncrement, appData.searchDepth,
8283                           searchTime);
8284           return;
8285         }
8286         if (!StrStr(message, "llegal")) {
8287             return;
8288         }
8289         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8290             gameMode == IcsIdle) return;
8291         if (forwardMostMove <= backwardMostMove) return;
8292         if (pausing) PauseEvent();
8293       if(appData.forceIllegal) {
8294             // [HGM] illegal: machine refused move; force position after move into it
8295           SendToProgram("force\n", cps);
8296           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8297                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8298                 // when black is to move, while there might be nothing on a2 or black
8299                 // might already have the move. So send the board as if white has the move.
8300                 // But first we must change the stm of the engine, as it refused the last move
8301                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8302                 if(WhiteOnMove(forwardMostMove)) {
8303                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8304                     SendBoard(cps, forwardMostMove); // kludgeless board
8305                 } else {
8306                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8307                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8308                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8309                 }
8310           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8311             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8312                  gameMode == TwoMachinesPlay)
8313               SendToProgram("go\n", cps);
8314             return;
8315       } else
8316         if (gameMode == PlayFromGameFile) {
8317             /* Stop reading this game file */
8318             gameMode = EditGame;
8319             ModeHighlight();
8320         }
8321         /* [HGM] illegal-move claim should forfeit game when Xboard */
8322         /* only passes fully legal moves                            */
8323         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8324             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8325                                 "False illegal-move claim", GE_XBOARD );
8326             return; // do not take back move we tested as valid
8327         }
8328         currentMove = forwardMostMove-1;
8329         DisplayMove(currentMove-1); /* before DisplayMoveError */
8330         SwitchClocks(forwardMostMove-1); // [HGM] race
8331         DisplayBothClocks();
8332         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8333                 parseList[currentMove], _(cps->which));
8334         DisplayMoveError(buf1);
8335         DrawPosition(FALSE, boards[currentMove]);
8336
8337         SetUserThinkingEnables();
8338         return;
8339     }
8340     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8341         /* Program has a broken "time" command that
8342            outputs a string not ending in newline.
8343            Don't use it. */
8344         cps->sendTime = 0;
8345     }
8346
8347     /*
8348      * If chess program startup fails, exit with an error message.
8349      * Attempts to recover here are futile.
8350      */
8351     if ((StrStr(message, "unknown host") != NULL)
8352         || (StrStr(message, "No remote directory") != NULL)
8353         || (StrStr(message, "not found") != NULL)
8354         || (StrStr(message, "No such file") != NULL)
8355         || (StrStr(message, "can't alloc") != NULL)
8356         || (StrStr(message, "Permission denied") != NULL)) {
8357
8358         cps->maybeThinking = FALSE;
8359         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8360                 _(cps->which), cps->program, cps->host, message);
8361         RemoveInputSource(cps->isr);
8362         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8363             if(cps == &first) appData.noChessProgram = TRUE;
8364             DisplayError(buf1, 0);
8365         }
8366         return;
8367     }
8368
8369     /*
8370      * Look for hint output
8371      */
8372     if (sscanf(message, "Hint: %s", buf1) == 1) {
8373         if (cps == &first && hintRequested) {
8374             hintRequested = FALSE;
8375             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8376                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8377                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8378                                     PosFlags(forwardMostMove),
8379                                     fromY, fromX, toY, toX, promoChar, buf1);
8380                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8381                 DisplayInformation(buf2);
8382             } else {
8383                 /* Hint move could not be parsed!? */
8384               snprintf(buf2, sizeof(buf2),
8385                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8386                         buf1, _(cps->which));
8387                 DisplayError(buf2, 0);
8388             }
8389         } else {
8390           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8391         }
8392         return;
8393     }
8394
8395     /*
8396      * Ignore other messages if game is not in progress
8397      */
8398     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8399         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8400
8401     /*
8402      * look for win, lose, draw, or draw offer
8403      */
8404     if (strncmp(message, "1-0", 3) == 0) {
8405         char *p, *q, *r = "";
8406         p = strchr(message, '{');
8407         if (p) {
8408             q = strchr(p, '}');
8409             if (q) {
8410                 *q = NULLCHAR;
8411                 r = p + 1;
8412             }
8413         }
8414         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8415         return;
8416     } else if (strncmp(message, "0-1", 3) == 0) {
8417         char *p, *q, *r = "";
8418         p = strchr(message, '{');
8419         if (p) {
8420             q = strchr(p, '}');
8421             if (q) {
8422                 *q = NULLCHAR;
8423                 r = p + 1;
8424             }
8425         }
8426         /* Kludge for Arasan 4.1 bug */
8427         if (strcmp(r, "Black resigns") == 0) {
8428             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8429             return;
8430         }
8431         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8432         return;
8433     } else if (strncmp(message, "1/2", 3) == 0) {
8434         char *p, *q, *r = "";
8435         p = strchr(message, '{');
8436         if (p) {
8437             q = strchr(p, '}');
8438             if (q) {
8439                 *q = NULLCHAR;
8440                 r = p + 1;
8441             }
8442         }
8443
8444         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8445         return;
8446
8447     } else if (strncmp(message, "White resign", 12) == 0) {
8448         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "Black resign", 12) == 0) {
8451         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8452         return;
8453     } else if (strncmp(message, "White matches", 13) == 0 ||
8454                strncmp(message, "Black matches", 13) == 0   ) {
8455         /* [HGM] ignore GNUShogi noises */
8456         return;
8457     } else if (strncmp(message, "White", 5) == 0 &&
8458                message[5] != '(' &&
8459                StrStr(message, "Black") == NULL) {
8460         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8461         return;
8462     } else if (strncmp(message, "Black", 5) == 0 &&
8463                message[5] != '(') {
8464         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8465         return;
8466     } else if (strcmp(message, "resign") == 0 ||
8467                strcmp(message, "computer resigns") == 0) {
8468         switch (gameMode) {
8469           case MachinePlaysBlack:
8470           case IcsPlayingBlack:
8471             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8472             break;
8473           case MachinePlaysWhite:
8474           case IcsPlayingWhite:
8475             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8476             break;
8477           case TwoMachinesPlay:
8478             if (cps->twoMachinesColor[0] == 'w')
8479               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8480             else
8481               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8482             break;
8483           default:
8484             /* can't happen */
8485             break;
8486         }
8487         return;
8488     } else if (strncmp(message, "opponent mates", 14) == 0) {
8489         switch (gameMode) {
8490           case MachinePlaysBlack:
8491           case IcsPlayingBlack:
8492             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8493             break;
8494           case MachinePlaysWhite:
8495           case IcsPlayingWhite:
8496             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8497             break;
8498           case TwoMachinesPlay:
8499             if (cps->twoMachinesColor[0] == 'w')
8500               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8501             else
8502               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8503             break;
8504           default:
8505             /* can't happen */
8506             break;
8507         }
8508         return;
8509     } else if (strncmp(message, "computer mates", 14) == 0) {
8510         switch (gameMode) {
8511           case MachinePlaysBlack:
8512           case IcsPlayingBlack:
8513             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8514             break;
8515           case MachinePlaysWhite:
8516           case IcsPlayingWhite:
8517             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8518             break;
8519           case TwoMachinesPlay:
8520             if (cps->twoMachinesColor[0] == 'w')
8521               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8522             else
8523               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8524             break;
8525           default:
8526             /* can't happen */
8527             break;
8528         }
8529         return;
8530     } else if (strncmp(message, "checkmate", 9) == 0) {
8531         if (WhiteOnMove(forwardMostMove)) {
8532             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8533         } else {
8534             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8535         }
8536         return;
8537     } else if (strstr(message, "Draw") != NULL ||
8538                strstr(message, "game is a draw") != NULL) {
8539         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8540         return;
8541     } else if (strstr(message, "offer") != NULL &&
8542                strstr(message, "draw") != NULL) {
8543 #if ZIPPY
8544         if (appData.zippyPlay && first.initDone) {
8545             /* Relay offer to ICS */
8546             SendToICS(ics_prefix);
8547             SendToICS("draw\n");
8548         }
8549 #endif
8550         cps->offeredDraw = 2; /* valid until this engine moves twice */
8551         if (gameMode == TwoMachinesPlay) {
8552             if (cps->other->offeredDraw) {
8553                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8554             /* [HGM] in two-machine mode we delay relaying draw offer      */
8555             /* until after we also have move, to see if it is really claim */
8556             }
8557         } else if (gameMode == MachinePlaysWhite ||
8558                    gameMode == MachinePlaysBlack) {
8559           if (userOfferedDraw) {
8560             DisplayInformation(_("Machine accepts your draw offer"));
8561             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8562           } else {
8563             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8564           }
8565         }
8566     }
8567
8568
8569     /*
8570      * Look for thinking output
8571      */
8572     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8573           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8574                                 ) {
8575         int plylev, mvleft, mvtot, curscore, time;
8576         char mvname[MOVE_LEN];
8577         u64 nodes; // [DM]
8578         char plyext;
8579         int ignore = FALSE;
8580         int prefixHint = FALSE;
8581         mvname[0] = NULLCHAR;
8582
8583         switch (gameMode) {
8584           case MachinePlaysBlack:
8585           case IcsPlayingBlack:
8586             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8587             break;
8588           case MachinePlaysWhite:
8589           case IcsPlayingWhite:
8590             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8591             break;
8592           case AnalyzeMode:
8593           case AnalyzeFile:
8594             break;
8595           case IcsObserving: /* [DM] icsEngineAnalyze */
8596             if (!appData.icsEngineAnalyze) ignore = TRUE;
8597             break;
8598           case TwoMachinesPlay:
8599             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8600                 ignore = TRUE;
8601             }
8602             break;
8603           default:
8604             ignore = TRUE;
8605             break;
8606         }
8607
8608         if (!ignore) {
8609             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8610             buf1[0] = NULLCHAR;
8611             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8612                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8613
8614                 if (plyext != ' ' && plyext != '\t') {
8615                     time *= 100;
8616                 }
8617
8618                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8619                 if( cps->scoreIsAbsolute &&
8620                     ( gameMode == MachinePlaysBlack ||
8621                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8622                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8623                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8624                      !WhiteOnMove(currentMove)
8625                     ) )
8626                 {
8627                     curscore = -curscore;
8628                 }
8629
8630                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8631
8632                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8633                         char buf[MSG_SIZ];
8634                         FILE *f;
8635                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8636                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8637                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8638                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8639                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8640                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8641                                 fclose(f);
8642                         } else DisplayError(_("failed writing PV"), 0);
8643                 }
8644
8645                 tempStats.depth = plylev;
8646                 tempStats.nodes = nodes;
8647                 tempStats.time = time;
8648                 tempStats.score = curscore;
8649                 tempStats.got_only_move = 0;
8650
8651                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8652                         int ticklen;
8653
8654                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8655                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8656                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8657                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8658                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8659                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8660                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8661                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8662                 }
8663
8664                 /* Buffer overflow protection */
8665                 if (pv[0] != NULLCHAR) {
8666                     if (strlen(pv) >= sizeof(tempStats.movelist)
8667                         && appData.debugMode) {
8668                         fprintf(debugFP,
8669                                 "PV is too long; using the first %u bytes.\n",
8670                                 (unsigned) sizeof(tempStats.movelist) - 1);
8671                     }
8672
8673                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8674                 } else {
8675                     sprintf(tempStats.movelist, " no PV\n");
8676                 }
8677
8678                 if (tempStats.seen_stat) {
8679                     tempStats.ok_to_send = 1;
8680                 }
8681
8682                 if (strchr(tempStats.movelist, '(') != NULL) {
8683                     tempStats.line_is_book = 1;
8684                     tempStats.nr_moves = 0;
8685                     tempStats.moves_left = 0;
8686                 } else {
8687                     tempStats.line_is_book = 0;
8688                 }
8689
8690                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8691                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8692
8693                 SendProgramStatsToFrontend( cps, &tempStats );
8694
8695                 /*
8696                     [AS] Protect the thinkOutput buffer from overflow... this
8697                     is only useful if buf1 hasn't overflowed first!
8698                 */
8699                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8700                          plylev,
8701                          (gameMode == TwoMachinesPlay ?
8702                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8703                          ((double) curscore) / 100.0,
8704                          prefixHint ? lastHint : "",
8705                          prefixHint ? " " : "" );
8706
8707                 if( buf1[0] != NULLCHAR ) {
8708                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8709
8710                     if( strlen(pv) > max_len ) {
8711                         if( appData.debugMode) {
8712                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8713                         }
8714                         pv[max_len+1] = '\0';
8715                     }
8716
8717                     strcat( thinkOutput, pv);
8718                 }
8719
8720                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8721                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8722                     DisplayMove(currentMove - 1);
8723                 }
8724                 return;
8725
8726             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8727                 /* crafty (9.25+) says "(only move) <move>"
8728                  * if there is only 1 legal move
8729                  */
8730                 sscanf(p, "(only move) %s", buf1);
8731                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8732                 sprintf(programStats.movelist, "%s (only move)", buf1);
8733                 programStats.depth = 1;
8734                 programStats.nr_moves = 1;
8735                 programStats.moves_left = 1;
8736                 programStats.nodes = 1;
8737                 programStats.time = 1;
8738                 programStats.got_only_move = 1;
8739
8740                 /* Not really, but we also use this member to
8741                    mean "line isn't going to change" (Crafty
8742                    isn't searching, so stats won't change) */
8743                 programStats.line_is_book = 1;
8744
8745                 SendProgramStatsToFrontend( cps, &programStats );
8746
8747                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8748                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8749                     DisplayMove(currentMove - 1);
8750                 }
8751                 return;
8752             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8753                               &time, &nodes, &plylev, &mvleft,
8754                               &mvtot, mvname) >= 5) {
8755                 /* The stat01: line is from Crafty (9.29+) in response
8756                    to the "." command */
8757                 programStats.seen_stat = 1;
8758                 cps->maybeThinking = TRUE;
8759
8760                 if (programStats.got_only_move || !appData.periodicUpdates)
8761                   return;
8762
8763                 programStats.depth = plylev;
8764                 programStats.time = time;
8765                 programStats.nodes = nodes;
8766                 programStats.moves_left = mvleft;
8767                 programStats.nr_moves = mvtot;
8768                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8769                 programStats.ok_to_send = 1;
8770                 programStats.movelist[0] = '\0';
8771
8772                 SendProgramStatsToFrontend( cps, &programStats );
8773
8774                 return;
8775
8776             } else if (strncmp(message,"++",2) == 0) {
8777                 /* Crafty 9.29+ outputs this */
8778                 programStats.got_fail = 2;
8779                 return;
8780
8781             } else if (strncmp(message,"--",2) == 0) {
8782                 /* Crafty 9.29+ outputs this */
8783                 programStats.got_fail = 1;
8784                 return;
8785
8786             } else if (thinkOutput[0] != NULLCHAR &&
8787                        strncmp(message, "    ", 4) == 0) {
8788                 unsigned message_len;
8789
8790                 p = message;
8791                 while (*p && *p == ' ') p++;
8792
8793                 message_len = strlen( p );
8794
8795                 /* [AS] Avoid buffer overflow */
8796                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8797                     strcat(thinkOutput, " ");
8798                     strcat(thinkOutput, p);
8799                 }
8800
8801                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8802                     strcat(programStats.movelist, " ");
8803                     strcat(programStats.movelist, p);
8804                 }
8805
8806                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8807                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8808                     DisplayMove(currentMove - 1);
8809                 }
8810                 return;
8811             }
8812         }
8813         else {
8814             buf1[0] = NULLCHAR;
8815
8816             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8817                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8818             {
8819                 ChessProgramStats cpstats;
8820
8821                 if (plyext != ' ' && plyext != '\t') {
8822                     time *= 100;
8823                 }
8824
8825                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8826                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8827                     curscore = -curscore;
8828                 }
8829
8830                 cpstats.depth = plylev;
8831                 cpstats.nodes = nodes;
8832                 cpstats.time = time;
8833                 cpstats.score = curscore;
8834                 cpstats.got_only_move = 0;
8835                 cpstats.movelist[0] = '\0';
8836
8837                 if (buf1[0] != NULLCHAR) {
8838                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8839                 }
8840
8841                 cpstats.ok_to_send = 0;
8842                 cpstats.line_is_book = 0;
8843                 cpstats.nr_moves = 0;
8844                 cpstats.moves_left = 0;
8845
8846                 SendProgramStatsToFrontend( cps, &cpstats );
8847             }
8848         }
8849     }
8850 }
8851
8852
8853 /* Parse a game score from the character string "game", and
8854    record it as the history of the current game.  The game
8855    score is NOT assumed to start from the standard position.
8856    The display is not updated in any way.
8857    */
8858 void
8859 ParseGameHistory (char *game)
8860 {
8861     ChessMove moveType;
8862     int fromX, fromY, toX, toY, boardIndex;
8863     char promoChar;
8864     char *p, *q;
8865     char buf[MSG_SIZ];
8866
8867     if (appData.debugMode)
8868       fprintf(debugFP, "Parsing game history: %s\n", game);
8869
8870     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8871     gameInfo.site = StrSave(appData.icsHost);
8872     gameInfo.date = PGNDate();
8873     gameInfo.round = StrSave("-");
8874
8875     /* Parse out names of players */
8876     while (*game == ' ') game++;
8877     p = buf;
8878     while (*game != ' ') *p++ = *game++;
8879     *p = NULLCHAR;
8880     gameInfo.white = StrSave(buf);
8881     while (*game == ' ') game++;
8882     p = buf;
8883     while (*game != ' ' && *game != '\n') *p++ = *game++;
8884     *p = NULLCHAR;
8885     gameInfo.black = StrSave(buf);
8886
8887     /* Parse moves */
8888     boardIndex = blackPlaysFirst ? 1 : 0;
8889     yynewstr(game);
8890     for (;;) {
8891         yyboardindex = boardIndex;
8892         moveType = (ChessMove) Myylex();
8893         switch (moveType) {
8894           case IllegalMove:             /* maybe suicide chess, etc. */
8895   if (appData.debugMode) {
8896     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8897     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8898     setbuf(debugFP, NULL);
8899   }
8900           case WhitePromotion:
8901           case BlackPromotion:
8902           case WhiteNonPromotion:
8903           case BlackNonPromotion:
8904           case NormalMove:
8905           case WhiteCapturesEnPassant:
8906           case BlackCapturesEnPassant:
8907           case WhiteKingSideCastle:
8908           case WhiteQueenSideCastle:
8909           case BlackKingSideCastle:
8910           case BlackQueenSideCastle:
8911           case WhiteKingSideCastleWild:
8912           case WhiteQueenSideCastleWild:
8913           case BlackKingSideCastleWild:
8914           case BlackQueenSideCastleWild:
8915           /* PUSH Fabien */
8916           case WhiteHSideCastleFR:
8917           case WhiteASideCastleFR:
8918           case BlackHSideCastleFR:
8919           case BlackASideCastleFR:
8920           /* POP Fabien */
8921             fromX = currentMoveString[0] - AAA;
8922             fromY = currentMoveString[1] - ONE;
8923             toX = currentMoveString[2] - AAA;
8924             toY = currentMoveString[3] - ONE;
8925             promoChar = currentMoveString[4];
8926             break;
8927           case WhiteDrop:
8928           case BlackDrop:
8929             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8930             fromX = moveType == WhiteDrop ?
8931               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8932             (int) CharToPiece(ToLower(currentMoveString[0]));
8933             fromY = DROP_RANK;
8934             toX = currentMoveString[2] - AAA;
8935             toY = currentMoveString[3] - ONE;
8936             promoChar = NULLCHAR;
8937             break;
8938           case AmbiguousMove:
8939             /* bug? */
8940             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8941   if (appData.debugMode) {
8942     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8943     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8944     setbuf(debugFP, NULL);
8945   }
8946             DisplayError(buf, 0);
8947             return;
8948           case ImpossibleMove:
8949             /* bug? */
8950             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8951   if (appData.debugMode) {
8952     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8953     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8954     setbuf(debugFP, NULL);
8955   }
8956             DisplayError(buf, 0);
8957             return;
8958           case EndOfFile:
8959             if (boardIndex < backwardMostMove) {
8960                 /* Oops, gap.  How did that happen? */
8961                 DisplayError(_("Gap in move list"), 0);
8962                 return;
8963             }
8964             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8965             if (boardIndex > forwardMostMove) {
8966                 forwardMostMove = boardIndex;
8967             }
8968             return;
8969           case ElapsedTime:
8970             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8971                 strcat(parseList[boardIndex-1], " ");
8972                 strcat(parseList[boardIndex-1], yy_text);
8973             }
8974             continue;
8975           case Comment:
8976           case PGNTag:
8977           case NAG:
8978           default:
8979             /* ignore */
8980             continue;
8981           case WhiteWins:
8982           case BlackWins:
8983           case GameIsDrawn:
8984           case GameUnfinished:
8985             if (gameMode == IcsExamining) {
8986                 if (boardIndex < backwardMostMove) {
8987                     /* Oops, gap.  How did that happen? */
8988                     return;
8989                 }
8990                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8991                 return;
8992             }
8993             gameInfo.result = moveType;
8994             p = strchr(yy_text, '{');
8995             if (p == NULL) p = strchr(yy_text, '(');
8996             if (p == NULL) {
8997                 p = yy_text;
8998                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8999             } else {
9000                 q = strchr(p, *p == '{' ? '}' : ')');
9001                 if (q != NULL) *q = NULLCHAR;
9002                 p++;
9003             }
9004             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9005             gameInfo.resultDetails = StrSave(p);
9006             continue;
9007         }
9008         if (boardIndex >= forwardMostMove &&
9009             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9010             backwardMostMove = blackPlaysFirst ? 1 : 0;
9011             return;
9012         }
9013         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9014                                  fromY, fromX, toY, toX, promoChar,
9015                                  parseList[boardIndex]);
9016         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9017         /* currentMoveString is set as a side-effect of yylex */
9018         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9019         strcat(moveList[boardIndex], "\n");
9020         boardIndex++;
9021         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9022         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9023           case MT_NONE:
9024           case MT_STALEMATE:
9025           default:
9026             break;
9027           case MT_CHECK:
9028             if(gameInfo.variant != VariantShogi)
9029                 strcat(parseList[boardIndex - 1], "+");
9030             break;
9031           case MT_CHECKMATE:
9032           case MT_STAINMATE:
9033             strcat(parseList[boardIndex - 1], "#");
9034             break;
9035         }
9036     }
9037 }
9038
9039
9040 /* Apply a move to the given board  */
9041 void
9042 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9043 {
9044   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9045   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9046
9047     /* [HGM] compute & store e.p. status and castling rights for new position */
9048     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9049
9050       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9051       oldEP = (signed char)board[EP_STATUS];
9052       board[EP_STATUS] = EP_NONE;
9053
9054   if (fromY == DROP_RANK) {
9055         /* must be first */
9056         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9057             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9058             return;
9059         }
9060         piece = board[toY][toX] = (ChessSquare) fromX;
9061   } else {
9062       int i;
9063
9064       if( board[toY][toX] != EmptySquare )
9065            board[EP_STATUS] = EP_CAPTURE;
9066
9067       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9068            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9069                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9070       } else
9071       if( board[fromY][fromX] == WhitePawn ) {
9072            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9073                board[EP_STATUS] = EP_PAWN_MOVE;
9074            if( toY-fromY==2) {
9075                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9076                         gameInfo.variant != VariantBerolina || toX < fromX)
9077                       board[EP_STATUS] = toX | berolina;
9078                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9079                         gameInfo.variant != VariantBerolina || toX > fromX)
9080                       board[EP_STATUS] = toX;
9081            }
9082       } else
9083       if( board[fromY][fromX] == BlackPawn ) {
9084            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9085                board[EP_STATUS] = EP_PAWN_MOVE;
9086            if( toY-fromY== -2) {
9087                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9088                         gameInfo.variant != VariantBerolina || toX < fromX)
9089                       board[EP_STATUS] = toX | berolina;
9090                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9091                         gameInfo.variant != VariantBerolina || toX > fromX)
9092                       board[EP_STATUS] = toX;
9093            }
9094        }
9095
9096        for(i=0; i<nrCastlingRights; i++) {
9097            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9098               board[CASTLING][i] == toX   && castlingRank[i] == toY
9099              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9100        }
9101
9102      if (fromX == toX && fromY == toY) return;
9103
9104      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9105      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9106      if(gameInfo.variant == VariantKnightmate)
9107          king += (int) WhiteUnicorn - (int) WhiteKing;
9108
9109     /* Code added by Tord: */
9110     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9111     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9112         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9113       board[fromY][fromX] = EmptySquare;
9114       board[toY][toX] = EmptySquare;
9115       if((toX > fromX) != (piece == WhiteRook)) {
9116         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9117       } else {
9118         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9119       }
9120     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9121                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9122       board[fromY][fromX] = EmptySquare;
9123       board[toY][toX] = EmptySquare;
9124       if((toX > fromX) != (piece == BlackRook)) {
9125         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9126       } else {
9127         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9128       }
9129     /* End of code added by Tord */
9130
9131     } else if (board[fromY][fromX] == king
9132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9133         && toY == fromY && toX > fromX+1) {
9134         board[fromY][fromX] = EmptySquare;
9135         board[toY][toX] = king;
9136         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9137         board[fromY][BOARD_RGHT-1] = EmptySquare;
9138     } else if (board[fromY][fromX] == king
9139         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9140                && toY == fromY && toX < fromX-1) {
9141         board[fromY][fromX] = EmptySquare;
9142         board[toY][toX] = king;
9143         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9144         board[fromY][BOARD_LEFT] = EmptySquare;
9145     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9146                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9147                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9148                ) {
9149         /* white pawn promotion */
9150         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9151         if(gameInfo.variant==VariantBughouse ||
9152            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9153             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9154         board[fromY][fromX] = EmptySquare;
9155     } else if ((fromY >= BOARD_HEIGHT>>1)
9156                && (toX != fromX)
9157                && gameInfo.variant != VariantXiangqi
9158                && gameInfo.variant != VariantBerolina
9159                && (board[fromY][fromX] == WhitePawn)
9160                && (board[toY][toX] == EmptySquare)) {
9161         board[fromY][fromX] = EmptySquare;
9162         board[toY][toX] = WhitePawn;
9163         captured = board[toY - 1][toX];
9164         board[toY - 1][toX] = EmptySquare;
9165     } else if ((fromY == BOARD_HEIGHT-4)
9166                && (toX == fromX)
9167                && gameInfo.variant == VariantBerolina
9168                && (board[fromY][fromX] == WhitePawn)
9169                && (board[toY][toX] == EmptySquare)) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = WhitePawn;
9172         if(oldEP & EP_BEROLIN_A) {
9173                 captured = board[fromY][fromX-1];
9174                 board[fromY][fromX-1] = EmptySquare;
9175         }else{  captured = board[fromY][fromX+1];
9176                 board[fromY][fromX+1] = EmptySquare;
9177         }
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX > fromX+1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9184         board[fromY][BOARD_RGHT-1] = EmptySquare;
9185     } else if (board[fromY][fromX] == king
9186         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9187                && toY == fromY && toX < fromX-1) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = king;
9190         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9191         board[fromY][BOARD_LEFT] = EmptySquare;
9192     } else if (fromY == 7 && fromX == 3
9193                && board[fromY][fromX] == BlackKing
9194                && toY == 7 && toX == 5) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = BlackKing;
9197         board[fromY][7] = EmptySquare;
9198         board[toY][4] = BlackRook;
9199     } else if (fromY == 7 && fromX == 3
9200                && board[fromY][fromX] == BlackKing
9201                && toY == 7 && toX == 1) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = BlackKing;
9204         board[fromY][0] = EmptySquare;
9205         board[toY][2] = BlackRook;
9206     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9207                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9208                && toY < promoRank && promoChar
9209                ) {
9210         /* black pawn promotion */
9211         board[toY][toX] = CharToPiece(ToLower(promoChar));
9212         if(gameInfo.variant==VariantBughouse ||
9213            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9214             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9215         board[fromY][fromX] = EmptySquare;
9216     } else if ((fromY < BOARD_HEIGHT>>1)
9217                && (toX != fromX)
9218                && gameInfo.variant != VariantXiangqi
9219                && gameInfo.variant != VariantBerolina
9220                && (board[fromY][fromX] == BlackPawn)
9221                && (board[toY][toX] == EmptySquare)) {
9222         board[fromY][fromX] = EmptySquare;
9223         board[toY][toX] = BlackPawn;
9224         captured = board[toY + 1][toX];
9225         board[toY + 1][toX] = EmptySquare;
9226     } else if ((fromY == 3)
9227                && (toX == fromX)
9228                && gameInfo.variant == VariantBerolina
9229                && (board[fromY][fromX] == BlackPawn)
9230                && (board[toY][toX] == EmptySquare)) {
9231         board[fromY][fromX] = EmptySquare;
9232         board[toY][toX] = BlackPawn;
9233         if(oldEP & EP_BEROLIN_A) {
9234                 captured = board[fromY][fromX-1];
9235                 board[fromY][fromX-1] = EmptySquare;
9236         }else{  captured = board[fromY][fromX+1];
9237                 board[fromY][fromX+1] = EmptySquare;
9238         }
9239     } else {
9240         board[toY][toX] = board[fromY][fromX];
9241         board[fromY][fromX] = EmptySquare;
9242     }
9243   }
9244
9245     if (gameInfo.holdingsWidth != 0) {
9246
9247       /* !!A lot more code needs to be written to support holdings  */
9248       /* [HGM] OK, so I have written it. Holdings are stored in the */
9249       /* penultimate board files, so they are automaticlly stored   */
9250       /* in the game history.                                       */
9251       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9252                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9253         /* Delete from holdings, by decreasing count */
9254         /* and erasing image if necessary            */
9255         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9256         if(p < (int) BlackPawn) { /* white drop */
9257              p -= (int)WhitePawn;
9258                  p = PieceToNumber((ChessSquare)p);
9259              if(p >= gameInfo.holdingsSize) p = 0;
9260              if(--board[p][BOARD_WIDTH-2] <= 0)
9261                   board[p][BOARD_WIDTH-1] = EmptySquare;
9262              if((int)board[p][BOARD_WIDTH-2] < 0)
9263                         board[p][BOARD_WIDTH-2] = 0;
9264         } else {                  /* black drop */
9265              p -= (int)BlackPawn;
9266                  p = PieceToNumber((ChessSquare)p);
9267              if(p >= gameInfo.holdingsSize) p = 0;
9268              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9269                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9270              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9271                         board[BOARD_HEIGHT-1-p][1] = 0;
9272         }
9273       }
9274       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9275           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9276         /* [HGM] holdings: Add to holdings, if holdings exist */
9277         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9278                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9279                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9280         }
9281         p = (int) captured;
9282         if (p >= (int) BlackPawn) {
9283           p -= (int)BlackPawn;
9284           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9285                   /* in Shogi restore piece to its original  first */
9286                   captured = (ChessSquare) (DEMOTED captured);
9287                   p = DEMOTED p;
9288           }
9289           p = PieceToNumber((ChessSquare)p);
9290           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9291           board[p][BOARD_WIDTH-2]++;
9292           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9293         } else {
9294           p -= (int)WhitePawn;
9295           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9296                   captured = (ChessSquare) (DEMOTED captured);
9297                   p = DEMOTED p;
9298           }
9299           p = PieceToNumber((ChessSquare)p);
9300           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9301           board[BOARD_HEIGHT-1-p][1]++;
9302           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9303         }
9304       }
9305     } else if (gameInfo.variant == VariantAtomic) {
9306       if (captured != EmptySquare) {
9307         int y, x;
9308         for (y = toY-1; y <= toY+1; y++) {
9309           for (x = toX-1; x <= toX+1; x++) {
9310             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9311                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9312               board[y][x] = EmptySquare;
9313             }
9314           }
9315         }
9316         board[toY][toX] = EmptySquare;
9317       }
9318     }
9319     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9320         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9321     } else
9322     if(promoChar == '+') {
9323         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9324         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9325     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9326         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9327     }
9328     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9329                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9330         // [HGM] superchess: take promotion piece out of holdings
9331         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9332         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9333             if(!--board[k][BOARD_WIDTH-2])
9334                 board[k][BOARD_WIDTH-1] = EmptySquare;
9335         } else {
9336             if(!--board[BOARD_HEIGHT-1-k][1])
9337                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9338         }
9339     }
9340
9341 }
9342
9343 /* Updates forwardMostMove */
9344 void
9345 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9346 {
9347 //    forwardMostMove++; // [HGM] bare: moved downstream
9348
9349     (void) CoordsToAlgebraic(boards[forwardMostMove],
9350                              PosFlags(forwardMostMove),
9351                              fromY, fromX, toY, toX, promoChar,
9352                              parseList[forwardMostMove]);
9353
9354     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9355         int timeLeft; static int lastLoadFlag=0; int king, piece;
9356         piece = boards[forwardMostMove][fromY][fromX];
9357         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9358         if(gameInfo.variant == VariantKnightmate)
9359             king += (int) WhiteUnicorn - (int) WhiteKing;
9360         if(forwardMostMove == 0) {
9361             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9362                 fprintf(serverMoves, "%s;", UserName());
9363             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9364                 fprintf(serverMoves, "%s;", second.tidy);
9365             fprintf(serverMoves, "%s;", first.tidy);
9366             if(gameMode == MachinePlaysWhite)
9367                 fprintf(serverMoves, "%s;", UserName());
9368             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9369                 fprintf(serverMoves, "%s;", second.tidy);
9370         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9371         lastLoadFlag = loadFlag;
9372         // print base move
9373         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9374         // print castling suffix
9375         if( toY == fromY && piece == king ) {
9376             if(toX-fromX > 1)
9377                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9378             if(fromX-toX >1)
9379                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9380         }
9381         // e.p. suffix
9382         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9383              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9384              boards[forwardMostMove][toY][toX] == EmptySquare
9385              && fromX != toX && fromY != toY)
9386                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9387         // promotion suffix
9388         if(promoChar != NULLCHAR)
9389                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9390         if(!loadFlag) {
9391                 char buf[MOVE_LEN*2], *p; int len;
9392             fprintf(serverMoves, "/%d/%d",
9393                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9394             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9395             else                      timeLeft = blackTimeRemaining/1000;
9396             fprintf(serverMoves, "/%d", timeLeft);
9397                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9398                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9399                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9400             fprintf(serverMoves, "/%s", buf);
9401         }
9402         fflush(serverMoves);
9403     }
9404
9405     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9406         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9407       return;
9408     }
9409     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9410     if (commentList[forwardMostMove+1] != NULL) {
9411         free(commentList[forwardMostMove+1]);
9412         commentList[forwardMostMove+1] = NULL;
9413     }
9414     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9415     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9416     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9417     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9418     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9419     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9420     adjustedClock = FALSE;
9421     gameInfo.result = GameUnfinished;
9422     if (gameInfo.resultDetails != NULL) {
9423         free(gameInfo.resultDetails);
9424         gameInfo.resultDetails = NULL;
9425     }
9426     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9427                               moveList[forwardMostMove - 1]);
9428     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9429       case MT_NONE:
9430       case MT_STALEMATE:
9431       default:
9432         break;
9433       case MT_CHECK:
9434         if(gameInfo.variant != VariantShogi)
9435             strcat(parseList[forwardMostMove - 1], "+");
9436         break;
9437       case MT_CHECKMATE:
9438       case MT_STAINMATE:
9439         strcat(parseList[forwardMostMove - 1], "#");
9440         break;
9441     }
9442
9443 }
9444
9445 /* Updates currentMove if not pausing */
9446 void
9447 ShowMove (int fromX, int fromY, int toX, int toY)
9448 {
9449     int instant = (gameMode == PlayFromGameFile) ?
9450         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9451     if(appData.noGUI) return;
9452     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9453         if (!instant) {
9454             if (forwardMostMove == currentMove + 1) {
9455                 AnimateMove(boards[forwardMostMove - 1],
9456                             fromX, fromY, toX, toY);
9457             }
9458             if (appData.highlightLastMove) {
9459                 SetHighlights(fromX, fromY, toX, toY);
9460             }
9461         }
9462         currentMove = forwardMostMove;
9463     }
9464
9465     if (instant) return;
9466
9467     DisplayMove(currentMove - 1);
9468     DrawPosition(FALSE, boards[currentMove]);
9469     DisplayBothClocks();
9470     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9471 }
9472
9473 void
9474 SendEgtPath (ChessProgramState *cps)
9475 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9476         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9477
9478         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9479
9480         while(*p) {
9481             char c, *q = name+1, *r, *s;
9482
9483             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9484             while(*p && *p != ',') *q++ = *p++;
9485             *q++ = ':'; *q = 0;
9486             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9487                 strcmp(name, ",nalimov:") == 0 ) {
9488                 // take nalimov path from the menu-changeable option first, if it is defined
9489               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9490                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9491             } else
9492             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9493                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9494                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9495                 s = r = StrStr(s, ":") + 1; // beginning of path info
9496                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9497                 c = *r; *r = 0;             // temporarily null-terminate path info
9498                     *--q = 0;               // strip of trailig ':' from name
9499                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9500                 *r = c;
9501                 SendToProgram(buf,cps);     // send egtbpath command for this format
9502             }
9503             if(*p == ',') p++; // read away comma to position for next format name
9504         }
9505 }
9506
9507 void
9508 InitChessProgram (ChessProgramState *cps, int setup)
9509 /* setup needed to setup FRC opening position */
9510 {
9511     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9512     if (appData.noChessProgram) return;
9513     hintRequested = FALSE;
9514     bookRequested = FALSE;
9515
9516     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9517     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9518     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9519     if(cps->memSize) { /* [HGM] memory */
9520       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9521         SendToProgram(buf, cps);
9522     }
9523     SendEgtPath(cps); /* [HGM] EGT */
9524     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9525       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9526         SendToProgram(buf, cps);
9527     }
9528
9529     SendToProgram(cps->initString, cps);
9530     if (gameInfo.variant != VariantNormal &&
9531         gameInfo.variant != VariantLoadable
9532         /* [HGM] also send variant if board size non-standard */
9533         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9534                                             ) {
9535       char *v = VariantName(gameInfo.variant);
9536       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9537         /* [HGM] in protocol 1 we have to assume all variants valid */
9538         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9539         DisplayFatalError(buf, 0, 1);
9540         return;
9541       }
9542
9543       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9544       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantXiangqi )
9546            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantShogi )
9548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9549       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9551       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9552           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9554       if( gameInfo.variant == VariantCourier )
9555            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantSuper )
9557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9558       if( gameInfo.variant == VariantGreat )
9559            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9560       if( gameInfo.variant == VariantSChess )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9562       if( gameInfo.variant == VariantGrand )
9563            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9564
9565       if(overruled) {
9566         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9567                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9568            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9569            if(StrStr(cps->variants, b) == NULL) {
9570                // specific sized variant not known, check if general sizing allowed
9571                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9572                    if(StrStr(cps->variants, "boardsize") == NULL) {
9573                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9574                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9575                        DisplayFatalError(buf, 0, 1);
9576                        return;
9577                    }
9578                    /* [HGM] here we really should compare with the maximum supported board size */
9579                }
9580            }
9581       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9582       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9583       SendToProgram(buf, cps);
9584     }
9585     currentlyInitializedVariant = gameInfo.variant;
9586
9587     /* [HGM] send opening position in FRC to first engine */
9588     if(setup) {
9589           SendToProgram("force\n", cps);
9590           SendBoard(cps, 0);
9591           /* engine is now in force mode! Set flag to wake it up after first move. */
9592           setboardSpoiledMachineBlack = 1;
9593     }
9594
9595     if (cps->sendICS) {
9596       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9597       SendToProgram(buf, cps);
9598     }
9599     cps->maybeThinking = FALSE;
9600     cps->offeredDraw = 0;
9601     if (!appData.icsActive) {
9602         SendTimeControl(cps, movesPerSession, timeControl,
9603                         timeIncrement, appData.searchDepth,
9604                         searchTime);
9605     }
9606     if (appData.showThinking
9607         // [HGM] thinking: four options require thinking output to be sent
9608         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9609                                 ) {
9610         SendToProgram("post\n", cps);
9611     }
9612     SendToProgram("hard\n", cps);
9613     if (!appData.ponderNextMove) {
9614         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9615            it without being sure what state we are in first.  "hard"
9616            is not a toggle, so that one is OK.
9617          */
9618         SendToProgram("easy\n", cps);
9619     }
9620     if (cps->usePing) {
9621       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9622       SendToProgram(buf, cps);
9623     }
9624     cps->initDone = TRUE;
9625     ClearEngineOutputPane(cps == &second);
9626 }
9627
9628
9629 void
9630 StartChessProgram (ChessProgramState *cps)
9631 {
9632     char buf[MSG_SIZ];
9633     int err;
9634
9635     if (appData.noChessProgram) return;
9636     cps->initDone = FALSE;
9637
9638     if (strcmp(cps->host, "localhost") == 0) {
9639         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9640     } else if (*appData.remoteShell == NULLCHAR) {
9641         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9642     } else {
9643         if (*appData.remoteUser == NULLCHAR) {
9644           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9645                     cps->program);
9646         } else {
9647           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9648                     cps->host, appData.remoteUser, cps->program);
9649         }
9650         err = StartChildProcess(buf, "", &cps->pr);
9651     }
9652
9653     if (err != 0) {
9654       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9655         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9656         if(cps != &first) return;
9657         appData.noChessProgram = TRUE;
9658         ThawUI();
9659         SetNCPMode();
9660 //      DisplayFatalError(buf, err, 1);
9661 //      cps->pr = NoProc;
9662 //      cps->isr = NULL;
9663         return;
9664     }
9665
9666     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9667     if (cps->protocolVersion > 1) {
9668       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9669       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9670       cps->comboCnt = 0;  //                and values of combo boxes
9671       SendToProgram(buf, cps);
9672     } else {
9673       SendToProgram("xboard\n", cps);
9674     }
9675 }
9676
9677 void
9678 TwoMachinesEventIfReady P((void))
9679 {
9680   static int curMess = 0;
9681   if (first.lastPing != first.lastPong) {
9682     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9683     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9684     return;
9685   }
9686   if (second.lastPing != second.lastPong) {
9687     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9688     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9689     return;
9690   }
9691   DisplayMessage("", ""); curMess = 0;
9692   ThawUI();
9693   TwoMachinesEvent();
9694 }
9695
9696 char *
9697 MakeName (char *template)
9698 {
9699     time_t clock;
9700     struct tm *tm;
9701     static char buf[MSG_SIZ];
9702     char *p = buf;
9703     int i;
9704
9705     clock = time((time_t *)NULL);
9706     tm = localtime(&clock);
9707
9708     while(*p++ = *template++) if(p[-1] == '%') {
9709         switch(*template++) {
9710           case 0:   *p = 0; return buf;
9711           case 'Y': i = tm->tm_year+1900; break;
9712           case 'y': i = tm->tm_year-100; break;
9713           case 'M': i = tm->tm_mon+1; break;
9714           case 'd': i = tm->tm_mday; break;
9715           case 'h': i = tm->tm_hour; break;
9716           case 'm': i = tm->tm_min; break;
9717           case 's': i = tm->tm_sec; break;
9718           default:  i = 0;
9719         }
9720         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9721     }
9722     return buf;
9723 }
9724
9725 int
9726 CountPlayers (char *p)
9727 {
9728     int n = 0;
9729     while(p = strchr(p, '\n')) p++, n++; // count participants
9730     return n;
9731 }
9732
9733 FILE *
9734 WriteTourneyFile (char *results, FILE *f)
9735 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9736     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9737     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9738         // create a file with tournament description
9739         fprintf(f, "-participants {%s}\n", appData.participants);
9740         fprintf(f, "-seedBase %d\n", appData.seedBase);
9741         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9742         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9743         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9744         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9745         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9746         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9747         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9748         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9749         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9750         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9751         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9752         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9753         if(searchTime > 0)
9754                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9755         else {
9756                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9757                 fprintf(f, "-tc %s\n", appData.timeControl);
9758                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9759         }
9760         fprintf(f, "-results \"%s\"\n", results);
9761     }
9762     return f;
9763 }
9764
9765 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9766
9767 void
9768 Substitute (char *participants, int expunge)
9769 {
9770     int i, changed, changes=0, nPlayers=0;
9771     char *p, *q, *r, buf[MSG_SIZ];
9772     if(participants == NULL) return;
9773     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9774     r = p = participants; q = appData.participants;
9775     while(*p && *p == *q) {
9776         if(*p == '\n') r = p+1, nPlayers++;
9777         p++; q++;
9778     }
9779     if(*p) { // difference
9780         while(*p && *p++ != '\n');
9781         while(*q && *q++ != '\n');
9782       changed = nPlayers;
9783         changes = 1 + (strcmp(p, q) != 0);
9784     }
9785     if(changes == 1) { // a single engine mnemonic was changed
9786         q = r; while(*q) nPlayers += (*q++ == '\n');
9787         p = buf; while(*r && (*p = *r++) != '\n') p++;
9788         *p = NULLCHAR;
9789         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9790         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9791         if(mnemonic[i]) { // The substitute is valid
9792             FILE *f;
9793             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9794                 flock(fileno(f), LOCK_EX);
9795                 ParseArgsFromFile(f);
9796                 fseek(f, 0, SEEK_SET);
9797                 FREE(appData.participants); appData.participants = participants;
9798                 if(expunge) { // erase results of replaced engine
9799                     int len = strlen(appData.results), w, b, dummy;
9800                     for(i=0; i<len; i++) {
9801                         Pairing(i, nPlayers, &w, &b, &dummy);
9802                         if((w == changed || b == changed) && appData.results[i] == '*') {
9803                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9804                             fclose(f);
9805                             return;
9806                         }
9807                     }
9808                     for(i=0; i<len; i++) {
9809                         Pairing(i, nPlayers, &w, &b, &dummy);
9810                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9811                     }
9812                 }
9813                 WriteTourneyFile(appData.results, f);
9814                 fclose(f); // release lock
9815                 return;
9816             }
9817         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9818     }
9819     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9820     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9821     free(participants);
9822     return;
9823 }
9824
9825 int
9826 CreateTourney (char *name)
9827 {
9828         FILE *f;
9829         if(matchMode && strcmp(name, appData.tourneyFile)) {
9830              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9831         }
9832         if(name[0] == NULLCHAR) {
9833             if(appData.participants[0])
9834                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9835             return 0;
9836         }
9837         f = fopen(name, "r");
9838         if(f) { // file exists
9839             ASSIGN(appData.tourneyFile, name);
9840             ParseArgsFromFile(f); // parse it
9841         } else {
9842             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9843             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9844                 DisplayError(_("Not enough participants"), 0);
9845                 return 0;
9846             }
9847             ASSIGN(appData.tourneyFile, name);
9848             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9849             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9850         }
9851         fclose(f);
9852         appData.noChessProgram = FALSE;
9853         appData.clockMode = TRUE;
9854         SetGNUMode();
9855         return 1;
9856 }
9857
9858 int
9859 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9860 {
9861     char buf[MSG_SIZ], *p, *q;
9862     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9863     skip = !all && group[0]; // if group requested, we start in skip mode
9864     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9865         p = names; q = buf; header = 0;
9866         while(*p && *p != '\n') *q++ = *p++;
9867         *q = 0;
9868         if(*p == '\n') p++;
9869         if(buf[0] == '#') {
9870             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9871             depth++; // we must be entering a new group
9872             if(all) continue; // suppress printing group headers when complete list requested
9873             header = 1;
9874             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9875         }
9876         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9877         if(engineList[i]) free(engineList[i]);
9878         engineList[i] = strdup(buf);
9879         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9880         if(engineMnemonic[i]) free(engineMnemonic[i]);
9881         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9882             strcat(buf, " (");
9883             sscanf(q + 8, "%s", buf + strlen(buf));
9884             strcat(buf, ")");
9885         }
9886         engineMnemonic[i] = strdup(buf);
9887         i++;
9888     }
9889     engineList[i] = engineMnemonic[i] = NULL;
9890     return i;
9891 }
9892
9893 // following implemented as macro to avoid type limitations
9894 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9895
9896 void
9897 SwapEngines (int n)
9898 {   // swap settings for first engine and other engine (so far only some selected options)
9899     int h;
9900     char *p;
9901     if(n == 0) return;
9902     SWAP(directory, p)
9903     SWAP(chessProgram, p)
9904     SWAP(isUCI, h)
9905     SWAP(hasOwnBookUCI, h)
9906     SWAP(protocolVersion, h)
9907     SWAP(reuse, h)
9908     SWAP(scoreIsAbsolute, h)
9909     SWAP(timeOdds, h)
9910     SWAP(logo, p)
9911     SWAP(pgnName, p)
9912     SWAP(pvSAN, h)
9913     SWAP(engOptions, p)
9914 }
9915
9916 int
9917 SetPlayer (int player, char *p)
9918 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9919     int i;
9920     char buf[MSG_SIZ], *engineName;
9921     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9922     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9923     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9924     if(mnemonic[i]) {
9925         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9926         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9927         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9928         ParseArgsFromString(buf);
9929     }
9930     free(engineName);
9931     return i;
9932 }
9933
9934 char *recentEngines;
9935
9936 void
9937 RecentEngineEvent (int nr)
9938 {
9939     int n;
9940 //    SwapEngines(1); // bump first to second
9941 //    ReplaceEngine(&second, 1); // and load it there
9942     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9943     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9944     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9945         ReplaceEngine(&first, 0);
9946         FloatToFront(&appData.recentEngineList, command[n]);
9947     }
9948 }
9949
9950 int
9951 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9952 {   // determine players from game number
9953     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9954
9955     if(appData.tourneyType == 0) {
9956         roundsPerCycle = (nPlayers - 1) | 1;
9957         pairingsPerRound = nPlayers / 2;
9958     } else if(appData.tourneyType > 0) {
9959         roundsPerCycle = nPlayers - appData.tourneyType;
9960         pairingsPerRound = appData.tourneyType;
9961     }
9962     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9963     gamesPerCycle = gamesPerRound * roundsPerCycle;
9964     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9965     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9966     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9967     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9968     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9969     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9970
9971     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9972     if(appData.roundSync) *syncInterval = gamesPerRound;
9973
9974     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9975
9976     if(appData.tourneyType == 0) {
9977         if(curPairing == (nPlayers-1)/2 ) {
9978             *whitePlayer = curRound;
9979             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9980         } else {
9981             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9982             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9983             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9984             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9985         }
9986     } else if(appData.tourneyType > 1) {
9987         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9988         *whitePlayer = curRound + appData.tourneyType;
9989     } else if(appData.tourneyType > 0) {
9990         *whitePlayer = curPairing;
9991         *blackPlayer = curRound + appData.tourneyType;
9992     }
9993
9994     // take care of white/black alternation per round. 
9995     // For cycles and games this is already taken care of by default, derived from matchGame!
9996     return curRound & 1;
9997 }
9998
9999 int
10000 NextTourneyGame (int nr, int *swapColors)
10001 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10002     char *p, *q;
10003     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10004     FILE *tf;
10005     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10006     tf = fopen(appData.tourneyFile, "r");
10007     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10008     ParseArgsFromFile(tf); fclose(tf);
10009     InitTimeControls(); // TC might be altered from tourney file
10010
10011     nPlayers = CountPlayers(appData.participants); // count participants
10012     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10013     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10014
10015     if(syncInterval) {
10016         p = q = appData.results;
10017         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10018         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10019             DisplayMessage(_("Waiting for other game(s)"),"");
10020             waitingForGame = TRUE;
10021             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10022             return 0;
10023         }
10024         waitingForGame = FALSE;
10025     }
10026
10027     if(appData.tourneyType < 0) {
10028         if(nr>=0 && !pairingReceived) {
10029             char buf[1<<16];
10030             if(pairing.pr == NoProc) {
10031                 if(!appData.pairingEngine[0]) {
10032                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10033                     return 0;
10034                 }
10035                 StartChessProgram(&pairing); // starts the pairing engine
10036             }
10037             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10038             SendToProgram(buf, &pairing);
10039             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10040             SendToProgram(buf, &pairing);
10041             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10042         }
10043         pairingReceived = 0;                              // ... so we continue here 
10044         *swapColors = 0;
10045         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10046         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10047         matchGame = 1; roundNr = nr / syncInterval + 1;
10048     }
10049
10050     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10051
10052     // redefine engines, engine dir, etc.
10053     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10054     if(first.pr == NoProc || nr < 0) {
10055       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10056       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10057     }
10058     if(second.pr == NoProc) {
10059       SwapEngines(1);
10060       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10061       SwapEngines(1);         // and make that valid for second engine by swapping
10062       InitEngine(&second, 1);
10063     }
10064     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10065     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10066     return 1;
10067 }
10068
10069 void
10070 NextMatchGame ()
10071 {   // performs game initialization that does not invoke engines, and then tries to start the game
10072     int res, firstWhite, swapColors = 0;
10073     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10074     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10075     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10076     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10077     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10078     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10079     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10080     Reset(FALSE, first.pr != NoProc);
10081     res = LoadGameOrPosition(matchGame); // setup game
10082     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10083     if(!res) return; // abort when bad game/pos file
10084     TwoMachinesEvent();
10085 }
10086
10087 void
10088 UserAdjudicationEvent (int result)
10089 {
10090     ChessMove gameResult = GameIsDrawn;
10091
10092     if( result > 0 ) {
10093         gameResult = WhiteWins;
10094     }
10095     else if( result < 0 ) {
10096         gameResult = BlackWins;
10097     }
10098
10099     if( gameMode == TwoMachinesPlay ) {
10100         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10101     }
10102 }
10103
10104
10105 // [HGM] save: calculate checksum of game to make games easily identifiable
10106 int
10107 StringCheckSum (char *s)
10108 {
10109         int i = 0;
10110         if(s==NULL) return 0;
10111         while(*s) i = i*259 + *s++;
10112         return i;
10113 }
10114
10115 int
10116 GameCheckSum ()
10117 {
10118         int i, sum=0;
10119         for(i=backwardMostMove; i<forwardMostMove; i++) {
10120                 sum += pvInfoList[i].depth;
10121                 sum += StringCheckSum(parseList[i]);
10122                 sum += StringCheckSum(commentList[i]);
10123                 sum *= 261;
10124         }
10125         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10126         return sum + StringCheckSum(commentList[i]);
10127 } // end of save patch
10128
10129 void
10130 GameEnds (ChessMove result, char *resultDetails, int whosays)
10131 {
10132     GameMode nextGameMode;
10133     int isIcsGame;
10134     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10135
10136     if(endingGame) return; /* [HGM] crash: forbid recursion */
10137     endingGame = 1;
10138     if(twoBoards) { // [HGM] dual: switch back to one board
10139         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10140         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10141     }
10142     if (appData.debugMode) {
10143       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10144               result, resultDetails ? resultDetails : "(null)", whosays);
10145     }
10146
10147     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10148
10149     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10150         /* If we are playing on ICS, the server decides when the
10151            game is over, but the engine can offer to draw, claim
10152            a draw, or resign.
10153          */
10154 #if ZIPPY
10155         if (appData.zippyPlay && first.initDone) {
10156             if (result == GameIsDrawn) {
10157                 /* In case draw still needs to be claimed */
10158                 SendToICS(ics_prefix);
10159                 SendToICS("draw\n");
10160             } else if (StrCaseStr(resultDetails, "resign")) {
10161                 SendToICS(ics_prefix);
10162                 SendToICS("resign\n");
10163             }
10164         }
10165 #endif
10166         endingGame = 0; /* [HGM] crash */
10167         return;
10168     }
10169
10170     /* If we're loading the game from a file, stop */
10171     if (whosays == GE_FILE) {
10172       (void) StopLoadGameTimer();
10173       gameFileFP = NULL;
10174     }
10175
10176     /* Cancel draw offers */
10177     first.offeredDraw = second.offeredDraw = 0;
10178
10179     /* If this is an ICS game, only ICS can really say it's done;
10180        if not, anyone can. */
10181     isIcsGame = (gameMode == IcsPlayingWhite ||
10182                  gameMode == IcsPlayingBlack ||
10183                  gameMode == IcsObserving    ||
10184                  gameMode == IcsExamining);
10185
10186     if (!isIcsGame || whosays == GE_ICS) {
10187         /* OK -- not an ICS game, or ICS said it was done */
10188         StopClocks();
10189         if (!isIcsGame && !appData.noChessProgram)
10190           SetUserThinkingEnables();
10191
10192         /* [HGM] if a machine claims the game end we verify this claim */
10193         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10194             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10195                 char claimer;
10196                 ChessMove trueResult = (ChessMove) -1;
10197
10198                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10199                                             first.twoMachinesColor[0] :
10200                                             second.twoMachinesColor[0] ;
10201
10202                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10204                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10205                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10206                 } else
10207                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10208                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10209                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10210                 } else
10211                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10212                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10213                 }
10214
10215                 // now verify win claims, but not in drop games, as we don't understand those yet
10216                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10217                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10218                     (result == WhiteWins && claimer == 'w' ||
10219                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10220                       if (appData.debugMode) {
10221                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10222                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10223                       }
10224                       if(result != trueResult) {
10225                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10226                               result = claimer == 'w' ? BlackWins : WhiteWins;
10227                               resultDetails = buf;
10228                       }
10229                 } else
10230                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10231                     && (forwardMostMove <= backwardMostMove ||
10232                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10233                         (claimer=='b')==(forwardMostMove&1))
10234                                                                                   ) {
10235                       /* [HGM] verify: draws that were not flagged are false claims */
10236                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10237                       result = claimer == 'w' ? BlackWins : WhiteWins;
10238                       resultDetails = buf;
10239                 }
10240                 /* (Claiming a loss is accepted no questions asked!) */
10241             }
10242             /* [HGM] bare: don't allow bare King to win */
10243             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10244                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10245                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10246                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10247                && result != GameIsDrawn)
10248             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10249                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10250                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10251                         if(p >= 0 && p <= (int)WhiteKing) k++;
10252                 }
10253                 if (appData.debugMode) {
10254                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10255                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10256                 }
10257                 if(k <= 1) {
10258                         result = GameIsDrawn;
10259                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10260                         resultDetails = buf;
10261                 }
10262             }
10263         }
10264
10265
10266         if(serverMoves != NULL && !loadFlag) { char c = '=';
10267             if(result==WhiteWins) c = '+';
10268             if(result==BlackWins) c = '-';
10269             if(resultDetails != NULL)
10270                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10271         }
10272         if (resultDetails != NULL) {
10273             gameInfo.result = result;
10274             gameInfo.resultDetails = StrSave(resultDetails);
10275
10276             /* display last move only if game was not loaded from file */
10277             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10278                 DisplayMove(currentMove - 1);
10279
10280             if (forwardMostMove != 0) {
10281                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10282                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10283                                                                 ) {
10284                     if (*appData.saveGameFile != NULLCHAR) {
10285                         SaveGameToFile(appData.saveGameFile, TRUE);
10286                     } else if (appData.autoSaveGames) {
10287                         AutoSaveGame();
10288                     }
10289                     if (*appData.savePositionFile != NULLCHAR) {
10290                         SavePositionToFile(appData.savePositionFile);
10291                     }
10292                 }
10293             }
10294
10295             /* Tell program how game ended in case it is learning */
10296             /* [HGM] Moved this to after saving the PGN, just in case */
10297             /* engine died and we got here through time loss. In that */
10298             /* case we will get a fatal error writing the pipe, which */
10299             /* would otherwise lose us the PGN.                       */
10300             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10301             /* output during GameEnds should never be fatal anymore   */
10302             if (gameMode == MachinePlaysWhite ||
10303                 gameMode == MachinePlaysBlack ||
10304                 gameMode == TwoMachinesPlay ||
10305                 gameMode == IcsPlayingWhite ||
10306                 gameMode == IcsPlayingBlack ||
10307                 gameMode == BeginningOfGame) {
10308                 char buf[MSG_SIZ];
10309                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10310                         resultDetails);
10311                 if (first.pr != NoProc) {
10312                     SendToProgram(buf, &first);
10313                 }
10314                 if (second.pr != NoProc &&
10315                     gameMode == TwoMachinesPlay) {
10316                     SendToProgram(buf, &second);
10317                 }
10318             }
10319         }
10320
10321         if (appData.icsActive) {
10322             if (appData.quietPlay &&
10323                 (gameMode == IcsPlayingWhite ||
10324                  gameMode == IcsPlayingBlack)) {
10325                 SendToICS(ics_prefix);
10326                 SendToICS("set shout 1\n");
10327             }
10328             nextGameMode = IcsIdle;
10329             ics_user_moved = FALSE;
10330             /* clean up premove.  It's ugly when the game has ended and the
10331              * premove highlights are still on the board.
10332              */
10333             if (gotPremove) {
10334               gotPremove = FALSE;
10335               ClearPremoveHighlights();
10336               DrawPosition(FALSE, boards[currentMove]);
10337             }
10338             if (whosays == GE_ICS) {
10339                 switch (result) {
10340                 case WhiteWins:
10341                     if (gameMode == IcsPlayingWhite)
10342                         PlayIcsWinSound();
10343                     else if(gameMode == IcsPlayingBlack)
10344                         PlayIcsLossSound();
10345                     break;
10346                 case BlackWins:
10347                     if (gameMode == IcsPlayingBlack)
10348                         PlayIcsWinSound();
10349                     else if(gameMode == IcsPlayingWhite)
10350                         PlayIcsLossSound();
10351                     break;
10352                 case GameIsDrawn:
10353                     PlayIcsDrawSound();
10354                     break;
10355                 default:
10356                     PlayIcsUnfinishedSound();
10357                 }
10358             }
10359         } else if (gameMode == EditGame ||
10360                    gameMode == PlayFromGameFile ||
10361                    gameMode == AnalyzeMode ||
10362                    gameMode == AnalyzeFile) {
10363             nextGameMode = gameMode;
10364         } else {
10365             nextGameMode = EndOfGame;
10366         }
10367         pausing = FALSE;
10368         ModeHighlight();
10369     } else {
10370         nextGameMode = gameMode;
10371     }
10372
10373     if (appData.noChessProgram) {
10374         gameMode = nextGameMode;
10375         ModeHighlight();
10376         endingGame = 0; /* [HGM] crash */
10377         return;
10378     }
10379
10380     if (first.reuse) {
10381         /* Put first chess program into idle state */
10382         if (first.pr != NoProc &&
10383             (gameMode == MachinePlaysWhite ||
10384              gameMode == MachinePlaysBlack ||
10385              gameMode == TwoMachinesPlay ||
10386              gameMode == IcsPlayingWhite ||
10387              gameMode == IcsPlayingBlack ||
10388              gameMode == BeginningOfGame)) {
10389             SendToProgram("force\n", &first);
10390             if (first.usePing) {
10391               char buf[MSG_SIZ];
10392               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10393               SendToProgram(buf, &first);
10394             }
10395         }
10396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10397         /* Kill off first chess program */
10398         if (first.isr != NULL)
10399           RemoveInputSource(first.isr);
10400         first.isr = NULL;
10401
10402         if (first.pr != NoProc) {
10403             ExitAnalyzeMode();
10404             DoSleep( appData.delayBeforeQuit );
10405             SendToProgram("quit\n", &first);
10406             DoSleep( appData.delayAfterQuit );
10407             DestroyChildProcess(first.pr, first.useSigterm);
10408         }
10409         first.pr = NoProc;
10410     }
10411     if (second.reuse) {
10412         /* Put second chess program into idle state */
10413         if (second.pr != NoProc &&
10414             gameMode == TwoMachinesPlay) {
10415             SendToProgram("force\n", &second);
10416             if (second.usePing) {
10417               char buf[MSG_SIZ];
10418               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10419               SendToProgram(buf, &second);
10420             }
10421         }
10422     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10423         /* Kill off second chess program */
10424         if (second.isr != NULL)
10425           RemoveInputSource(second.isr);
10426         second.isr = NULL;
10427
10428         if (second.pr != NoProc) {
10429             DoSleep( appData.delayBeforeQuit );
10430             SendToProgram("quit\n", &second);
10431             DoSleep( appData.delayAfterQuit );
10432             DestroyChildProcess(second.pr, second.useSigterm);
10433         }
10434         second.pr = NoProc;
10435     }
10436
10437     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10438         char resChar = '=';
10439         switch (result) {
10440         case WhiteWins:
10441           resChar = '+';
10442           if (first.twoMachinesColor[0] == 'w') {
10443             first.matchWins++;
10444           } else {
10445             second.matchWins++;
10446           }
10447           break;
10448         case BlackWins:
10449           resChar = '-';
10450           if (first.twoMachinesColor[0] == 'b') {
10451             first.matchWins++;
10452           } else {
10453             second.matchWins++;
10454           }
10455           break;
10456         case GameUnfinished:
10457           resChar = ' ';
10458         default:
10459           break;
10460         }
10461
10462         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10463         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10464             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10465             ReserveGame(nextGame, resChar); // sets nextGame
10466             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10467             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10468         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10469
10470         if (nextGame <= appData.matchGames && !abortMatch) {
10471             gameMode = nextGameMode;
10472             matchGame = nextGame; // this will be overruled in tourney mode!
10473             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10474             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10475             endingGame = 0; /* [HGM] crash */
10476             return;
10477         } else {
10478             gameMode = nextGameMode;
10479             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10480                      first.tidy, second.tidy,
10481                      first.matchWins, second.matchWins,
10482                      appData.matchGames - (first.matchWins + second.matchWins));
10483             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10484             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10485             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10486             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10487                 first.twoMachinesColor = "black\n";
10488                 second.twoMachinesColor = "white\n";
10489             } else {
10490                 first.twoMachinesColor = "white\n";
10491                 second.twoMachinesColor = "black\n";
10492             }
10493         }
10494     }
10495     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10496         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10497       ExitAnalyzeMode();
10498     gameMode = nextGameMode;
10499     ModeHighlight();
10500     endingGame = 0;  /* [HGM] crash */
10501     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10502         if(matchMode == TRUE) { // match through command line: exit with or without popup
10503             if(ranking) {
10504                 ToNrEvent(forwardMostMove);
10505                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10506                 else ExitEvent(0);
10507             } else DisplayFatalError(buf, 0, 0);
10508         } else { // match through menu; just stop, with or without popup
10509             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10510             ModeHighlight();
10511             if(ranking){
10512                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10513             } else DisplayNote(buf);
10514       }
10515       if(ranking) free(ranking);
10516     }
10517 }
10518
10519 /* Assumes program was just initialized (initString sent).
10520    Leaves program in force mode. */
10521 void
10522 FeedMovesToProgram (ChessProgramState *cps, int upto)
10523 {
10524     int i;
10525
10526     if (appData.debugMode)
10527       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10528               startedFromSetupPosition ? "position and " : "",
10529               backwardMostMove, upto, cps->which);
10530     if(currentlyInitializedVariant != gameInfo.variant) {
10531       char buf[MSG_SIZ];
10532         // [HGM] variantswitch: make engine aware of new variant
10533         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10534                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10535         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10536         SendToProgram(buf, cps);
10537         currentlyInitializedVariant = gameInfo.variant;
10538     }
10539     SendToProgram("force\n", cps);
10540     if (startedFromSetupPosition) {
10541         SendBoard(cps, backwardMostMove);
10542     if (appData.debugMode) {
10543         fprintf(debugFP, "feedMoves\n");
10544     }
10545     }
10546     for (i = backwardMostMove; i < upto; i++) {
10547         SendMoveToProgram(i, cps);
10548     }
10549 }
10550
10551
10552 int
10553 ResurrectChessProgram ()
10554 {
10555      /* The chess program may have exited.
10556         If so, restart it and feed it all the moves made so far. */
10557     static int doInit = 0;
10558
10559     if (appData.noChessProgram) return 1;
10560
10561     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10562         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10563         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10564         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10565     } else {
10566         if (first.pr != NoProc) return 1;
10567         StartChessProgram(&first);
10568     }
10569     InitChessProgram(&first, FALSE);
10570     FeedMovesToProgram(&first, currentMove);
10571
10572     if (!first.sendTime) {
10573         /* can't tell gnuchess what its clock should read,
10574            so we bow to its notion. */
10575         ResetClocks();
10576         timeRemaining[0][currentMove] = whiteTimeRemaining;
10577         timeRemaining[1][currentMove] = blackTimeRemaining;
10578     }
10579
10580     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10581                 appData.icsEngineAnalyze) && first.analysisSupport) {
10582       SendToProgram("analyze\n", &first);
10583       first.analyzing = TRUE;
10584     }
10585     return 1;
10586 }
10587
10588 /*
10589  * Button procedures
10590  */
10591 void
10592 Reset (int redraw, int init)
10593 {
10594     int i;
10595
10596     if (appData.debugMode) {
10597         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10598                 redraw, init, gameMode);
10599     }
10600     CleanupTail(); // [HGM] vari: delete any stored variations
10601     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10602     pausing = pauseExamInvalid = FALSE;
10603     startedFromSetupPosition = blackPlaysFirst = FALSE;
10604     firstMove = TRUE;
10605     whiteFlag = blackFlag = FALSE;
10606     userOfferedDraw = FALSE;
10607     hintRequested = bookRequested = FALSE;
10608     first.maybeThinking = FALSE;
10609     second.maybeThinking = FALSE;
10610     first.bookSuspend = FALSE; // [HGM] book
10611     second.bookSuspend = FALSE;
10612     thinkOutput[0] = NULLCHAR;
10613     lastHint[0] = NULLCHAR;
10614     ClearGameInfo(&gameInfo);
10615     gameInfo.variant = StringToVariant(appData.variant);
10616     ics_user_moved = ics_clock_paused = FALSE;
10617     ics_getting_history = H_FALSE;
10618     ics_gamenum = -1;
10619     white_holding[0] = black_holding[0] = NULLCHAR;
10620     ClearProgramStats();
10621     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10622
10623     ResetFrontEnd();
10624     ClearHighlights();
10625     flipView = appData.flipView;
10626     ClearPremoveHighlights();
10627     gotPremove = FALSE;
10628     alarmSounded = FALSE;
10629
10630     GameEnds(EndOfFile, NULL, GE_PLAYER);
10631     if(appData.serverMovesName != NULL) {
10632         /* [HGM] prepare to make moves file for broadcasting */
10633         clock_t t = clock();
10634         if(serverMoves != NULL) fclose(serverMoves);
10635         serverMoves = fopen(appData.serverMovesName, "r");
10636         if(serverMoves != NULL) {
10637             fclose(serverMoves);
10638             /* delay 15 sec before overwriting, so all clients can see end */
10639             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10640         }
10641         serverMoves = fopen(appData.serverMovesName, "w");
10642     }
10643
10644     ExitAnalyzeMode();
10645     gameMode = BeginningOfGame;
10646     ModeHighlight();
10647     if(appData.icsActive) gameInfo.variant = VariantNormal;
10648     currentMove = forwardMostMove = backwardMostMove = 0;
10649     MarkTargetSquares(1);
10650     InitPosition(redraw);
10651     for (i = 0; i < MAX_MOVES; i++) {
10652         if (commentList[i] != NULL) {
10653             free(commentList[i]);
10654             commentList[i] = NULL;
10655         }
10656     }
10657     ResetClocks();
10658     timeRemaining[0][0] = whiteTimeRemaining;
10659     timeRemaining[1][0] = blackTimeRemaining;
10660
10661     if (first.pr == NoProc) {
10662         StartChessProgram(&first);
10663     }
10664     if (init) {
10665             InitChessProgram(&first, startedFromSetupPosition);
10666     }
10667     DisplayTitle("");
10668     DisplayMessage("", "");
10669     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10670     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10671 }
10672
10673 void
10674 AutoPlayGameLoop ()
10675 {
10676     for (;;) {
10677         if (!AutoPlayOneMove())
10678           return;
10679         if (matchMode || appData.timeDelay == 0)
10680           continue;
10681         if (appData.timeDelay < 0)
10682           return;
10683         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10684         break;
10685     }
10686 }
10687
10688
10689 int
10690 AutoPlayOneMove ()
10691 {
10692     int fromX, fromY, toX, toY;
10693
10694     if (appData.debugMode) {
10695       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10696     }
10697
10698     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10699       return FALSE;
10700
10701     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10702       pvInfoList[currentMove].depth = programStats.depth;
10703       pvInfoList[currentMove].score = programStats.score;
10704       pvInfoList[currentMove].time  = 0;
10705       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10706     }
10707
10708     if (currentMove >= forwardMostMove) {
10709       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10710 //      gameMode = EndOfGame;
10711 //      ModeHighlight();
10712
10713       /* [AS] Clear current move marker at the end of a game */
10714       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10715
10716       return FALSE;
10717     }
10718
10719     toX = moveList[currentMove][2] - AAA;
10720     toY = moveList[currentMove][3] - ONE;
10721
10722     if (moveList[currentMove][1] == '@') {
10723         if (appData.highlightLastMove) {
10724             SetHighlights(-1, -1, toX, toY);
10725         }
10726     } else {
10727         fromX = moveList[currentMove][0] - AAA;
10728         fromY = moveList[currentMove][1] - ONE;
10729
10730         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10731
10732         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10733
10734         if (appData.highlightLastMove) {
10735             SetHighlights(fromX, fromY, toX, toY);
10736         }
10737     }
10738     DisplayMove(currentMove);
10739     SendMoveToProgram(currentMove++, &first);
10740     DisplayBothClocks();
10741     DrawPosition(FALSE, boards[currentMove]);
10742     // [HGM] PV info: always display, routine tests if empty
10743     DisplayComment(currentMove - 1, commentList[currentMove]);
10744     return TRUE;
10745 }
10746
10747
10748 int
10749 LoadGameOneMove (ChessMove readAhead)
10750 {
10751     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10752     char promoChar = NULLCHAR;
10753     ChessMove moveType;
10754     char move[MSG_SIZ];
10755     char *p, *q;
10756
10757     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10758         gameMode != AnalyzeMode && gameMode != Training) {
10759         gameFileFP = NULL;
10760         return FALSE;
10761     }
10762
10763     yyboardindex = forwardMostMove;
10764     if (readAhead != EndOfFile) {
10765       moveType = readAhead;
10766     } else {
10767       if (gameFileFP == NULL)
10768           return FALSE;
10769       moveType = (ChessMove) Myylex();
10770     }
10771
10772     done = FALSE;
10773     switch (moveType) {
10774       case Comment:
10775         if (appData.debugMode)
10776           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10777         p = yy_text;
10778
10779         /* append the comment but don't display it */
10780         AppendComment(currentMove, p, FALSE);
10781         return TRUE;
10782
10783       case WhiteCapturesEnPassant:
10784       case BlackCapturesEnPassant:
10785       case WhitePromotion:
10786       case BlackPromotion:
10787       case WhiteNonPromotion:
10788       case BlackNonPromotion:
10789       case NormalMove:
10790       case WhiteKingSideCastle:
10791       case WhiteQueenSideCastle:
10792       case BlackKingSideCastle:
10793       case BlackQueenSideCastle:
10794       case WhiteKingSideCastleWild:
10795       case WhiteQueenSideCastleWild:
10796       case BlackKingSideCastleWild:
10797       case BlackQueenSideCastleWild:
10798       /* PUSH Fabien */
10799       case WhiteHSideCastleFR:
10800       case WhiteASideCastleFR:
10801       case BlackHSideCastleFR:
10802       case BlackASideCastleFR:
10803       /* POP Fabien */
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10806         fromX = currentMoveString[0] - AAA;
10807         fromY = currentMoveString[1] - ONE;
10808         toX = currentMoveString[2] - AAA;
10809         toY = currentMoveString[3] - ONE;
10810         promoChar = currentMoveString[4];
10811         break;
10812
10813       case WhiteDrop:
10814       case BlackDrop:
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10817         fromX = moveType == WhiteDrop ?
10818           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10819         (int) CharToPiece(ToLower(currentMoveString[0]));
10820         fromY = DROP_RANK;
10821         toX = currentMoveString[2] - AAA;
10822         toY = currentMoveString[3] - ONE;
10823         break;
10824
10825       case WhiteWins:
10826       case BlackWins:
10827       case GameIsDrawn:
10828       case GameUnfinished:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10831         p = strchr(yy_text, '{');
10832         if (p == NULL) p = strchr(yy_text, '(');
10833         if (p == NULL) {
10834             p = yy_text;
10835             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10836         } else {
10837             q = strchr(p, *p == '{' ? '}' : ')');
10838             if (q != NULL) *q = NULLCHAR;
10839             p++;
10840         }
10841         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10842         GameEnds(moveType, p, GE_FILE);
10843         done = TRUE;
10844         if (cmailMsgLoaded) {
10845             ClearHighlights();
10846             flipView = WhiteOnMove(currentMove);
10847             if (moveType == GameUnfinished) flipView = !flipView;
10848             if (appData.debugMode)
10849               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10850         }
10851         break;
10852
10853       case EndOfFile:
10854         if (appData.debugMode)
10855           fprintf(debugFP, "Parser hit end of file\n");
10856         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10857           case MT_NONE:
10858           case MT_CHECK:
10859             break;
10860           case MT_CHECKMATE:
10861           case MT_STAINMATE:
10862             if (WhiteOnMove(currentMove)) {
10863                 GameEnds(BlackWins, "Black mates", GE_FILE);
10864             } else {
10865                 GameEnds(WhiteWins, "White mates", GE_FILE);
10866             }
10867             break;
10868           case MT_STALEMATE:
10869             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10870             break;
10871         }
10872         done = TRUE;
10873         break;
10874
10875       case MoveNumberOne:
10876         if (lastLoadGameStart == GNUChessGame) {
10877             /* GNUChessGames have numbers, but they aren't move numbers */
10878             if (appData.debugMode)
10879               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10880                       yy_text, (int) moveType);
10881             return LoadGameOneMove(EndOfFile); /* tail recursion */
10882         }
10883         /* else fall thru */
10884
10885       case XBoardGame:
10886       case GNUChessGame:
10887       case PGNTag:
10888         /* Reached start of next game in file */
10889         if (appData.debugMode)
10890           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10891         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10892           case MT_NONE:
10893           case MT_CHECK:
10894             break;
10895           case MT_CHECKMATE:
10896           case MT_STAINMATE:
10897             if (WhiteOnMove(currentMove)) {
10898                 GameEnds(BlackWins, "Black mates", GE_FILE);
10899             } else {
10900                 GameEnds(WhiteWins, "White mates", GE_FILE);
10901             }
10902             break;
10903           case MT_STALEMATE:
10904             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10905             break;
10906         }
10907         done = TRUE;
10908         break;
10909
10910       case PositionDiagram:     /* should not happen; ignore */
10911       case ElapsedTime:         /* ignore */
10912       case NAG:                 /* ignore */
10913         if (appData.debugMode)
10914           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10915                   yy_text, (int) moveType);
10916         return LoadGameOneMove(EndOfFile); /* tail recursion */
10917
10918       case IllegalMove:
10919         if (appData.testLegality) {
10920             if (appData.debugMode)
10921               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10922             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10923                     (forwardMostMove / 2) + 1,
10924                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10925             DisplayError(move, 0);
10926             done = TRUE;
10927         } else {
10928             if (appData.debugMode)
10929               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10930                       yy_text, currentMoveString);
10931             fromX = currentMoveString[0] - AAA;
10932             fromY = currentMoveString[1] - ONE;
10933             toX = currentMoveString[2] - AAA;
10934             toY = currentMoveString[3] - ONE;
10935             promoChar = currentMoveString[4];
10936         }
10937         break;
10938
10939       case AmbiguousMove:
10940         if (appData.debugMode)
10941           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10942         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10943                 (forwardMostMove / 2) + 1,
10944                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10945         DisplayError(move, 0);
10946         done = TRUE;
10947         break;
10948
10949       default:
10950       case ImpossibleMove:
10951         if (appData.debugMode)
10952           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10953         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10954                 (forwardMostMove / 2) + 1,
10955                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10956         DisplayError(move, 0);
10957         done = TRUE;
10958         break;
10959     }
10960
10961     if (done) {
10962         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10963             DrawPosition(FALSE, boards[currentMove]);
10964             DisplayBothClocks();
10965             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10966               DisplayComment(currentMove - 1, commentList[currentMove]);
10967         }
10968         (void) StopLoadGameTimer();
10969         gameFileFP = NULL;
10970         cmailOldMove = forwardMostMove;
10971         return FALSE;
10972     } else {
10973         /* currentMoveString is set as a side-effect of yylex */
10974
10975         thinkOutput[0] = NULLCHAR;
10976         MakeMove(fromX, fromY, toX, toY, promoChar);
10977         currentMove = forwardMostMove;
10978         return TRUE;
10979     }
10980 }
10981
10982 /* Load the nth game from the given file */
10983 int
10984 LoadGameFromFile (char *filename, int n, char *title, int useList)
10985 {
10986     FILE *f;
10987     char buf[MSG_SIZ];
10988
10989     if (strcmp(filename, "-") == 0) {
10990         f = stdin;
10991         title = "stdin";
10992     } else {
10993         f = fopen(filename, "rb");
10994         if (f == NULL) {
10995           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10996             DisplayError(buf, errno);
10997             return FALSE;
10998         }
10999     }
11000     if (fseek(f, 0, 0) == -1) {
11001         /* f is not seekable; probably a pipe */
11002         useList = FALSE;
11003     }
11004     if (useList && n == 0) {
11005         int error = GameListBuild(f);
11006         if (error) {
11007             DisplayError(_("Cannot build game list"), error);
11008         } else if (!ListEmpty(&gameList) &&
11009                    ((ListGame *) gameList.tailPred)->number > 1) {
11010             GameListPopUp(f, title);
11011             return TRUE;
11012         }
11013         GameListDestroy();
11014         n = 1;
11015     }
11016     if (n == 0) n = 1;
11017     return LoadGame(f, n, title, FALSE);
11018 }
11019
11020
11021 void
11022 MakeRegisteredMove ()
11023 {
11024     int fromX, fromY, toX, toY;
11025     char promoChar;
11026     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11027         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11028           case CMAIL_MOVE:
11029           case CMAIL_DRAW:
11030             if (appData.debugMode)
11031               fprintf(debugFP, "Restoring %s for game %d\n",
11032                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11033
11034             thinkOutput[0] = NULLCHAR;
11035             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11036             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11037             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11038             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11039             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11040             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11041             MakeMove(fromX, fromY, toX, toY, promoChar);
11042             ShowMove(fromX, fromY, toX, toY);
11043
11044             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11045               case MT_NONE:
11046               case MT_CHECK:
11047                 break;
11048
11049               case MT_CHECKMATE:
11050               case MT_STAINMATE:
11051                 if (WhiteOnMove(currentMove)) {
11052                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11053                 } else {
11054                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11055                 }
11056                 break;
11057
11058               case MT_STALEMATE:
11059                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11060                 break;
11061             }
11062
11063             break;
11064
11065           case CMAIL_RESIGN:
11066             if (WhiteOnMove(currentMove)) {
11067                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11068             } else {
11069                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11070             }
11071             break;
11072
11073           case CMAIL_ACCEPT:
11074             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11075             break;
11076
11077           default:
11078             break;
11079         }
11080     }
11081
11082     return;
11083 }
11084
11085 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11086 int
11087 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11088 {
11089     int retVal;
11090
11091     if (gameNumber > nCmailGames) {
11092         DisplayError(_("No more games in this message"), 0);
11093         return FALSE;
11094     }
11095     if (f == lastLoadGameFP) {
11096         int offset = gameNumber - lastLoadGameNumber;
11097         if (offset == 0) {
11098             cmailMsg[0] = NULLCHAR;
11099             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11100                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11101                 nCmailMovesRegistered--;
11102             }
11103             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11104             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11105                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11106             }
11107         } else {
11108             if (! RegisterMove()) return FALSE;
11109         }
11110     }
11111
11112     retVal = LoadGame(f, gameNumber, title, useList);
11113
11114     /* Make move registered during previous look at this game, if any */
11115     MakeRegisteredMove();
11116
11117     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11118         commentList[currentMove]
11119           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11120         DisplayComment(currentMove - 1, commentList[currentMove]);
11121     }
11122
11123     return retVal;
11124 }
11125
11126 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11127 int
11128 ReloadGame (int offset)
11129 {
11130     int gameNumber = lastLoadGameNumber + offset;
11131     if (lastLoadGameFP == NULL) {
11132         DisplayError(_("No game has been loaded yet"), 0);
11133         return FALSE;
11134     }
11135     if (gameNumber <= 0) {
11136         DisplayError(_("Can't back up any further"), 0);
11137         return FALSE;
11138     }
11139     if (cmailMsgLoaded) {
11140         return CmailLoadGame(lastLoadGameFP, gameNumber,
11141                              lastLoadGameTitle, lastLoadGameUseList);
11142     } else {
11143         return LoadGame(lastLoadGameFP, gameNumber,
11144                         lastLoadGameTitle, lastLoadGameUseList);
11145     }
11146 }
11147
11148 int keys[EmptySquare+1];
11149
11150 int
11151 PositionMatches (Board b1, Board b2)
11152 {
11153     int r, f, sum=0;
11154     switch(appData.searchMode) {
11155         case 1: return CompareWithRights(b1, b2);
11156         case 2:
11157             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11158                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11159             }
11160             return TRUE;
11161         case 3:
11162             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11163               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11164                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11165             }
11166             return sum==0;
11167         case 4:
11168             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11169                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11170             }
11171             return sum==0;
11172     }
11173     return TRUE;
11174 }
11175
11176 #define Q_PROMO  4
11177 #define Q_EP     3
11178 #define Q_BCASTL 2
11179 #define Q_WCASTL 1
11180
11181 int pieceList[256], quickBoard[256];
11182 ChessSquare pieceType[256] = { EmptySquare };
11183 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11184 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11185 int soughtTotal, turn;
11186 Boolean epOK, flipSearch;
11187
11188 typedef struct {
11189     unsigned char piece, to;
11190 } Move;
11191
11192 #define DSIZE (250000)
11193
11194 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11195 Move *moveDatabase = initialSpace;
11196 unsigned int movePtr, dataSize = DSIZE;
11197
11198 int
11199 MakePieceList (Board board, int *counts)
11200 {
11201     int r, f, n=Q_PROMO, total=0;
11202     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11203     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11204         int sq = f + (r<<4);
11205         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11206             quickBoard[sq] = ++n;
11207             pieceList[n] = sq;
11208             pieceType[n] = board[r][f];
11209             counts[board[r][f]]++;
11210             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11211             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11212             total++;
11213         }
11214     }
11215     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11216     return total;
11217 }
11218
11219 void
11220 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11221 {
11222     int sq = fromX + (fromY<<4);
11223     int piece = quickBoard[sq];
11224     quickBoard[sq] = 0;
11225     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11226     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11227         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11228         moveDatabase[movePtr++].piece = Q_WCASTL;
11229         quickBoard[sq] = piece;
11230         piece = quickBoard[from]; quickBoard[from] = 0;
11231         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11232     } else
11233     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11234         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11235         moveDatabase[movePtr++].piece = Q_BCASTL;
11236         quickBoard[sq] = piece;
11237         piece = quickBoard[from]; quickBoard[from] = 0;
11238         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11239     } else
11240     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11241         quickBoard[(fromY<<4)+toX] = 0;
11242         moveDatabase[movePtr].piece = Q_EP;
11243         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11244         moveDatabase[movePtr].to = sq;
11245     } else
11246     if(promoPiece != pieceType[piece]) {
11247         moveDatabase[movePtr++].piece = Q_PROMO;
11248         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11249     }
11250     moveDatabase[movePtr].piece = piece;
11251     quickBoard[sq] = piece;
11252     movePtr++;
11253 }
11254
11255 int
11256 PackGame (Board board)
11257 {
11258     Move *newSpace = NULL;
11259     moveDatabase[movePtr].piece = 0; // terminate previous game
11260     if(movePtr > dataSize) {
11261         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11262         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11263         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11264         if(newSpace) {
11265             int i;
11266             Move *p = moveDatabase, *q = newSpace;
11267             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11268             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11269             moveDatabase = newSpace;
11270         } else { // calloc failed, we must be out of memory. Too bad...
11271             dataSize = 0; // prevent calloc events for all subsequent games
11272             return 0;     // and signal this one isn't cached
11273         }
11274     }
11275     movePtr++;
11276     MakePieceList(board, counts);
11277     return movePtr;
11278 }
11279
11280 int
11281 QuickCompare (Board board, int *minCounts, int *maxCounts)
11282 {   // compare according to search mode
11283     int r, f;
11284     switch(appData.searchMode)
11285     {
11286       case 1: // exact position match
11287         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11288         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11289             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11290         }
11291         break;
11292       case 2: // can have extra material on empty squares
11293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11294             if(board[r][f] == EmptySquare) continue;
11295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11296         }
11297         break;
11298       case 3: // material with exact Pawn structure
11299         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11300             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11301             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11302         } // fall through to material comparison
11303       case 4: // exact material
11304         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11305         break;
11306       case 6: // material range with given imbalance
11307         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11308         // fall through to range comparison
11309       case 5: // material range
11310         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11311     }
11312     return TRUE;
11313 }
11314
11315 int
11316 QuickScan (Board board, Move *move)
11317 {   // reconstruct game,and compare all positions in it
11318     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11319     do {
11320         int piece = move->piece;
11321         int to = move->to, from = pieceList[piece];
11322         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11323           if(!piece) return -1;
11324           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11325             piece = (++move)->piece;
11326             from = pieceList[piece];
11327             counts[pieceType[piece]]--;
11328             pieceType[piece] = (ChessSquare) move->to;
11329             counts[move->to]++;
11330           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11331             counts[pieceType[quickBoard[to]]]--;
11332             quickBoard[to] = 0; total--;
11333             move++;
11334             continue;
11335           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11336             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11337             from  = pieceList[piece]; // so this must be King
11338             quickBoard[from] = 0;
11339             quickBoard[to] = piece;
11340             pieceList[piece] = to;
11341             move++;
11342             continue;
11343           }
11344         }
11345         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11346         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11347         quickBoard[from] = 0;
11348         quickBoard[to] = piece;
11349         pieceList[piece] = to;
11350         cnt++; turn ^= 3;
11351         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11352            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11353            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11354                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11355           ) {
11356             static int lastCounts[EmptySquare+1];
11357             int i;
11358             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11359             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11360         } else stretch = 0;
11361         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11362         move++;
11363     } while(1);
11364 }
11365
11366 void
11367 InitSearch ()
11368 {
11369     int r, f;
11370     flipSearch = FALSE;
11371     CopyBoard(soughtBoard, boards[currentMove]);
11372     soughtTotal = MakePieceList(soughtBoard, maxSought);
11373     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11374     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11375     CopyBoard(reverseBoard, boards[currentMove]);
11376     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11377         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11378         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11379         reverseBoard[r][f] = piece;
11380     }
11381     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11382     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11383     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11384                  || (boards[currentMove][CASTLING][2] == NoRights || 
11385                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11386                  && (boards[currentMove][CASTLING][5] == NoRights || 
11387                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11388       ) {
11389         flipSearch = TRUE;
11390         CopyBoard(flipBoard, soughtBoard);
11391         CopyBoard(rotateBoard, reverseBoard);
11392         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11393             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11394             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11395         }
11396     }
11397     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11398     if(appData.searchMode >= 5) {
11399         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11400         MakePieceList(soughtBoard, minSought);
11401         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11402     }
11403     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11404         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11405 }
11406
11407 GameInfo dummyInfo;
11408
11409 int
11410 GameContainsPosition (FILE *f, ListGame *lg)
11411 {
11412     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11413     int fromX, fromY, toX, toY;
11414     char promoChar;
11415     static int initDone=FALSE;
11416
11417     // weed out games based on numerical tag comparison
11418     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11419     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11420     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11421     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11422     if(!initDone) {
11423         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11424         initDone = TRUE;
11425     }
11426     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11427     else CopyBoard(boards[scratch], initialPosition); // default start position
11428     if(lg->moves) {
11429         turn = btm + 1;
11430         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11431         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11432     }
11433     if(btm) plyNr++;
11434     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11435     fseek(f, lg->offset, 0);
11436     yynewfile(f);
11437     while(1) {
11438         yyboardindex = scratch;
11439         quickFlag = plyNr+1;
11440         next = Myylex();
11441         quickFlag = 0;
11442         switch(next) {
11443             case PGNTag:
11444                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11445             default:
11446                 continue;
11447
11448             case XBoardGame:
11449             case GNUChessGame:
11450                 if(plyNr) return -1; // after we have seen moves, this is for new game
11451               continue;
11452
11453             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11454             case ImpossibleMove:
11455             case WhiteWins: // game ends here with these four
11456             case BlackWins:
11457             case GameIsDrawn:
11458             case GameUnfinished:
11459                 return -1;
11460
11461             case IllegalMove:
11462                 if(appData.testLegality) return -1;
11463             case WhiteCapturesEnPassant:
11464             case BlackCapturesEnPassant:
11465             case WhitePromotion:
11466             case BlackPromotion:
11467             case WhiteNonPromotion:
11468             case BlackNonPromotion:
11469             case NormalMove:
11470             case WhiteKingSideCastle:
11471             case WhiteQueenSideCastle:
11472             case BlackKingSideCastle:
11473             case BlackQueenSideCastle:
11474             case WhiteKingSideCastleWild:
11475             case WhiteQueenSideCastleWild:
11476             case BlackKingSideCastleWild:
11477             case BlackQueenSideCastleWild:
11478             case WhiteHSideCastleFR:
11479             case WhiteASideCastleFR:
11480             case BlackHSideCastleFR:
11481             case BlackASideCastleFR:
11482                 fromX = currentMoveString[0] - AAA;
11483                 fromY = currentMoveString[1] - ONE;
11484                 toX = currentMoveString[2] - AAA;
11485                 toY = currentMoveString[3] - ONE;
11486                 promoChar = currentMoveString[4];
11487                 break;
11488             case WhiteDrop:
11489             case BlackDrop:
11490                 fromX = next == WhiteDrop ?
11491                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11492                   (int) CharToPiece(ToLower(currentMoveString[0]));
11493                 fromY = DROP_RANK;
11494                 toX = currentMoveString[2] - AAA;
11495                 toY = currentMoveString[3] - ONE;
11496                 promoChar = 0;
11497                 break;
11498         }
11499         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11500         plyNr++;
11501         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11502         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11503         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11504         if(appData.findMirror) {
11505             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11506             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11507         }
11508     }
11509 }
11510
11511 /* Load the nth game from open file f */
11512 int
11513 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11514 {
11515     ChessMove cm;
11516     char buf[MSG_SIZ];
11517     int gn = gameNumber;
11518     ListGame *lg = NULL;
11519     int numPGNTags = 0;
11520     int err, pos = -1;
11521     GameMode oldGameMode;
11522     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11523
11524     if (appData.debugMode)
11525         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11526
11527     if (gameMode == Training )
11528         SetTrainingModeOff();
11529
11530     oldGameMode = gameMode;
11531     if (gameMode != BeginningOfGame) {
11532       Reset(FALSE, TRUE);
11533     }
11534
11535     gameFileFP = f;
11536     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11537         fclose(lastLoadGameFP);
11538     }
11539
11540     if (useList) {
11541         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11542
11543         if (lg) {
11544             fseek(f, lg->offset, 0);
11545             GameListHighlight(gameNumber);
11546             pos = lg->position;
11547             gn = 1;
11548         }
11549         else {
11550             DisplayError(_("Game number out of range"), 0);
11551             return FALSE;
11552         }
11553     } else {
11554         GameListDestroy();
11555         if (fseek(f, 0, 0) == -1) {
11556             if (f == lastLoadGameFP ?
11557                 gameNumber == lastLoadGameNumber + 1 :
11558                 gameNumber == 1) {
11559                 gn = 1;
11560             } else {
11561                 DisplayError(_("Can't seek on game file"), 0);
11562                 return FALSE;
11563             }
11564         }
11565     }
11566     lastLoadGameFP = f;
11567     lastLoadGameNumber = gameNumber;
11568     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11569     lastLoadGameUseList = useList;
11570
11571     yynewfile(f);
11572
11573     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11574       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11575                 lg->gameInfo.black);
11576             DisplayTitle(buf);
11577     } else if (*title != NULLCHAR) {
11578         if (gameNumber > 1) {
11579           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11580             DisplayTitle(buf);
11581         } else {
11582             DisplayTitle(title);
11583         }
11584     }
11585
11586     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11587         gameMode = PlayFromGameFile;
11588         ModeHighlight();
11589     }
11590
11591     currentMove = forwardMostMove = backwardMostMove = 0;
11592     CopyBoard(boards[0], initialPosition);
11593     StopClocks();
11594
11595     /*
11596      * Skip the first gn-1 games in the file.
11597      * Also skip over anything that precedes an identifiable
11598      * start of game marker, to avoid being confused by
11599      * garbage at the start of the file.  Currently
11600      * recognized start of game markers are the move number "1",
11601      * the pattern "gnuchess .* game", the pattern
11602      * "^[#;%] [^ ]* game file", and a PGN tag block.
11603      * A game that starts with one of the latter two patterns
11604      * will also have a move number 1, possibly
11605      * following a position diagram.
11606      * 5-4-02: Let's try being more lenient and allowing a game to
11607      * start with an unnumbered move.  Does that break anything?
11608      */
11609     cm = lastLoadGameStart = EndOfFile;
11610     while (gn > 0) {
11611         yyboardindex = forwardMostMove;
11612         cm = (ChessMove) Myylex();
11613         switch (cm) {
11614           case EndOfFile:
11615             if (cmailMsgLoaded) {
11616                 nCmailGames = CMAIL_MAX_GAMES - gn;
11617             } else {
11618                 Reset(TRUE, TRUE);
11619                 DisplayError(_("Game not found in file"), 0);
11620             }
11621             return FALSE;
11622
11623           case GNUChessGame:
11624           case XBoardGame:
11625             gn--;
11626             lastLoadGameStart = cm;
11627             break;
11628
11629           case MoveNumberOne:
11630             switch (lastLoadGameStart) {
11631               case GNUChessGame:
11632               case XBoardGame:
11633               case PGNTag:
11634                 break;
11635               case MoveNumberOne:
11636               case EndOfFile:
11637                 gn--;           /* count this game */
11638                 lastLoadGameStart = cm;
11639                 break;
11640               default:
11641                 /* impossible */
11642                 break;
11643             }
11644             break;
11645
11646           case PGNTag:
11647             switch (lastLoadGameStart) {
11648               case GNUChessGame:
11649               case PGNTag:
11650               case MoveNumberOne:
11651               case EndOfFile:
11652                 gn--;           /* count this game */
11653                 lastLoadGameStart = cm;
11654                 break;
11655               case XBoardGame:
11656                 lastLoadGameStart = cm; /* game counted already */
11657                 break;
11658               default:
11659                 /* impossible */
11660                 break;
11661             }
11662             if (gn > 0) {
11663                 do {
11664                     yyboardindex = forwardMostMove;
11665                     cm = (ChessMove) Myylex();
11666                 } while (cm == PGNTag || cm == Comment);
11667             }
11668             break;
11669
11670           case WhiteWins:
11671           case BlackWins:
11672           case GameIsDrawn:
11673             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11674                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11675                     != CMAIL_OLD_RESULT) {
11676                     nCmailResults ++ ;
11677                     cmailResult[  CMAIL_MAX_GAMES
11678                                 - gn - 1] = CMAIL_OLD_RESULT;
11679                 }
11680             }
11681             break;
11682
11683           case NormalMove:
11684             /* Only a NormalMove can be at the start of a game
11685              * without a position diagram. */
11686             if (lastLoadGameStart == EndOfFile ) {
11687               gn--;
11688               lastLoadGameStart = MoveNumberOne;
11689             }
11690             break;
11691
11692           default:
11693             break;
11694         }
11695     }
11696
11697     if (appData.debugMode)
11698       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11699
11700     if (cm == XBoardGame) {
11701         /* Skip any header junk before position diagram and/or move 1 */
11702         for (;;) {
11703             yyboardindex = forwardMostMove;
11704             cm = (ChessMove) Myylex();
11705
11706             if (cm == EndOfFile ||
11707                 cm == GNUChessGame || cm == XBoardGame) {
11708                 /* Empty game; pretend end-of-file and handle later */
11709                 cm = EndOfFile;
11710                 break;
11711             }
11712
11713             if (cm == MoveNumberOne || cm == PositionDiagram ||
11714                 cm == PGNTag || cm == Comment)
11715               break;
11716         }
11717     } else if (cm == GNUChessGame) {
11718         if (gameInfo.event != NULL) {
11719             free(gameInfo.event);
11720         }
11721         gameInfo.event = StrSave(yy_text);
11722     }
11723
11724     startedFromSetupPosition = FALSE;
11725     while (cm == PGNTag) {
11726         if (appData.debugMode)
11727           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11728         err = ParsePGNTag(yy_text, &gameInfo);
11729         if (!err) numPGNTags++;
11730
11731         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11732         if(gameInfo.variant != oldVariant) {
11733             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11734             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11735             InitPosition(TRUE);
11736             oldVariant = gameInfo.variant;
11737             if (appData.debugMode)
11738               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11739         }
11740
11741
11742         if (gameInfo.fen != NULL) {
11743           Board initial_position;
11744           startedFromSetupPosition = TRUE;
11745           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11746             Reset(TRUE, TRUE);
11747             DisplayError(_("Bad FEN position in file"), 0);
11748             return FALSE;
11749           }
11750           CopyBoard(boards[0], initial_position);
11751           if (blackPlaysFirst) {
11752             currentMove = forwardMostMove = backwardMostMove = 1;
11753             CopyBoard(boards[1], initial_position);
11754             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11755             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11756             timeRemaining[0][1] = whiteTimeRemaining;
11757             timeRemaining[1][1] = blackTimeRemaining;
11758             if (commentList[0] != NULL) {
11759               commentList[1] = commentList[0];
11760               commentList[0] = NULL;
11761             }
11762           } else {
11763             currentMove = forwardMostMove = backwardMostMove = 0;
11764           }
11765           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11766           {   int i;
11767               initialRulePlies = FENrulePlies;
11768               for( i=0; i< nrCastlingRights; i++ )
11769                   initialRights[i] = initial_position[CASTLING][i];
11770           }
11771           yyboardindex = forwardMostMove;
11772           free(gameInfo.fen);
11773           gameInfo.fen = NULL;
11774         }
11775
11776         yyboardindex = forwardMostMove;
11777         cm = (ChessMove) Myylex();
11778
11779         /* Handle comments interspersed among the tags */
11780         while (cm == Comment) {
11781             char *p;
11782             if (appData.debugMode)
11783               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11784             p = yy_text;
11785             AppendComment(currentMove, p, FALSE);
11786             yyboardindex = forwardMostMove;
11787             cm = (ChessMove) Myylex();
11788         }
11789     }
11790
11791     /* don't rely on existence of Event tag since if game was
11792      * pasted from clipboard the Event tag may not exist
11793      */
11794     if (numPGNTags > 0){
11795         char *tags;
11796         if (gameInfo.variant == VariantNormal) {
11797           VariantClass v = StringToVariant(gameInfo.event);
11798           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11799           if(v < VariantShogi) gameInfo.variant = v;
11800         }
11801         if (!matchMode) {
11802           if( appData.autoDisplayTags ) {
11803             tags = PGNTags(&gameInfo);
11804             TagsPopUp(tags, CmailMsg());
11805             free(tags);
11806           }
11807         }
11808     } else {
11809         /* Make something up, but don't display it now */
11810         SetGameInfo();
11811         TagsPopDown();
11812     }
11813
11814     if (cm == PositionDiagram) {
11815         int i, j;
11816         char *p;
11817         Board initial_position;
11818
11819         if (appData.debugMode)
11820           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11821
11822         if (!startedFromSetupPosition) {
11823             p = yy_text;
11824             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11825               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11826                 switch (*p) {
11827                   case '{':
11828                   case '[':
11829                   case '-':
11830                   case ' ':
11831                   case '\t':
11832                   case '\n':
11833                   case '\r':
11834                     break;
11835                   default:
11836                     initial_position[i][j++] = CharToPiece(*p);
11837                     break;
11838                 }
11839             while (*p == ' ' || *p == '\t' ||
11840                    *p == '\n' || *p == '\r') p++;
11841
11842             if (strncmp(p, "black", strlen("black"))==0)
11843               blackPlaysFirst = TRUE;
11844             else
11845               blackPlaysFirst = FALSE;
11846             startedFromSetupPosition = TRUE;
11847
11848             CopyBoard(boards[0], initial_position);
11849             if (blackPlaysFirst) {
11850                 currentMove = forwardMostMove = backwardMostMove = 1;
11851                 CopyBoard(boards[1], initial_position);
11852                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11853                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11854                 timeRemaining[0][1] = whiteTimeRemaining;
11855                 timeRemaining[1][1] = blackTimeRemaining;
11856                 if (commentList[0] != NULL) {
11857                     commentList[1] = commentList[0];
11858                     commentList[0] = NULL;
11859                 }
11860             } else {
11861                 currentMove = forwardMostMove = backwardMostMove = 0;
11862             }
11863         }
11864         yyboardindex = forwardMostMove;
11865         cm = (ChessMove) Myylex();
11866     }
11867
11868     if (first.pr == NoProc) {
11869         StartChessProgram(&first);
11870     }
11871     InitChessProgram(&first, FALSE);
11872     SendToProgram("force\n", &first);
11873     if (startedFromSetupPosition) {
11874         SendBoard(&first, forwardMostMove);
11875     if (appData.debugMode) {
11876         fprintf(debugFP, "Load Game\n");
11877     }
11878         DisplayBothClocks();
11879     }
11880
11881     /* [HGM] server: flag to write setup moves in broadcast file as one */
11882     loadFlag = appData.suppressLoadMoves;
11883
11884     while (cm == Comment) {
11885         char *p;
11886         if (appData.debugMode)
11887           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11888         p = yy_text;
11889         AppendComment(currentMove, p, FALSE);
11890         yyboardindex = forwardMostMove;
11891         cm = (ChessMove) Myylex();
11892     }
11893
11894     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11895         cm == WhiteWins || cm == BlackWins ||
11896         cm == GameIsDrawn || cm == GameUnfinished) {
11897         DisplayMessage("", _("No moves in game"));
11898         if (cmailMsgLoaded) {
11899             if (appData.debugMode)
11900               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11901             ClearHighlights();
11902             flipView = FALSE;
11903         }
11904         DrawPosition(FALSE, boards[currentMove]);
11905         DisplayBothClocks();
11906         gameMode = EditGame;
11907         ModeHighlight();
11908         gameFileFP = NULL;
11909         cmailOldMove = 0;
11910         return TRUE;
11911     }
11912
11913     // [HGM] PV info: routine tests if comment empty
11914     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11915         DisplayComment(currentMove - 1, commentList[currentMove]);
11916     }
11917     if (!matchMode && appData.timeDelay != 0)
11918       DrawPosition(FALSE, boards[currentMove]);
11919
11920     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11921       programStats.ok_to_send = 1;
11922     }
11923
11924     /* if the first token after the PGN tags is a move
11925      * and not move number 1, retrieve it from the parser
11926      */
11927     if (cm != MoveNumberOne)
11928         LoadGameOneMove(cm);
11929
11930     /* load the remaining moves from the file */
11931     while (LoadGameOneMove(EndOfFile)) {
11932       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11933       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11934     }
11935
11936     /* rewind to the start of the game */
11937     currentMove = backwardMostMove;
11938
11939     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11940
11941     if (oldGameMode == AnalyzeFile ||
11942         oldGameMode == AnalyzeMode) {
11943       AnalyzeFileEvent();
11944     }
11945
11946     if (!matchMode && pos >= 0) {
11947         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11948     } else
11949     if (matchMode || appData.timeDelay == 0) {
11950       ToEndEvent();
11951     } else if (appData.timeDelay > 0) {
11952       AutoPlayGameLoop();
11953     }
11954
11955     if (appData.debugMode)
11956         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11957
11958     loadFlag = 0; /* [HGM] true game starts */
11959     return TRUE;
11960 }
11961
11962 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11963 int
11964 ReloadPosition (int offset)
11965 {
11966     int positionNumber = lastLoadPositionNumber + offset;
11967     if (lastLoadPositionFP == NULL) {
11968         DisplayError(_("No position has been loaded yet"), 0);
11969         return FALSE;
11970     }
11971     if (positionNumber <= 0) {
11972         DisplayError(_("Can't back up any further"), 0);
11973         return FALSE;
11974     }
11975     return LoadPosition(lastLoadPositionFP, positionNumber,
11976                         lastLoadPositionTitle);
11977 }
11978
11979 /* Load the nth position from the given file */
11980 int
11981 LoadPositionFromFile (char *filename, int n, char *title)
11982 {
11983     FILE *f;
11984     char buf[MSG_SIZ];
11985
11986     if (strcmp(filename, "-") == 0) {
11987         return LoadPosition(stdin, n, "stdin");
11988     } else {
11989         f = fopen(filename, "rb");
11990         if (f == NULL) {
11991             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11992             DisplayError(buf, errno);
11993             return FALSE;
11994         } else {
11995             return LoadPosition(f, n, title);
11996         }
11997     }
11998 }
11999
12000 /* Load the nth position from the given open file, and close it */
12001 int
12002 LoadPosition (FILE *f, int positionNumber, char *title)
12003 {
12004     char *p, line[MSG_SIZ];
12005     Board initial_position;
12006     int i, j, fenMode, pn;
12007
12008     if (gameMode == Training )
12009         SetTrainingModeOff();
12010
12011     if (gameMode != BeginningOfGame) {
12012         Reset(FALSE, TRUE);
12013     }
12014     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12015         fclose(lastLoadPositionFP);
12016     }
12017     if (positionNumber == 0) positionNumber = 1;
12018     lastLoadPositionFP = f;
12019     lastLoadPositionNumber = positionNumber;
12020     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12021     if (first.pr == NoProc && !appData.noChessProgram) {
12022       StartChessProgram(&first);
12023       InitChessProgram(&first, FALSE);
12024     }
12025     pn = positionNumber;
12026     if (positionNumber < 0) {
12027         /* Negative position number means to seek to that byte offset */
12028         if (fseek(f, -positionNumber, 0) == -1) {
12029             DisplayError(_("Can't seek on position file"), 0);
12030             return FALSE;
12031         };
12032         pn = 1;
12033     } else {
12034         if (fseek(f, 0, 0) == -1) {
12035             if (f == lastLoadPositionFP ?
12036                 positionNumber == lastLoadPositionNumber + 1 :
12037                 positionNumber == 1) {
12038                 pn = 1;
12039             } else {
12040                 DisplayError(_("Can't seek on position file"), 0);
12041                 return FALSE;
12042             }
12043         }
12044     }
12045     /* See if this file is FEN or old-style xboard */
12046     if (fgets(line, MSG_SIZ, f) == NULL) {
12047         DisplayError(_("Position not found in file"), 0);
12048         return FALSE;
12049     }
12050     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12051     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12052
12053     if (pn >= 2) {
12054         if (fenMode || line[0] == '#') pn--;
12055         while (pn > 0) {
12056             /* skip positions before number pn */
12057             if (fgets(line, MSG_SIZ, f) == NULL) {
12058                 Reset(TRUE, TRUE);
12059                 DisplayError(_("Position not found in file"), 0);
12060                 return FALSE;
12061             }
12062             if (fenMode || line[0] == '#') pn--;
12063         }
12064     }
12065
12066     if (fenMode) {
12067         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12068             DisplayError(_("Bad FEN position in file"), 0);
12069             return FALSE;
12070         }
12071     } else {
12072         (void) fgets(line, MSG_SIZ, f);
12073         (void) fgets(line, MSG_SIZ, f);
12074
12075         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12076             (void) fgets(line, MSG_SIZ, f);
12077             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12078                 if (*p == ' ')
12079                   continue;
12080                 initial_position[i][j++] = CharToPiece(*p);
12081             }
12082         }
12083
12084         blackPlaysFirst = FALSE;
12085         if (!feof(f)) {
12086             (void) fgets(line, MSG_SIZ, f);
12087             if (strncmp(line, "black", strlen("black"))==0)
12088               blackPlaysFirst = TRUE;
12089         }
12090     }
12091     startedFromSetupPosition = TRUE;
12092
12093     CopyBoard(boards[0], initial_position);
12094     if (blackPlaysFirst) {
12095         currentMove = forwardMostMove = backwardMostMove = 1;
12096         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12097         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12098         CopyBoard(boards[1], initial_position);
12099         DisplayMessage("", _("Black to play"));
12100     } else {
12101         currentMove = forwardMostMove = backwardMostMove = 0;
12102         DisplayMessage("", _("White to play"));
12103     }
12104     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12105     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12106         SendToProgram("force\n", &first);
12107         SendBoard(&first, forwardMostMove);
12108     }
12109     if (appData.debugMode) {
12110 int i, j;
12111   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12112   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12113         fprintf(debugFP, "Load Position\n");
12114     }
12115
12116     if (positionNumber > 1) {
12117       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12118         DisplayTitle(line);
12119     } else {
12120         DisplayTitle(title);
12121     }
12122     gameMode = EditGame;
12123     ModeHighlight();
12124     ResetClocks();
12125     timeRemaining[0][1] = whiteTimeRemaining;
12126     timeRemaining[1][1] = blackTimeRemaining;
12127     DrawPosition(FALSE, boards[currentMove]);
12128
12129     return TRUE;
12130 }
12131
12132
12133 void
12134 CopyPlayerNameIntoFileName (char **dest, char *src)
12135 {
12136     while (*src != NULLCHAR && *src != ',') {
12137         if (*src == ' ') {
12138             *(*dest)++ = '_';
12139             src++;
12140         } else {
12141             *(*dest)++ = *src++;
12142         }
12143     }
12144 }
12145
12146 char *
12147 DefaultFileName (char *ext)
12148 {
12149     static char def[MSG_SIZ];
12150     char *p;
12151
12152     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12153         p = def;
12154         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12155         *p++ = '-';
12156         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12157         *p++ = '.';
12158         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12159     } else {
12160         def[0] = NULLCHAR;
12161     }
12162     return def;
12163 }
12164
12165 /* Save the current game to the given file */
12166 int
12167 SaveGameToFile (char *filename, int append)
12168 {
12169     FILE *f;
12170     char buf[MSG_SIZ];
12171     int result, i, t,tot=0;
12172
12173     if (strcmp(filename, "-") == 0) {
12174         return SaveGame(stdout, 0, NULL);
12175     } else {
12176         for(i=0; i<10; i++) { // upto 10 tries
12177              f = fopen(filename, append ? "a" : "w");
12178              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12179              if(f || errno != 13) break;
12180              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12181              tot += t;
12182         }
12183         if (f == NULL) {
12184             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12185             DisplayError(buf, errno);
12186             return FALSE;
12187         } else {
12188             safeStrCpy(buf, lastMsg, MSG_SIZ);
12189             DisplayMessage(_("Waiting for access to save file"), "");
12190             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12191             DisplayMessage(_("Saving game"), "");
12192             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12193             result = SaveGame(f, 0, NULL);
12194             DisplayMessage(buf, "");
12195             return result;
12196         }
12197     }
12198 }
12199
12200 char *
12201 SavePart (char *str)
12202 {
12203     static char buf[MSG_SIZ];
12204     char *p;
12205
12206     p = strchr(str, ' ');
12207     if (p == NULL) return str;
12208     strncpy(buf, str, p - str);
12209     buf[p - str] = NULLCHAR;
12210     return buf;
12211 }
12212
12213 #define PGN_MAX_LINE 75
12214
12215 #define PGN_SIDE_WHITE  0
12216 #define PGN_SIDE_BLACK  1
12217
12218 static int
12219 FindFirstMoveOutOfBook (int side)
12220 {
12221     int result = -1;
12222
12223     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12224         int index = backwardMostMove;
12225         int has_book_hit = 0;
12226
12227         if( (index % 2) != side ) {
12228             index++;
12229         }
12230
12231         while( index < forwardMostMove ) {
12232             /* Check to see if engine is in book */
12233             int depth = pvInfoList[index].depth;
12234             int score = pvInfoList[index].score;
12235             int in_book = 0;
12236
12237             if( depth <= 2 ) {
12238                 in_book = 1;
12239             }
12240             else if( score == 0 && depth == 63 ) {
12241                 in_book = 1; /* Zappa */
12242             }
12243             else if( score == 2 && depth == 99 ) {
12244                 in_book = 1; /* Abrok */
12245             }
12246
12247             has_book_hit += in_book;
12248
12249             if( ! in_book ) {
12250                 result = index;
12251
12252                 break;
12253             }
12254
12255             index += 2;
12256         }
12257     }
12258
12259     return result;
12260 }
12261
12262 void
12263 GetOutOfBookInfo (char * buf)
12264 {
12265     int oob[2];
12266     int i;
12267     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12268
12269     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12270     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12271
12272     *buf = '\0';
12273
12274     if( oob[0] >= 0 || oob[1] >= 0 ) {
12275         for( i=0; i<2; i++ ) {
12276             int idx = oob[i];
12277
12278             if( idx >= 0 ) {
12279                 if( i > 0 && oob[0] >= 0 ) {
12280                     strcat( buf, "   " );
12281                 }
12282
12283                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12284                 sprintf( buf+strlen(buf), "%s%.2f",
12285                     pvInfoList[idx].score >= 0 ? "+" : "",
12286                     pvInfoList[idx].score / 100.0 );
12287             }
12288         }
12289     }
12290 }
12291
12292 /* Save game in PGN style and close the file */
12293 int
12294 SaveGamePGN (FILE *f)
12295 {
12296     int i, offset, linelen, newblock;
12297     time_t tm;
12298 //    char *movetext;
12299     char numtext[32];
12300     int movelen, numlen, blank;
12301     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12302
12303     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12304
12305     tm = time((time_t *) NULL);
12306
12307     PrintPGNTags(f, &gameInfo);
12308
12309     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12310
12311     if (backwardMostMove > 0 || startedFromSetupPosition) {
12312         char *fen = PositionToFEN(backwardMostMove, NULL);
12313         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12314         fprintf(f, "\n{--------------\n");
12315         PrintPosition(f, backwardMostMove);
12316         fprintf(f, "--------------}\n");
12317         free(fen);
12318     }
12319     else {
12320         /* [AS] Out of book annotation */
12321         if( appData.saveOutOfBookInfo ) {
12322             char buf[64];
12323
12324             GetOutOfBookInfo( buf );
12325
12326             if( buf[0] != '\0' ) {
12327                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12328             }
12329         }
12330
12331         fprintf(f, "\n");
12332     }
12333
12334     i = backwardMostMove;
12335     linelen = 0;
12336     newblock = TRUE;
12337
12338     while (i < forwardMostMove) {
12339         /* Print comments preceding this move */
12340         if (commentList[i] != NULL) {
12341             if (linelen > 0) fprintf(f, "\n");
12342             fprintf(f, "%s", commentList[i]);
12343             linelen = 0;
12344             newblock = TRUE;
12345         }
12346
12347         /* Format move number */
12348         if ((i % 2) == 0)
12349           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12350         else
12351           if (newblock)
12352             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12353           else
12354             numtext[0] = NULLCHAR;
12355
12356         numlen = strlen(numtext);
12357         newblock = FALSE;
12358
12359         /* Print move number */
12360         blank = linelen > 0 && numlen > 0;
12361         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12362             fprintf(f, "\n");
12363             linelen = 0;
12364             blank = 0;
12365         }
12366         if (blank) {
12367             fprintf(f, " ");
12368             linelen++;
12369         }
12370         fprintf(f, "%s", numtext);
12371         linelen += numlen;
12372
12373         /* Get move */
12374         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12375         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12376
12377         /* Print move */
12378         blank = linelen > 0 && movelen > 0;
12379         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12380             fprintf(f, "\n");
12381             linelen = 0;
12382             blank = 0;
12383         }
12384         if (blank) {
12385             fprintf(f, " ");
12386             linelen++;
12387         }
12388         fprintf(f, "%s", move_buffer);
12389         linelen += movelen;
12390
12391         /* [AS] Add PV info if present */
12392         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12393             /* [HGM] add time */
12394             char buf[MSG_SIZ]; int seconds;
12395
12396             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12397
12398             if( seconds <= 0)
12399               buf[0] = 0;
12400             else
12401               if( seconds < 30 )
12402                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12403               else
12404                 {
12405                   seconds = (seconds + 4)/10; // round to full seconds
12406                   if( seconds < 60 )
12407                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12408                   else
12409                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12410                 }
12411
12412             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12413                       pvInfoList[i].score >= 0 ? "+" : "",
12414                       pvInfoList[i].score / 100.0,
12415                       pvInfoList[i].depth,
12416                       buf );
12417
12418             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12419
12420             /* Print score/depth */
12421             blank = linelen > 0 && movelen > 0;
12422             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12423                 fprintf(f, "\n");
12424                 linelen = 0;
12425                 blank = 0;
12426             }
12427             if (blank) {
12428                 fprintf(f, " ");
12429                 linelen++;
12430             }
12431             fprintf(f, "%s", move_buffer);
12432             linelen += movelen;
12433         }
12434
12435         i++;
12436     }
12437
12438     /* Start a new line */
12439     if (linelen > 0) fprintf(f, "\n");
12440
12441     /* Print comments after last move */
12442     if (commentList[i] != NULL) {
12443         fprintf(f, "%s\n", commentList[i]);
12444     }
12445
12446     /* Print result */
12447     if (gameInfo.resultDetails != NULL &&
12448         gameInfo.resultDetails[0] != NULLCHAR) {
12449         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12450                 PGNResult(gameInfo.result));
12451     } else {
12452         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12453     }
12454
12455     fclose(f);
12456     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12457     return TRUE;
12458 }
12459
12460 /* Save game in old style and close the file */
12461 int
12462 SaveGameOldStyle (FILE *f)
12463 {
12464     int i, offset;
12465     time_t tm;
12466
12467     tm = time((time_t *) NULL);
12468
12469     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12470     PrintOpponents(f);
12471
12472     if (backwardMostMove > 0 || startedFromSetupPosition) {
12473         fprintf(f, "\n[--------------\n");
12474         PrintPosition(f, backwardMostMove);
12475         fprintf(f, "--------------]\n");
12476     } else {
12477         fprintf(f, "\n");
12478     }
12479
12480     i = backwardMostMove;
12481     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12482
12483     while (i < forwardMostMove) {
12484         if (commentList[i] != NULL) {
12485             fprintf(f, "[%s]\n", commentList[i]);
12486         }
12487
12488         if ((i % 2) == 1) {
12489             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12490             i++;
12491         } else {
12492             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12493             i++;
12494             if (commentList[i] != NULL) {
12495                 fprintf(f, "\n");
12496                 continue;
12497             }
12498             if (i >= forwardMostMove) {
12499                 fprintf(f, "\n");
12500                 break;
12501             }
12502             fprintf(f, "%s\n", parseList[i]);
12503             i++;
12504         }
12505     }
12506
12507     if (commentList[i] != NULL) {
12508         fprintf(f, "[%s]\n", commentList[i]);
12509     }
12510
12511     /* This isn't really the old style, but it's close enough */
12512     if (gameInfo.resultDetails != NULL &&
12513         gameInfo.resultDetails[0] != NULLCHAR) {
12514         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12515                 gameInfo.resultDetails);
12516     } else {
12517         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12518     }
12519
12520     fclose(f);
12521     return TRUE;
12522 }
12523
12524 /* Save the current game to open file f and close the file */
12525 int
12526 SaveGame (FILE *f, int dummy, char *dummy2)
12527 {
12528     if (gameMode == EditPosition) EditPositionDone(TRUE);
12529     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12530     if (appData.oldSaveStyle)
12531       return SaveGameOldStyle(f);
12532     else
12533       return SaveGamePGN(f);
12534 }
12535
12536 /* Save the current position to the given file */
12537 int
12538 SavePositionToFile (char *filename)
12539 {
12540     FILE *f;
12541     char buf[MSG_SIZ];
12542
12543     if (strcmp(filename, "-") == 0) {
12544         return SavePosition(stdout, 0, NULL);
12545     } else {
12546         f = fopen(filename, "a");
12547         if (f == NULL) {
12548             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12549             DisplayError(buf, errno);
12550             return FALSE;
12551         } else {
12552             safeStrCpy(buf, lastMsg, MSG_SIZ);
12553             DisplayMessage(_("Waiting for access to save file"), "");
12554             flock(fileno(f), LOCK_EX); // [HGM] lock
12555             DisplayMessage(_("Saving position"), "");
12556             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12557             SavePosition(f, 0, NULL);
12558             DisplayMessage(buf, "");
12559             return TRUE;
12560         }
12561     }
12562 }
12563
12564 /* Save the current position to the given open file and close the file */
12565 int
12566 SavePosition (FILE *f, int dummy, char *dummy2)
12567 {
12568     time_t tm;
12569     char *fen;
12570
12571     if (gameMode == EditPosition) EditPositionDone(TRUE);
12572     if (appData.oldSaveStyle) {
12573         tm = time((time_t *) NULL);
12574
12575         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12576         PrintOpponents(f);
12577         fprintf(f, "[--------------\n");
12578         PrintPosition(f, currentMove);
12579         fprintf(f, "--------------]\n");
12580     } else {
12581         fen = PositionToFEN(currentMove, NULL);
12582         fprintf(f, "%s\n", fen);
12583         free(fen);
12584     }
12585     fclose(f);
12586     return TRUE;
12587 }
12588
12589 void
12590 ReloadCmailMsgEvent (int unregister)
12591 {
12592 #if !WIN32
12593     static char *inFilename = NULL;
12594     static char *outFilename;
12595     int i;
12596     struct stat inbuf, outbuf;
12597     int status;
12598
12599     /* Any registered moves are unregistered if unregister is set, */
12600     /* i.e. invoked by the signal handler */
12601     if (unregister) {
12602         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12603             cmailMoveRegistered[i] = FALSE;
12604             if (cmailCommentList[i] != NULL) {
12605                 free(cmailCommentList[i]);
12606                 cmailCommentList[i] = NULL;
12607             }
12608         }
12609         nCmailMovesRegistered = 0;
12610     }
12611
12612     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12613         cmailResult[i] = CMAIL_NOT_RESULT;
12614     }
12615     nCmailResults = 0;
12616
12617     if (inFilename == NULL) {
12618         /* Because the filenames are static they only get malloced once  */
12619         /* and they never get freed                                      */
12620         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12621         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12622
12623         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12624         sprintf(outFilename, "%s.out", appData.cmailGameName);
12625     }
12626
12627     status = stat(outFilename, &outbuf);
12628     if (status < 0) {
12629         cmailMailedMove = FALSE;
12630     } else {
12631         status = stat(inFilename, &inbuf);
12632         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12633     }
12634
12635     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12636        counts the games, notes how each one terminated, etc.
12637
12638        It would be nice to remove this kludge and instead gather all
12639        the information while building the game list.  (And to keep it
12640        in the game list nodes instead of having a bunch of fixed-size
12641        parallel arrays.)  Note this will require getting each game's
12642        termination from the PGN tags, as the game list builder does
12643        not process the game moves.  --mann
12644        */
12645     cmailMsgLoaded = TRUE;
12646     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12647
12648     /* Load first game in the file or popup game menu */
12649     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12650
12651 #endif /* !WIN32 */
12652     return;
12653 }
12654
12655 int
12656 RegisterMove ()
12657 {
12658     FILE *f;
12659     char string[MSG_SIZ];
12660
12661     if (   cmailMailedMove
12662         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12663         return TRUE;            /* Allow free viewing  */
12664     }
12665
12666     /* Unregister move to ensure that we don't leave RegisterMove        */
12667     /* with the move registered when the conditions for registering no   */
12668     /* longer hold                                                       */
12669     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12670         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12671         nCmailMovesRegistered --;
12672
12673         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12674           {
12675               free(cmailCommentList[lastLoadGameNumber - 1]);
12676               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12677           }
12678     }
12679
12680     if (cmailOldMove == -1) {
12681         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12682         return FALSE;
12683     }
12684
12685     if (currentMove > cmailOldMove + 1) {
12686         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12687         return FALSE;
12688     }
12689
12690     if (currentMove < cmailOldMove) {
12691         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12692         return FALSE;
12693     }
12694
12695     if (forwardMostMove > currentMove) {
12696         /* Silently truncate extra moves */
12697         TruncateGame();
12698     }
12699
12700     if (   (currentMove == cmailOldMove + 1)
12701         || (   (currentMove == cmailOldMove)
12702             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12703                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12704         if (gameInfo.result != GameUnfinished) {
12705             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12706         }
12707
12708         if (commentList[currentMove] != NULL) {
12709             cmailCommentList[lastLoadGameNumber - 1]
12710               = StrSave(commentList[currentMove]);
12711         }
12712         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12713
12714         if (appData.debugMode)
12715           fprintf(debugFP, "Saving %s for game %d\n",
12716                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12717
12718         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12719
12720         f = fopen(string, "w");
12721         if (appData.oldSaveStyle) {
12722             SaveGameOldStyle(f); /* also closes the file */
12723
12724             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12725             f = fopen(string, "w");
12726             SavePosition(f, 0, NULL); /* also closes the file */
12727         } else {
12728             fprintf(f, "{--------------\n");
12729             PrintPosition(f, currentMove);
12730             fprintf(f, "--------------}\n\n");
12731
12732             SaveGame(f, 0, NULL); /* also closes the file*/
12733         }
12734
12735         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12736         nCmailMovesRegistered ++;
12737     } else if (nCmailGames == 1) {
12738         DisplayError(_("You have not made a move yet"), 0);
12739         return FALSE;
12740     }
12741
12742     return TRUE;
12743 }
12744
12745 void
12746 MailMoveEvent ()
12747 {
12748 #if !WIN32
12749     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12750     FILE *commandOutput;
12751     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12752     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12753     int nBuffers;
12754     int i;
12755     int archived;
12756     char *arcDir;
12757
12758     if (! cmailMsgLoaded) {
12759         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12760         return;
12761     }
12762
12763     if (nCmailGames == nCmailResults) {
12764         DisplayError(_("No unfinished games"), 0);
12765         return;
12766     }
12767
12768 #if CMAIL_PROHIBIT_REMAIL
12769     if (cmailMailedMove) {
12770       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);
12771         DisplayError(msg, 0);
12772         return;
12773     }
12774 #endif
12775
12776     if (! (cmailMailedMove || RegisterMove())) return;
12777
12778     if (   cmailMailedMove
12779         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12780       snprintf(string, MSG_SIZ, partCommandString,
12781                appData.debugMode ? " -v" : "", appData.cmailGameName);
12782         commandOutput = popen(string, "r");
12783
12784         if (commandOutput == NULL) {
12785             DisplayError(_("Failed to invoke cmail"), 0);
12786         } else {
12787             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12788                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12789             }
12790             if (nBuffers > 1) {
12791                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12792                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12793                 nBytes = MSG_SIZ - 1;
12794             } else {
12795                 (void) memcpy(msg, buffer, nBytes);
12796             }
12797             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12798
12799             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12800                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12801
12802                 archived = TRUE;
12803                 for (i = 0; i < nCmailGames; i ++) {
12804                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12805                         archived = FALSE;
12806                     }
12807                 }
12808                 if (   archived
12809                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12810                         != NULL)) {
12811                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12812                            arcDir,
12813                            appData.cmailGameName,
12814                            gameInfo.date);
12815                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12816                     cmailMsgLoaded = FALSE;
12817                 }
12818             }
12819
12820             DisplayInformation(msg);
12821             pclose(commandOutput);
12822         }
12823     } else {
12824         if ((*cmailMsg) != '\0') {
12825             DisplayInformation(cmailMsg);
12826         }
12827     }
12828
12829     return;
12830 #endif /* !WIN32 */
12831 }
12832
12833 char *
12834 CmailMsg ()
12835 {
12836 #if WIN32
12837     return NULL;
12838 #else
12839     int  prependComma = 0;
12840     char number[5];
12841     char string[MSG_SIZ];       /* Space for game-list */
12842     int  i;
12843
12844     if (!cmailMsgLoaded) return "";
12845
12846     if (cmailMailedMove) {
12847       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12848     } else {
12849         /* Create a list of games left */
12850       snprintf(string, MSG_SIZ, "[");
12851         for (i = 0; i < nCmailGames; i ++) {
12852             if (! (   cmailMoveRegistered[i]
12853                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12854                 if (prependComma) {
12855                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12856                 } else {
12857                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12858                     prependComma = 1;
12859                 }
12860
12861                 strcat(string, number);
12862             }
12863         }
12864         strcat(string, "]");
12865
12866         if (nCmailMovesRegistered + nCmailResults == 0) {
12867             switch (nCmailGames) {
12868               case 1:
12869                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12870                 break;
12871
12872               case 2:
12873                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12874                 break;
12875
12876               default:
12877                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12878                          nCmailGames);
12879                 break;
12880             }
12881         } else {
12882             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12883               case 1:
12884                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12885                          string);
12886                 break;
12887
12888               case 0:
12889                 if (nCmailResults == nCmailGames) {
12890                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12891                 } else {
12892                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12893                 }
12894                 break;
12895
12896               default:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12898                          string);
12899             }
12900         }
12901     }
12902     return cmailMsg;
12903 #endif /* WIN32 */
12904 }
12905
12906 void
12907 ResetGameEvent ()
12908 {
12909     if (gameMode == Training)
12910       SetTrainingModeOff();
12911
12912     Reset(TRUE, TRUE);
12913     cmailMsgLoaded = FALSE;
12914     if (appData.icsActive) {
12915       SendToICS(ics_prefix);
12916       SendToICS("refresh\n");
12917     }
12918 }
12919
12920 void
12921 ExitEvent (int status)
12922 {
12923     exiting++;
12924     if (exiting > 2) {
12925       /* Give up on clean exit */
12926       exit(status);
12927     }
12928     if (exiting > 1) {
12929       /* Keep trying for clean exit */
12930       return;
12931     }
12932
12933     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12934
12935     if (telnetISR != NULL) {
12936       RemoveInputSource(telnetISR);
12937     }
12938     if (icsPR != NoProc) {
12939       DestroyChildProcess(icsPR, TRUE);
12940     }
12941
12942     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12943     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12944
12945     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12946     /* make sure this other one finishes before killing it!                  */
12947     if(endingGame) { int count = 0;
12948         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12949         while(endingGame && count++ < 10) DoSleep(1);
12950         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12951     }
12952
12953     /* Kill off chess programs */
12954     if (first.pr != NoProc) {
12955         ExitAnalyzeMode();
12956
12957         DoSleep( appData.delayBeforeQuit );
12958         SendToProgram("quit\n", &first);
12959         DoSleep( appData.delayAfterQuit );
12960         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12961     }
12962     if (second.pr != NoProc) {
12963         DoSleep( appData.delayBeforeQuit );
12964         SendToProgram("quit\n", &second);
12965         DoSleep( appData.delayAfterQuit );
12966         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12967     }
12968     if (first.isr != NULL) {
12969         RemoveInputSource(first.isr);
12970     }
12971     if (second.isr != NULL) {
12972         RemoveInputSource(second.isr);
12973     }
12974
12975     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12976     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12977
12978     ShutDownFrontEnd();
12979     exit(status);
12980 }
12981
12982 void
12983 PauseEvent ()
12984 {
12985     if (appData.debugMode)
12986         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12987     if (pausing) {
12988         pausing = FALSE;
12989         ModeHighlight();
12990         if (gameMode == MachinePlaysWhite ||
12991             gameMode == MachinePlaysBlack) {
12992             StartClocks();
12993         } else {
12994             DisplayBothClocks();
12995         }
12996         if (gameMode == PlayFromGameFile) {
12997             if (appData.timeDelay >= 0)
12998                 AutoPlayGameLoop();
12999         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13000             Reset(FALSE, TRUE);
13001             SendToICS(ics_prefix);
13002             SendToICS("refresh\n");
13003         } else if (currentMove < forwardMostMove) {
13004             ForwardInner(forwardMostMove);
13005         }
13006         pauseExamInvalid = FALSE;
13007     } else {
13008         switch (gameMode) {
13009           default:
13010             return;
13011           case IcsExamining:
13012             pauseExamForwardMostMove = forwardMostMove;
13013             pauseExamInvalid = FALSE;
13014             /* fall through */
13015           case IcsObserving:
13016           case IcsPlayingWhite:
13017           case IcsPlayingBlack:
13018             pausing = TRUE;
13019             ModeHighlight();
13020             return;
13021           case PlayFromGameFile:
13022             (void) StopLoadGameTimer();
13023             pausing = TRUE;
13024             ModeHighlight();
13025             break;
13026           case BeginningOfGame:
13027             if (appData.icsActive) return;
13028             /* else fall through */
13029           case MachinePlaysWhite:
13030           case MachinePlaysBlack:
13031           case TwoMachinesPlay:
13032             if (forwardMostMove == 0)
13033               return;           /* don't pause if no one has moved */
13034             if ((gameMode == MachinePlaysWhite &&
13035                  !WhiteOnMove(forwardMostMove)) ||
13036                 (gameMode == MachinePlaysBlack &&
13037                  WhiteOnMove(forwardMostMove))) {
13038                 StopClocks();
13039             }
13040             pausing = TRUE;
13041             ModeHighlight();
13042             break;
13043         }
13044     }
13045 }
13046
13047 void
13048 EditCommentEvent ()
13049 {
13050     char title[MSG_SIZ];
13051
13052     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13053       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13054     } else {
13055       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13056                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13057                parseList[currentMove - 1]);
13058     }
13059
13060     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13061 }
13062
13063
13064 void
13065 EditTagsEvent ()
13066 {
13067     char *tags = PGNTags(&gameInfo);
13068     bookUp = FALSE;
13069     EditTagsPopUp(tags, NULL);
13070     free(tags);
13071 }
13072
13073 void
13074 AnalyzeModeEvent ()
13075 {
13076     if (appData.noChessProgram || gameMode == AnalyzeMode)
13077       return;
13078
13079     if (gameMode != AnalyzeFile) {
13080         if (!appData.icsEngineAnalyze) {
13081                EditGameEvent();
13082                if (gameMode != EditGame) return;
13083         }
13084         ResurrectChessProgram();
13085         SendToProgram("analyze\n", &first);
13086         first.analyzing = TRUE;
13087         /*first.maybeThinking = TRUE;*/
13088         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13089         EngineOutputPopUp();
13090     }
13091     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13092     pausing = FALSE;
13093     ModeHighlight();
13094     SetGameInfo();
13095
13096     StartAnalysisClock();
13097     GetTimeMark(&lastNodeCountTime);
13098     lastNodeCount = 0;
13099 }
13100
13101 void
13102 AnalyzeFileEvent ()
13103 {
13104     if (appData.noChessProgram || gameMode == AnalyzeFile)
13105       return;
13106
13107     if (gameMode != AnalyzeMode) {
13108         EditGameEvent();
13109         if (gameMode != EditGame) return;
13110         ResurrectChessProgram();
13111         SendToProgram("analyze\n", &first);
13112         first.analyzing = TRUE;
13113         /*first.maybeThinking = TRUE;*/
13114         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13115         EngineOutputPopUp();
13116     }
13117     gameMode = AnalyzeFile;
13118     pausing = FALSE;
13119     ModeHighlight();
13120     SetGameInfo();
13121
13122     StartAnalysisClock();
13123     GetTimeMark(&lastNodeCountTime);
13124     lastNodeCount = 0;
13125     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13126 }
13127
13128 void
13129 MachineWhiteEvent ()
13130 {
13131     char buf[MSG_SIZ];
13132     char *bookHit = NULL;
13133
13134     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13135       return;
13136
13137
13138     if (gameMode == PlayFromGameFile ||
13139         gameMode == TwoMachinesPlay  ||
13140         gameMode == Training         ||
13141         gameMode == AnalyzeMode      ||
13142         gameMode == EndOfGame)
13143         EditGameEvent();
13144
13145     if (gameMode == EditPosition)
13146         EditPositionDone(TRUE);
13147
13148     if (!WhiteOnMove(currentMove)) {
13149         DisplayError(_("It is not White's turn"), 0);
13150         return;
13151     }
13152
13153     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13154       ExitAnalyzeMode();
13155
13156     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13157         gameMode == AnalyzeFile)
13158         TruncateGame();
13159
13160     ResurrectChessProgram();    /* in case it isn't running */
13161     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13162         gameMode = MachinePlaysWhite;
13163         ResetClocks();
13164     } else
13165     gameMode = MachinePlaysWhite;
13166     pausing = FALSE;
13167     ModeHighlight();
13168     SetGameInfo();
13169     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13170     DisplayTitle(buf);
13171     if (first.sendName) {
13172       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13173       SendToProgram(buf, &first);
13174     }
13175     if (first.sendTime) {
13176       if (first.useColors) {
13177         SendToProgram("black\n", &first); /*gnu kludge*/
13178       }
13179       SendTimeRemaining(&first, TRUE);
13180     }
13181     if (first.useColors) {
13182       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13183     }
13184     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13185     SetMachineThinkingEnables();
13186     first.maybeThinking = TRUE;
13187     StartClocks();
13188     firstMove = FALSE;
13189
13190     if (appData.autoFlipView && !flipView) {
13191       flipView = !flipView;
13192       DrawPosition(FALSE, NULL);
13193       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13194     }
13195
13196     if(bookHit) { // [HGM] book: simulate book reply
13197         static char bookMove[MSG_SIZ]; // a bit generous?
13198
13199         programStats.nodes = programStats.depth = programStats.time =
13200         programStats.score = programStats.got_only_move = 0;
13201         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13202
13203         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13204         strcat(bookMove, bookHit);
13205         HandleMachineMove(bookMove, &first);
13206     }
13207 }
13208
13209 void
13210 MachineBlackEvent ()
13211 {
13212   char buf[MSG_SIZ];
13213   char *bookHit = NULL;
13214
13215     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13216         return;
13217
13218
13219     if (gameMode == PlayFromGameFile ||
13220         gameMode == TwoMachinesPlay  ||
13221         gameMode == Training         ||
13222         gameMode == AnalyzeMode      ||
13223         gameMode == EndOfGame)
13224         EditGameEvent();
13225
13226     if (gameMode == EditPosition)
13227         EditPositionDone(TRUE);
13228
13229     if (WhiteOnMove(currentMove)) {
13230         DisplayError(_("It is not Black's turn"), 0);
13231         return;
13232     }
13233
13234     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13235       ExitAnalyzeMode();
13236
13237     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13238         gameMode == AnalyzeFile)
13239         TruncateGame();
13240
13241     ResurrectChessProgram();    /* in case it isn't running */
13242     gameMode = MachinePlaysBlack;
13243     pausing = FALSE;
13244     ModeHighlight();
13245     SetGameInfo();
13246     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13247     DisplayTitle(buf);
13248     if (first.sendName) {
13249       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13250       SendToProgram(buf, &first);
13251     }
13252     if (first.sendTime) {
13253       if (first.useColors) {
13254         SendToProgram("white\n", &first); /*gnu kludge*/
13255       }
13256       SendTimeRemaining(&first, FALSE);
13257     }
13258     if (first.useColors) {
13259       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13260     }
13261     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13262     SetMachineThinkingEnables();
13263     first.maybeThinking = TRUE;
13264     StartClocks();
13265
13266     if (appData.autoFlipView && flipView) {
13267       flipView = !flipView;
13268       DrawPosition(FALSE, NULL);
13269       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13270     }
13271     if(bookHit) { // [HGM] book: simulate book reply
13272         static char bookMove[MSG_SIZ]; // a bit generous?
13273
13274         programStats.nodes = programStats.depth = programStats.time =
13275         programStats.score = programStats.got_only_move = 0;
13276         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13277
13278         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13279         strcat(bookMove, bookHit);
13280         HandleMachineMove(bookMove, &first);
13281     }
13282 }
13283
13284
13285 void
13286 DisplayTwoMachinesTitle ()
13287 {
13288     char buf[MSG_SIZ];
13289     if (appData.matchGames > 0) {
13290         if(appData.tourneyFile[0]) {
13291           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13292                    gameInfo.white, _("vs."), gameInfo.black,
13293                    nextGame+1, appData.matchGames+1,
13294                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13295         } else 
13296         if (first.twoMachinesColor[0] == 'w') {
13297           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13298                    gameInfo.white, _("vs."),  gameInfo.black,
13299                    first.matchWins, second.matchWins,
13300                    matchGame - 1 - (first.matchWins + second.matchWins));
13301         } else {
13302           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13303                    gameInfo.white, _("vs."), gameInfo.black,
13304                    second.matchWins, first.matchWins,
13305                    matchGame - 1 - (first.matchWins + second.matchWins));
13306         }
13307     } else {
13308       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13309     }
13310     DisplayTitle(buf);
13311 }
13312
13313 void
13314 SettingsMenuIfReady ()
13315 {
13316   if (second.lastPing != second.lastPong) {
13317     DisplayMessage("", _("Waiting for second chess program"));
13318     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13319     return;
13320   }
13321   ThawUI();
13322   DisplayMessage("", "");
13323   SettingsPopUp(&second);
13324 }
13325
13326 int
13327 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13328 {
13329     char buf[MSG_SIZ];
13330     if (cps->pr == NoProc) {
13331         StartChessProgram(cps);
13332         if (cps->protocolVersion == 1) {
13333           retry();
13334         } else {
13335           /* kludge: allow timeout for initial "feature" command */
13336           FreezeUI();
13337           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13338           DisplayMessage("", buf);
13339           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13340         }
13341         return 1;
13342     }
13343     return 0;
13344 }
13345
13346 void
13347 TwoMachinesEvent P((void))
13348 {
13349     int i;
13350     char buf[MSG_SIZ];
13351     ChessProgramState *onmove;
13352     char *bookHit = NULL;
13353     static int stalling = 0;
13354     TimeMark now;
13355     long wait;
13356
13357     if (appData.noChessProgram) return;
13358
13359     switch (gameMode) {
13360       case TwoMachinesPlay:
13361         return;
13362       case MachinePlaysWhite:
13363       case MachinePlaysBlack:
13364         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13365             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13366             return;
13367         }
13368         /* fall through */
13369       case BeginningOfGame:
13370       case PlayFromGameFile:
13371       case EndOfGame:
13372         EditGameEvent();
13373         if (gameMode != EditGame) return;
13374         break;
13375       case EditPosition:
13376         EditPositionDone(TRUE);
13377         break;
13378       case AnalyzeMode:
13379       case AnalyzeFile:
13380         ExitAnalyzeMode();
13381         break;
13382       case EditGame:
13383       default:
13384         break;
13385     }
13386
13387     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
13388         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
13389         if(strcmp(buf, currentDebugFile)) { // name has changed
13390             FILE *f = fopen(buf, "w");
13391             if(f) { // if opening the new file failed, just keep using the old one
13392                 ASSIGN(currentDebugFile, buf);
13393                 fclose(debugFP);
13394                 debugFP = f;
13395             }
13396         }
13397     }
13398 //    forwardMostMove = currentMove;
13399     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13400
13401     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13402
13403     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13404     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13405       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13406       return;
13407     }
13408     if(!stalling) {
13409       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13410       SendToProgram("force\n", &second);
13411       stalling = 1;
13412       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13413       return;
13414     }
13415     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13416     if(appData.matchPause>10000 || appData.matchPause<10)
13417                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13418     wait = SubtractTimeMarks(&now, &pauseStart);
13419     if(wait < appData.matchPause) {
13420         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13421         return;
13422     }
13423     // we are now committed to starting the game
13424     stalling = 0;
13425     DisplayMessage("", "");
13426     if (startedFromSetupPosition) {
13427         SendBoard(&second, backwardMostMove);
13428     if (appData.debugMode) {
13429         fprintf(debugFP, "Two Machines\n");
13430     }
13431     }
13432     for (i = backwardMostMove; i < forwardMostMove; i++) {
13433         SendMoveToProgram(i, &second);
13434     }
13435
13436     gameMode = TwoMachinesPlay;
13437     pausing = FALSE;
13438     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13439     SetGameInfo();
13440     DisplayTwoMachinesTitle();
13441     firstMove = TRUE;
13442     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13443         onmove = &first;
13444     } else {
13445         onmove = &second;
13446     }
13447     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13448     SendToProgram(first.computerString, &first);
13449     if (first.sendName) {
13450       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13451       SendToProgram(buf, &first);
13452     }
13453     SendToProgram(second.computerString, &second);
13454     if (second.sendName) {
13455       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13456       SendToProgram(buf, &second);
13457     }
13458
13459     ResetClocks();
13460     if (!first.sendTime || !second.sendTime) {
13461         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13462         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13463     }
13464     if (onmove->sendTime) {
13465       if (onmove->useColors) {
13466         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13467       }
13468       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13469     }
13470     if (onmove->useColors) {
13471       SendToProgram(onmove->twoMachinesColor, onmove);
13472     }
13473     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13474 //    SendToProgram("go\n", onmove);
13475     onmove->maybeThinking = TRUE;
13476     SetMachineThinkingEnables();
13477
13478     StartClocks();
13479
13480     if(bookHit) { // [HGM] book: simulate book reply
13481         static char bookMove[MSG_SIZ]; // a bit generous?
13482
13483         programStats.nodes = programStats.depth = programStats.time =
13484         programStats.score = programStats.got_only_move = 0;
13485         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13486
13487         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13488         strcat(bookMove, bookHit);
13489         savedMessage = bookMove; // args for deferred call
13490         savedState = onmove;
13491         ScheduleDelayedEvent(DeferredBookMove, 1);
13492     }
13493 }
13494
13495 void
13496 TrainingEvent ()
13497 {
13498     if (gameMode == Training) {
13499       SetTrainingModeOff();
13500       gameMode = PlayFromGameFile;
13501       DisplayMessage("", _("Training mode off"));
13502     } else {
13503       gameMode = Training;
13504       animateTraining = appData.animate;
13505
13506       /* make sure we are not already at the end of the game */
13507       if (currentMove < forwardMostMove) {
13508         SetTrainingModeOn();
13509         DisplayMessage("", _("Training mode on"));
13510       } else {
13511         gameMode = PlayFromGameFile;
13512         DisplayError(_("Already at end of game"), 0);
13513       }
13514     }
13515     ModeHighlight();
13516 }
13517
13518 void
13519 IcsClientEvent ()
13520 {
13521     if (!appData.icsActive) return;
13522     switch (gameMode) {
13523       case IcsPlayingWhite:
13524       case IcsPlayingBlack:
13525       case IcsObserving:
13526       case IcsIdle:
13527       case BeginningOfGame:
13528       case IcsExamining:
13529         return;
13530
13531       case EditGame:
13532         break;
13533
13534       case EditPosition:
13535         EditPositionDone(TRUE);
13536         break;
13537
13538       case AnalyzeMode:
13539       case AnalyzeFile:
13540         ExitAnalyzeMode();
13541         break;
13542
13543       default:
13544         EditGameEvent();
13545         break;
13546     }
13547
13548     gameMode = IcsIdle;
13549     ModeHighlight();
13550     return;
13551 }
13552
13553 void
13554 EditGameEvent ()
13555 {
13556     int i;
13557
13558     switch (gameMode) {
13559       case Training:
13560         SetTrainingModeOff();
13561         break;
13562       case MachinePlaysWhite:
13563       case MachinePlaysBlack:
13564       case BeginningOfGame:
13565         SendToProgram("force\n", &first);
13566         SetUserThinkingEnables();
13567         break;
13568       case PlayFromGameFile:
13569         (void) StopLoadGameTimer();
13570         if (gameFileFP != NULL) {
13571             gameFileFP = NULL;
13572         }
13573         break;
13574       case EditPosition:
13575         EditPositionDone(TRUE);
13576         break;
13577       case AnalyzeMode:
13578       case AnalyzeFile:
13579         ExitAnalyzeMode();
13580         SendToProgram("force\n", &first);
13581         break;
13582       case TwoMachinesPlay:
13583         GameEnds(EndOfFile, NULL, GE_PLAYER);
13584         ResurrectChessProgram();
13585         SetUserThinkingEnables();
13586         break;
13587       case EndOfGame:
13588         ResurrectChessProgram();
13589         break;
13590       case IcsPlayingBlack:
13591       case IcsPlayingWhite:
13592         DisplayError(_("Warning: You are still playing a game"), 0);
13593         break;
13594       case IcsObserving:
13595         DisplayError(_("Warning: You are still observing a game"), 0);
13596         break;
13597       case IcsExamining:
13598         DisplayError(_("Warning: You are still examining a game"), 0);
13599         break;
13600       case IcsIdle:
13601         break;
13602       case EditGame:
13603       default:
13604         return;
13605     }
13606
13607     pausing = FALSE;
13608     StopClocks();
13609     first.offeredDraw = second.offeredDraw = 0;
13610
13611     if (gameMode == PlayFromGameFile) {
13612         whiteTimeRemaining = timeRemaining[0][currentMove];
13613         blackTimeRemaining = timeRemaining[1][currentMove];
13614         DisplayTitle("");
13615     }
13616
13617     if (gameMode == MachinePlaysWhite ||
13618         gameMode == MachinePlaysBlack ||
13619         gameMode == TwoMachinesPlay ||
13620         gameMode == EndOfGame) {
13621         i = forwardMostMove;
13622         while (i > currentMove) {
13623             SendToProgram("undo\n", &first);
13624             i--;
13625         }
13626         if(!adjustedClock) {
13627         whiteTimeRemaining = timeRemaining[0][currentMove];
13628         blackTimeRemaining = timeRemaining[1][currentMove];
13629         DisplayBothClocks();
13630         }
13631         if (whiteFlag || blackFlag) {
13632             whiteFlag = blackFlag = 0;
13633         }
13634         DisplayTitle("");
13635     }
13636
13637     gameMode = EditGame;
13638     ModeHighlight();
13639     SetGameInfo();
13640 }
13641
13642
13643 void
13644 EditPositionEvent ()
13645 {
13646     if (gameMode == EditPosition) {
13647         EditGameEvent();
13648         return;
13649     }
13650
13651     EditGameEvent();
13652     if (gameMode != EditGame) return;
13653
13654     gameMode = EditPosition;
13655     ModeHighlight();
13656     SetGameInfo();
13657     if (currentMove > 0)
13658       CopyBoard(boards[0], boards[currentMove]);
13659
13660     blackPlaysFirst = !WhiteOnMove(currentMove);
13661     ResetClocks();
13662     currentMove = forwardMostMove = backwardMostMove = 0;
13663     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13664     DisplayMove(-1);
13665 }
13666
13667 void
13668 ExitAnalyzeMode ()
13669 {
13670     /* [DM] icsEngineAnalyze - possible call from other functions */
13671     if (appData.icsEngineAnalyze) {
13672         appData.icsEngineAnalyze = FALSE;
13673
13674         DisplayMessage("",_("Close ICS engine analyze..."));
13675     }
13676     if (first.analysisSupport && first.analyzing) {
13677       SendToProgram("exit\n", &first);
13678       first.analyzing = FALSE;
13679     }
13680     thinkOutput[0] = NULLCHAR;
13681 }
13682
13683 void
13684 EditPositionDone (Boolean fakeRights)
13685 {
13686     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13687
13688     startedFromSetupPosition = TRUE;
13689     InitChessProgram(&first, FALSE);
13690     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13691       boards[0][EP_STATUS] = EP_NONE;
13692       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13693     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13694         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13695         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13696       } else boards[0][CASTLING][2] = NoRights;
13697     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13698         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13699         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13700       } else boards[0][CASTLING][5] = NoRights;
13701     }
13702     SendToProgram("force\n", &first);
13703     if (blackPlaysFirst) {
13704         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13705         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13706         currentMove = forwardMostMove = backwardMostMove = 1;
13707         CopyBoard(boards[1], boards[0]);
13708     } else {
13709         currentMove = forwardMostMove = backwardMostMove = 0;
13710     }
13711     SendBoard(&first, forwardMostMove);
13712     if (appData.debugMode) {
13713         fprintf(debugFP, "EditPosDone\n");
13714     }
13715     DisplayTitle("");
13716     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13717     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13718     gameMode = EditGame;
13719     ModeHighlight();
13720     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13721     ClearHighlights(); /* [AS] */
13722 }
13723
13724 /* Pause for `ms' milliseconds */
13725 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13726 void
13727 TimeDelay (long ms)
13728 {
13729     TimeMark m1, m2;
13730
13731     GetTimeMark(&m1);
13732     do {
13733         GetTimeMark(&m2);
13734     } while (SubtractTimeMarks(&m2, &m1) < ms);
13735 }
13736
13737 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13738 void
13739 SendMultiLineToICS (char *buf)
13740 {
13741     char temp[MSG_SIZ+1], *p;
13742     int len;
13743
13744     len = strlen(buf);
13745     if (len > MSG_SIZ)
13746       len = MSG_SIZ;
13747
13748     strncpy(temp, buf, len);
13749     temp[len] = 0;
13750
13751     p = temp;
13752     while (*p) {
13753         if (*p == '\n' || *p == '\r')
13754           *p = ' ';
13755         ++p;
13756     }
13757
13758     strcat(temp, "\n");
13759     SendToICS(temp);
13760     SendToPlayer(temp, strlen(temp));
13761 }
13762
13763 void
13764 SetWhiteToPlayEvent ()
13765 {
13766     if (gameMode == EditPosition) {
13767         blackPlaysFirst = FALSE;
13768         DisplayBothClocks();    /* works because currentMove is 0 */
13769     } else if (gameMode == IcsExamining) {
13770         SendToICS(ics_prefix);
13771         SendToICS("tomove white\n");
13772     }
13773 }
13774
13775 void
13776 SetBlackToPlayEvent ()
13777 {
13778     if (gameMode == EditPosition) {
13779         blackPlaysFirst = TRUE;
13780         currentMove = 1;        /* kludge */
13781         DisplayBothClocks();
13782         currentMove = 0;
13783     } else if (gameMode == IcsExamining) {
13784         SendToICS(ics_prefix);
13785         SendToICS("tomove black\n");
13786     }
13787 }
13788
13789 void
13790 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13791 {
13792     char buf[MSG_SIZ];
13793     ChessSquare piece = boards[0][y][x];
13794
13795     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13796
13797     switch (selection) {
13798       case ClearBoard:
13799         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13800             SendToICS(ics_prefix);
13801             SendToICS("bsetup clear\n");
13802         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13803             SendToICS(ics_prefix);
13804             SendToICS("clearboard\n");
13805         } else {
13806             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13807                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13808                 for (y = 0; y < BOARD_HEIGHT; y++) {
13809                     if (gameMode == IcsExamining) {
13810                         if (boards[currentMove][y][x] != EmptySquare) {
13811                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13812                                     AAA + x, ONE + y);
13813                             SendToICS(buf);
13814                         }
13815                     } else {
13816                         boards[0][y][x] = p;
13817                     }
13818                 }
13819             }
13820         }
13821         if (gameMode == EditPosition) {
13822             DrawPosition(FALSE, boards[0]);
13823         }
13824         break;
13825
13826       case WhitePlay:
13827         SetWhiteToPlayEvent();
13828         break;
13829
13830       case BlackPlay:
13831         SetBlackToPlayEvent();
13832         break;
13833
13834       case EmptySquare:
13835         if (gameMode == IcsExamining) {
13836             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13837             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13838             SendToICS(buf);
13839         } else {
13840             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13841                 if(x == BOARD_LEFT-2) {
13842                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13843                     boards[0][y][1] = 0;
13844                 } else
13845                 if(x == BOARD_RGHT+1) {
13846                     if(y >= gameInfo.holdingsSize) break;
13847                     boards[0][y][BOARD_WIDTH-2] = 0;
13848                 } else break;
13849             }
13850             boards[0][y][x] = EmptySquare;
13851             DrawPosition(FALSE, boards[0]);
13852         }
13853         break;
13854
13855       case PromotePiece:
13856         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13857            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13858             selection = (ChessSquare) (PROMOTED piece);
13859         } else if(piece == EmptySquare) selection = WhiteSilver;
13860         else selection = (ChessSquare)((int)piece - 1);
13861         goto defaultlabel;
13862
13863       case DemotePiece:
13864         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13865            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13866             selection = (ChessSquare) (DEMOTED piece);
13867         } else if(piece == EmptySquare) selection = BlackSilver;
13868         else selection = (ChessSquare)((int)piece + 1);
13869         goto defaultlabel;
13870
13871       case WhiteQueen:
13872       case BlackQueen:
13873         if(gameInfo.variant == VariantShatranj ||
13874            gameInfo.variant == VariantXiangqi  ||
13875            gameInfo.variant == VariantCourier  ||
13876            gameInfo.variant == VariantMakruk     )
13877             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13878         goto defaultlabel;
13879
13880       case WhiteKing:
13881       case BlackKing:
13882         if(gameInfo.variant == VariantXiangqi)
13883             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13884         if(gameInfo.variant == VariantKnightmate)
13885             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13886       default:
13887         defaultlabel:
13888         if (gameMode == IcsExamining) {
13889             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13890             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13891                      PieceToChar(selection), AAA + x, ONE + y);
13892             SendToICS(buf);
13893         } else {
13894             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13895                 int n;
13896                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13897                     n = PieceToNumber(selection - BlackPawn);
13898                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13899                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13900                     boards[0][BOARD_HEIGHT-1-n][1]++;
13901                 } else
13902                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13903                     n = PieceToNumber(selection);
13904                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13905                     boards[0][n][BOARD_WIDTH-1] = selection;
13906                     boards[0][n][BOARD_WIDTH-2]++;
13907                 }
13908             } else
13909             boards[0][y][x] = selection;
13910             DrawPosition(TRUE, boards[0]);
13911         }
13912         break;
13913     }
13914 }
13915
13916
13917 void
13918 DropMenuEvent (ChessSquare selection, int x, int y)
13919 {
13920     ChessMove moveType;
13921
13922     switch (gameMode) {
13923       case IcsPlayingWhite:
13924       case MachinePlaysBlack:
13925         if (!WhiteOnMove(currentMove)) {
13926             DisplayMoveError(_("It is Black's turn"));
13927             return;
13928         }
13929         moveType = WhiteDrop;
13930         break;
13931       case IcsPlayingBlack:
13932       case MachinePlaysWhite:
13933         if (WhiteOnMove(currentMove)) {
13934             DisplayMoveError(_("It is White's turn"));
13935             return;
13936         }
13937         moveType = BlackDrop;
13938         break;
13939       case EditGame:
13940         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13941         break;
13942       default:
13943         return;
13944     }
13945
13946     if (moveType == BlackDrop && selection < BlackPawn) {
13947       selection = (ChessSquare) ((int) selection
13948                                  + (int) BlackPawn - (int) WhitePawn);
13949     }
13950     if (boards[currentMove][y][x] != EmptySquare) {
13951         DisplayMoveError(_("That square is occupied"));
13952         return;
13953     }
13954
13955     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13956 }
13957
13958 void
13959 AcceptEvent ()
13960 {
13961     /* Accept a pending offer of any kind from opponent */
13962
13963     if (appData.icsActive) {
13964         SendToICS(ics_prefix);
13965         SendToICS("accept\n");
13966     } else if (cmailMsgLoaded) {
13967         if (currentMove == cmailOldMove &&
13968             commentList[cmailOldMove] != NULL &&
13969             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13970                    "Black offers a draw" : "White offers a draw")) {
13971             TruncateGame();
13972             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13973             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13974         } else {
13975             DisplayError(_("There is no pending offer on this move"), 0);
13976             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13977         }
13978     } else {
13979         /* Not used for offers from chess program */
13980     }
13981 }
13982
13983 void
13984 DeclineEvent ()
13985 {
13986     /* Decline a pending offer of any kind from opponent */
13987
13988     if (appData.icsActive) {
13989         SendToICS(ics_prefix);
13990         SendToICS("decline\n");
13991     } else if (cmailMsgLoaded) {
13992         if (currentMove == cmailOldMove &&
13993             commentList[cmailOldMove] != NULL &&
13994             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13995                    "Black offers a draw" : "White offers a draw")) {
13996 #ifdef NOTDEF
13997             AppendComment(cmailOldMove, "Draw declined", TRUE);
13998             DisplayComment(cmailOldMove - 1, "Draw declined");
13999 #endif /*NOTDEF*/
14000         } else {
14001             DisplayError(_("There is no pending offer on this move"), 0);
14002         }
14003     } else {
14004         /* Not used for offers from chess program */
14005     }
14006 }
14007
14008 void
14009 RematchEvent ()
14010 {
14011     /* Issue ICS rematch command */
14012     if (appData.icsActive) {
14013         SendToICS(ics_prefix);
14014         SendToICS("rematch\n");
14015     }
14016 }
14017
14018 void
14019 CallFlagEvent ()
14020 {
14021     /* Call your opponent's flag (claim a win on time) */
14022     if (appData.icsActive) {
14023         SendToICS(ics_prefix);
14024         SendToICS("flag\n");
14025     } else {
14026         switch (gameMode) {
14027           default:
14028             return;
14029           case MachinePlaysWhite:
14030             if (whiteFlag) {
14031                 if (blackFlag)
14032                   GameEnds(GameIsDrawn, "Both players ran out of time",
14033                            GE_PLAYER);
14034                 else
14035                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14036             } else {
14037                 DisplayError(_("Your opponent is not out of time"), 0);
14038             }
14039             break;
14040           case MachinePlaysBlack:
14041             if (blackFlag) {
14042                 if (whiteFlag)
14043                   GameEnds(GameIsDrawn, "Both players ran out of time",
14044                            GE_PLAYER);
14045                 else
14046                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14047             } else {
14048                 DisplayError(_("Your opponent is not out of time"), 0);
14049             }
14050             break;
14051         }
14052     }
14053 }
14054
14055 void
14056 ClockClick (int which)
14057 {       // [HGM] code moved to back-end from winboard.c
14058         if(which) { // black clock
14059           if (gameMode == EditPosition || gameMode == IcsExamining) {
14060             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14061             SetBlackToPlayEvent();
14062           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14063           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14064           } else if (shiftKey) {
14065             AdjustClock(which, -1);
14066           } else if (gameMode == IcsPlayingWhite ||
14067                      gameMode == MachinePlaysBlack) {
14068             CallFlagEvent();
14069           }
14070         } else { // white clock
14071           if (gameMode == EditPosition || gameMode == IcsExamining) {
14072             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14073             SetWhiteToPlayEvent();
14074           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14075           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14076           } else if (shiftKey) {
14077             AdjustClock(which, -1);
14078           } else if (gameMode == IcsPlayingBlack ||
14079                    gameMode == MachinePlaysWhite) {
14080             CallFlagEvent();
14081           }
14082         }
14083 }
14084
14085 void
14086 DrawEvent ()
14087 {
14088     /* Offer draw or accept pending draw offer from opponent */
14089
14090     if (appData.icsActive) {
14091         /* Note: tournament rules require draw offers to be
14092            made after you make your move but before you punch
14093            your clock.  Currently ICS doesn't let you do that;
14094            instead, you immediately punch your clock after making
14095            a move, but you can offer a draw at any time. */
14096
14097         SendToICS(ics_prefix);
14098         SendToICS("draw\n");
14099         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14100     } else if (cmailMsgLoaded) {
14101         if (currentMove == cmailOldMove &&
14102             commentList[cmailOldMove] != NULL &&
14103             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14104                    "Black offers a draw" : "White offers a draw")) {
14105             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14106             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14107         } else if (currentMove == cmailOldMove + 1) {
14108             char *offer = WhiteOnMove(cmailOldMove) ?
14109               "White offers a draw" : "Black offers a draw";
14110             AppendComment(currentMove, offer, TRUE);
14111             DisplayComment(currentMove - 1, offer);
14112             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14113         } else {
14114             DisplayError(_("You must make your move before offering a draw"), 0);
14115             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14116         }
14117     } else if (first.offeredDraw) {
14118         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14119     } else {
14120         if (first.sendDrawOffers) {
14121             SendToProgram("draw\n", &first);
14122             userOfferedDraw = TRUE;
14123         }
14124     }
14125 }
14126
14127 void
14128 AdjournEvent ()
14129 {
14130     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14131
14132     if (appData.icsActive) {
14133         SendToICS(ics_prefix);
14134         SendToICS("adjourn\n");
14135     } else {
14136         /* Currently GNU Chess doesn't offer or accept Adjourns */
14137     }
14138 }
14139
14140
14141 void
14142 AbortEvent ()
14143 {
14144     /* Offer Abort or accept pending Abort offer from opponent */
14145
14146     if (appData.icsActive) {
14147         SendToICS(ics_prefix);
14148         SendToICS("abort\n");
14149     } else {
14150         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14151     }
14152 }
14153
14154 void
14155 ResignEvent ()
14156 {
14157     /* Resign.  You can do this even if it's not your turn. */
14158
14159     if (appData.icsActive) {
14160         SendToICS(ics_prefix);
14161         SendToICS("resign\n");
14162     } else {
14163         switch (gameMode) {
14164           case MachinePlaysWhite:
14165             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14166             break;
14167           case MachinePlaysBlack:
14168             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14169             break;
14170           case EditGame:
14171             if (cmailMsgLoaded) {
14172                 TruncateGame();
14173                 if (WhiteOnMove(cmailOldMove)) {
14174                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14175                 } else {
14176                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14177                 }
14178                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14179             }
14180             break;
14181           default:
14182             break;
14183         }
14184     }
14185 }
14186
14187
14188 void
14189 StopObservingEvent ()
14190 {
14191     /* Stop observing current games */
14192     SendToICS(ics_prefix);
14193     SendToICS("unobserve\n");
14194 }
14195
14196 void
14197 StopExaminingEvent ()
14198 {
14199     /* Stop observing current game */
14200     SendToICS(ics_prefix);
14201     SendToICS("unexamine\n");
14202 }
14203
14204 void
14205 ForwardInner (int target)
14206 {
14207     int limit;
14208
14209     if (appData.debugMode)
14210         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14211                 target, currentMove, forwardMostMove);
14212
14213     if (gameMode == EditPosition)
14214       return;
14215
14216     seekGraphUp = FALSE;
14217     MarkTargetSquares(1);
14218
14219     if (gameMode == PlayFromGameFile && !pausing)
14220       PauseEvent();
14221
14222     if (gameMode == IcsExamining && pausing)
14223       limit = pauseExamForwardMostMove;
14224     else
14225       limit = forwardMostMove;
14226
14227     if (target > limit) target = limit;
14228
14229     if (target > 0 && moveList[target - 1][0]) {
14230         int fromX, fromY, toX, toY;
14231         toX = moveList[target - 1][2] - AAA;
14232         toY = moveList[target - 1][3] - ONE;
14233         if (moveList[target - 1][1] == '@') {
14234             if (appData.highlightLastMove) {
14235                 SetHighlights(-1, -1, toX, toY);
14236             }
14237         } else {
14238             fromX = moveList[target - 1][0] - AAA;
14239             fromY = moveList[target - 1][1] - ONE;
14240             if (target == currentMove + 1) {
14241                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14242             }
14243             if (appData.highlightLastMove) {
14244                 SetHighlights(fromX, fromY, toX, toY);
14245             }
14246         }
14247     }
14248     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14249         gameMode == Training || gameMode == PlayFromGameFile ||
14250         gameMode == AnalyzeFile) {
14251         while (currentMove < target) {
14252             SendMoveToProgram(currentMove++, &first);
14253         }
14254     } else {
14255         currentMove = target;
14256     }
14257
14258     if (gameMode == EditGame || gameMode == EndOfGame) {
14259         whiteTimeRemaining = timeRemaining[0][currentMove];
14260         blackTimeRemaining = timeRemaining[1][currentMove];
14261     }
14262     DisplayBothClocks();
14263     DisplayMove(currentMove - 1);
14264     DrawPosition(FALSE, boards[currentMove]);
14265     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14266     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14267         DisplayComment(currentMove - 1, commentList[currentMove]);
14268     }
14269 }
14270
14271
14272 void
14273 ForwardEvent ()
14274 {
14275     if (gameMode == IcsExamining && !pausing) {
14276         SendToICS(ics_prefix);
14277         SendToICS("forward\n");
14278     } else {
14279         ForwardInner(currentMove + 1);
14280     }
14281 }
14282
14283 void
14284 ToEndEvent ()
14285 {
14286     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14287         /* to optimze, we temporarily turn off analysis mode while we feed
14288          * the remaining moves to the engine. Otherwise we get analysis output
14289          * after each move.
14290          */
14291         if (first.analysisSupport) {
14292           SendToProgram("exit\nforce\n", &first);
14293           first.analyzing = FALSE;
14294         }
14295     }
14296
14297     if (gameMode == IcsExamining && !pausing) {
14298         SendToICS(ics_prefix);
14299         SendToICS("forward 999999\n");
14300     } else {
14301         ForwardInner(forwardMostMove);
14302     }
14303
14304     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14305         /* we have fed all the moves, so reactivate analysis mode */
14306         SendToProgram("analyze\n", &first);
14307         first.analyzing = TRUE;
14308         /*first.maybeThinking = TRUE;*/
14309         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14310     }
14311 }
14312
14313 void
14314 BackwardInner (int target)
14315 {
14316     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14317
14318     if (appData.debugMode)
14319         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14320                 target, currentMove, forwardMostMove);
14321
14322     if (gameMode == EditPosition) return;
14323     seekGraphUp = FALSE;
14324     MarkTargetSquares(1);
14325     if (currentMove <= backwardMostMove) {
14326         ClearHighlights();
14327         DrawPosition(full_redraw, boards[currentMove]);
14328         return;
14329     }
14330     if (gameMode == PlayFromGameFile && !pausing)
14331       PauseEvent();
14332
14333     if (moveList[target][0]) {
14334         int fromX, fromY, toX, toY;
14335         toX = moveList[target][2] - AAA;
14336         toY = moveList[target][3] - ONE;
14337         if (moveList[target][1] == '@') {
14338             if (appData.highlightLastMove) {
14339                 SetHighlights(-1, -1, toX, toY);
14340             }
14341         } else {
14342             fromX = moveList[target][0] - AAA;
14343             fromY = moveList[target][1] - ONE;
14344             if (target == currentMove - 1) {
14345                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14346             }
14347             if (appData.highlightLastMove) {
14348                 SetHighlights(fromX, fromY, toX, toY);
14349             }
14350         }
14351     }
14352     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14353         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14354         while (currentMove > target) {
14355             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14356                 // null move cannot be undone. Reload program with move history before it.
14357                 int i;
14358                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14359                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14360                 }
14361                 SendBoard(&first, i); 
14362                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14363                 break;
14364             }
14365             SendToProgram("undo\n", &first);
14366             currentMove--;
14367         }
14368     } else {
14369         currentMove = target;
14370     }
14371
14372     if (gameMode == EditGame || gameMode == EndOfGame) {
14373         whiteTimeRemaining = timeRemaining[0][currentMove];
14374         blackTimeRemaining = timeRemaining[1][currentMove];
14375     }
14376     DisplayBothClocks();
14377     DisplayMove(currentMove - 1);
14378     DrawPosition(full_redraw, boards[currentMove]);
14379     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14380     // [HGM] PV info: routine tests if comment empty
14381     DisplayComment(currentMove - 1, commentList[currentMove]);
14382 }
14383
14384 void
14385 BackwardEvent ()
14386 {
14387     if (gameMode == IcsExamining && !pausing) {
14388         SendToICS(ics_prefix);
14389         SendToICS("backward\n");
14390     } else {
14391         BackwardInner(currentMove - 1);
14392     }
14393 }
14394
14395 void
14396 ToStartEvent ()
14397 {
14398     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14399         /* to optimize, we temporarily turn off analysis mode while we undo
14400          * all the moves. Otherwise we get analysis output after each undo.
14401          */
14402         if (first.analysisSupport) {
14403           SendToProgram("exit\nforce\n", &first);
14404           first.analyzing = FALSE;
14405         }
14406     }
14407
14408     if (gameMode == IcsExamining && !pausing) {
14409         SendToICS(ics_prefix);
14410         SendToICS("backward 999999\n");
14411     } else {
14412         BackwardInner(backwardMostMove);
14413     }
14414
14415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14416         /* we have fed all the moves, so reactivate analysis mode */
14417         SendToProgram("analyze\n", &first);
14418         first.analyzing = TRUE;
14419         /*first.maybeThinking = TRUE;*/
14420         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14421     }
14422 }
14423
14424 void
14425 ToNrEvent (int to)
14426 {
14427   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14428   if (to >= forwardMostMove) to = forwardMostMove;
14429   if (to <= backwardMostMove) to = backwardMostMove;
14430   if (to < currentMove) {
14431     BackwardInner(to);
14432   } else {
14433     ForwardInner(to);
14434   }
14435 }
14436
14437 void
14438 RevertEvent (Boolean annotate)
14439 {
14440     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14441         return;
14442     }
14443     if (gameMode != IcsExamining) {
14444         DisplayError(_("You are not examining a game"), 0);
14445         return;
14446     }
14447     if (pausing) {
14448         DisplayError(_("You can't revert while pausing"), 0);
14449         return;
14450     }
14451     SendToICS(ics_prefix);
14452     SendToICS("revert\n");
14453 }
14454
14455 void
14456 RetractMoveEvent ()
14457 {
14458     switch (gameMode) {
14459       case MachinePlaysWhite:
14460       case MachinePlaysBlack:
14461         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14462             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14463             return;
14464         }
14465         if (forwardMostMove < 2) return;
14466         currentMove = forwardMostMove = forwardMostMove - 2;
14467         whiteTimeRemaining = timeRemaining[0][currentMove];
14468         blackTimeRemaining = timeRemaining[1][currentMove];
14469         DisplayBothClocks();
14470         DisplayMove(currentMove - 1);
14471         ClearHighlights();/*!! could figure this out*/
14472         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14473         SendToProgram("remove\n", &first);
14474         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14475         break;
14476
14477       case BeginningOfGame:
14478       default:
14479         break;
14480
14481       case IcsPlayingWhite:
14482       case IcsPlayingBlack:
14483         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14484             SendToICS(ics_prefix);
14485             SendToICS("takeback 2\n");
14486         } else {
14487             SendToICS(ics_prefix);
14488             SendToICS("takeback 1\n");
14489         }
14490         break;
14491     }
14492 }
14493
14494 void
14495 MoveNowEvent ()
14496 {
14497     ChessProgramState *cps;
14498
14499     switch (gameMode) {
14500       case MachinePlaysWhite:
14501         if (!WhiteOnMove(forwardMostMove)) {
14502             DisplayError(_("It is your turn"), 0);
14503             return;
14504         }
14505         cps = &first;
14506         break;
14507       case MachinePlaysBlack:
14508         if (WhiteOnMove(forwardMostMove)) {
14509             DisplayError(_("It is your turn"), 0);
14510             return;
14511         }
14512         cps = &first;
14513         break;
14514       case TwoMachinesPlay:
14515         if (WhiteOnMove(forwardMostMove) ==
14516             (first.twoMachinesColor[0] == 'w')) {
14517             cps = &first;
14518         } else {
14519             cps = &second;
14520         }
14521         break;
14522       case BeginningOfGame:
14523       default:
14524         return;
14525     }
14526     SendToProgram("?\n", cps);
14527 }
14528
14529 void
14530 TruncateGameEvent ()
14531 {
14532     EditGameEvent();
14533     if (gameMode != EditGame) return;
14534     TruncateGame();
14535 }
14536
14537 void
14538 TruncateGame ()
14539 {
14540     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14541     if (forwardMostMove > currentMove) {
14542         if (gameInfo.resultDetails != NULL) {
14543             free(gameInfo.resultDetails);
14544             gameInfo.resultDetails = NULL;
14545             gameInfo.result = GameUnfinished;
14546         }
14547         forwardMostMove = currentMove;
14548         HistorySet(parseList, backwardMostMove, forwardMostMove,
14549                    currentMove-1);
14550     }
14551 }
14552
14553 void
14554 HintEvent ()
14555 {
14556     if (appData.noChessProgram) return;
14557     switch (gameMode) {
14558       case MachinePlaysWhite:
14559         if (WhiteOnMove(forwardMostMove)) {
14560             DisplayError(_("Wait until your turn"), 0);
14561             return;
14562         }
14563         break;
14564       case BeginningOfGame:
14565       case MachinePlaysBlack:
14566         if (!WhiteOnMove(forwardMostMove)) {
14567             DisplayError(_("Wait until your turn"), 0);
14568             return;
14569         }
14570         break;
14571       default:
14572         DisplayError(_("No hint available"), 0);
14573         return;
14574     }
14575     SendToProgram("hint\n", &first);
14576     hintRequested = TRUE;
14577 }
14578
14579 void
14580 BookEvent ()
14581 {
14582     if (appData.noChessProgram) return;
14583     switch (gameMode) {
14584       case MachinePlaysWhite:
14585         if (WhiteOnMove(forwardMostMove)) {
14586             DisplayError(_("Wait until your turn"), 0);
14587             return;
14588         }
14589         break;
14590       case BeginningOfGame:
14591       case MachinePlaysBlack:
14592         if (!WhiteOnMove(forwardMostMove)) {
14593             DisplayError(_("Wait until your turn"), 0);
14594             return;
14595         }
14596         break;
14597       case EditPosition:
14598         EditPositionDone(TRUE);
14599         break;
14600       case TwoMachinesPlay:
14601         return;
14602       default:
14603         break;
14604     }
14605     SendToProgram("bk\n", &first);
14606     bookOutput[0] = NULLCHAR;
14607     bookRequested = TRUE;
14608 }
14609
14610 void
14611 AboutGameEvent ()
14612 {
14613     char *tags = PGNTags(&gameInfo);
14614     TagsPopUp(tags, CmailMsg());
14615     free(tags);
14616 }
14617
14618 /* end button procedures */
14619
14620 void
14621 PrintPosition (FILE *fp, int move)
14622 {
14623     int i, j;
14624
14625     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14627             char c = PieceToChar(boards[move][i][j]);
14628             fputc(c == 'x' ? '.' : c, fp);
14629             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14630         }
14631     }
14632     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14633       fprintf(fp, "white to play\n");
14634     else
14635       fprintf(fp, "black to play\n");
14636 }
14637
14638 void
14639 PrintOpponents (FILE *fp)
14640 {
14641     if (gameInfo.white != NULL) {
14642         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14643     } else {
14644         fprintf(fp, "\n");
14645     }
14646 }
14647
14648 /* Find last component of program's own name, using some heuristics */
14649 void
14650 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14651 {
14652     char *p, *q, c;
14653     int local = (strcmp(host, "localhost") == 0);
14654     while (!local && (p = strchr(prog, ';')) != NULL) {
14655         p++;
14656         while (*p == ' ') p++;
14657         prog = p;
14658     }
14659     if (*prog == '"' || *prog == '\'') {
14660         q = strchr(prog + 1, *prog);
14661     } else {
14662         q = strchr(prog, ' ');
14663     }
14664     if (q == NULL) q = prog + strlen(prog);
14665     p = q;
14666     while (p >= prog && *p != '/' && *p != '\\') p--;
14667     p++;
14668     if(p == prog && *p == '"') p++;
14669     c = *q; *q = 0;
14670     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14671     memcpy(buf, p, q - p);
14672     buf[q - p] = NULLCHAR;
14673     if (!local) {
14674         strcat(buf, "@");
14675         strcat(buf, host);
14676     }
14677 }
14678
14679 char *
14680 TimeControlTagValue ()
14681 {
14682     char buf[MSG_SIZ];
14683     if (!appData.clockMode) {
14684       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14685     } else if (movesPerSession > 0) {
14686       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14687     } else if (timeIncrement == 0) {
14688       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14689     } else {
14690       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14691     }
14692     return StrSave(buf);
14693 }
14694
14695 void
14696 SetGameInfo ()
14697 {
14698     /* This routine is used only for certain modes */
14699     VariantClass v = gameInfo.variant;
14700     ChessMove r = GameUnfinished;
14701     char *p = NULL;
14702
14703     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14704         r = gameInfo.result;
14705         p = gameInfo.resultDetails;
14706         gameInfo.resultDetails = NULL;
14707     }
14708     ClearGameInfo(&gameInfo);
14709     gameInfo.variant = v;
14710
14711     switch (gameMode) {
14712       case MachinePlaysWhite:
14713         gameInfo.event = StrSave( appData.pgnEventHeader );
14714         gameInfo.site = StrSave(HostName());
14715         gameInfo.date = PGNDate();
14716         gameInfo.round = StrSave("-");
14717         gameInfo.white = StrSave(first.tidy);
14718         gameInfo.black = StrSave(UserName());
14719         gameInfo.timeControl = TimeControlTagValue();
14720         break;
14721
14722       case MachinePlaysBlack:
14723         gameInfo.event = StrSave( appData.pgnEventHeader );
14724         gameInfo.site = StrSave(HostName());
14725         gameInfo.date = PGNDate();
14726         gameInfo.round = StrSave("-");
14727         gameInfo.white = StrSave(UserName());
14728         gameInfo.black = StrSave(first.tidy);
14729         gameInfo.timeControl = TimeControlTagValue();
14730         break;
14731
14732       case TwoMachinesPlay:
14733         gameInfo.event = StrSave( appData.pgnEventHeader );
14734         gameInfo.site = StrSave(HostName());
14735         gameInfo.date = PGNDate();
14736         if (roundNr > 0) {
14737             char buf[MSG_SIZ];
14738             snprintf(buf, MSG_SIZ, "%d", roundNr);
14739             gameInfo.round = StrSave(buf);
14740         } else {
14741             gameInfo.round = StrSave("-");
14742         }
14743         if (first.twoMachinesColor[0] == 'w') {
14744             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14745             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14746         } else {
14747             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14748             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14749         }
14750         gameInfo.timeControl = TimeControlTagValue();
14751         break;
14752
14753       case EditGame:
14754         gameInfo.event = StrSave("Edited game");
14755         gameInfo.site = StrSave(HostName());
14756         gameInfo.date = PGNDate();
14757         gameInfo.round = StrSave("-");
14758         gameInfo.white = StrSave("-");
14759         gameInfo.black = StrSave("-");
14760         gameInfo.result = r;
14761         gameInfo.resultDetails = p;
14762         break;
14763
14764       case EditPosition:
14765         gameInfo.event = StrSave("Edited position");
14766         gameInfo.site = StrSave(HostName());
14767         gameInfo.date = PGNDate();
14768         gameInfo.round = StrSave("-");
14769         gameInfo.white = StrSave("-");
14770         gameInfo.black = StrSave("-");
14771         break;
14772
14773       case IcsPlayingWhite:
14774       case IcsPlayingBlack:
14775       case IcsObserving:
14776       case IcsExamining:
14777         break;
14778
14779       case PlayFromGameFile:
14780         gameInfo.event = StrSave("Game from non-PGN file");
14781         gameInfo.site = StrSave(HostName());
14782         gameInfo.date = PGNDate();
14783         gameInfo.round = StrSave("-");
14784         gameInfo.white = StrSave("?");
14785         gameInfo.black = StrSave("?");
14786         break;
14787
14788       default:
14789         break;
14790     }
14791 }
14792
14793 void
14794 ReplaceComment (int index, char *text)
14795 {
14796     int len;
14797     char *p;
14798     float score;
14799
14800     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14801        pvInfoList[index-1].depth == len &&
14802        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14803        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14804     while (*text == '\n') text++;
14805     len = strlen(text);
14806     while (len > 0 && text[len - 1] == '\n') len--;
14807
14808     if (commentList[index] != NULL)
14809       free(commentList[index]);
14810
14811     if (len == 0) {
14812         commentList[index] = NULL;
14813         return;
14814     }
14815   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14816       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14817       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14818     commentList[index] = (char *) malloc(len + 2);
14819     strncpy(commentList[index], text, len);
14820     commentList[index][len] = '\n';
14821     commentList[index][len + 1] = NULLCHAR;
14822   } else {
14823     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14824     char *p;
14825     commentList[index] = (char *) malloc(len + 7);
14826     safeStrCpy(commentList[index], "{\n", 3);
14827     safeStrCpy(commentList[index]+2, text, len+1);
14828     commentList[index][len+2] = NULLCHAR;
14829     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14830     strcat(commentList[index], "\n}\n");
14831   }
14832 }
14833
14834 void
14835 CrushCRs (char *text)
14836 {
14837   char *p = text;
14838   char *q = text;
14839   char ch;
14840
14841   do {
14842     ch = *p++;
14843     if (ch == '\r') continue;
14844     *q++ = ch;
14845   } while (ch != '\0');
14846 }
14847
14848 void
14849 AppendComment (int index, char *text, Boolean addBraces)
14850 /* addBraces  tells if we should add {} */
14851 {
14852     int oldlen, len;
14853     char *old;
14854
14855 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14856     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14857
14858     CrushCRs(text);
14859     while (*text == '\n') text++;
14860     len = strlen(text);
14861     while (len > 0 && text[len - 1] == '\n') len--;
14862     text[len] = NULLCHAR;
14863
14864     if (len == 0) return;
14865
14866     if (commentList[index] != NULL) {
14867       Boolean addClosingBrace = addBraces;
14868         old = commentList[index];
14869         oldlen = strlen(old);
14870         while(commentList[index][oldlen-1] ==  '\n')
14871           commentList[index][--oldlen] = NULLCHAR;
14872         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14873         safeStrCpy(commentList[index], old, oldlen + len + 6);
14874         free(old);
14875         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14876         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14877           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14878           while (*text == '\n') { text++; len--; }
14879           commentList[index][--oldlen] = NULLCHAR;
14880       }
14881         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14882         else          strcat(commentList[index], "\n");
14883         strcat(commentList[index], text);
14884         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14885         else          strcat(commentList[index], "\n");
14886     } else {
14887         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14888         if(addBraces)
14889           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14890         else commentList[index][0] = NULLCHAR;
14891         strcat(commentList[index], text);
14892         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14893         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14894     }
14895 }
14896
14897 static char *
14898 FindStr (char * text, char * sub_text)
14899 {
14900     char * result = strstr( text, sub_text );
14901
14902     if( result != NULL ) {
14903         result += strlen( sub_text );
14904     }
14905
14906     return result;
14907 }
14908
14909 /* [AS] Try to extract PV info from PGN comment */
14910 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14911 char *
14912 GetInfoFromComment (int index, char * text)
14913 {
14914     char * sep = text, *p;
14915
14916     if( text != NULL && index > 0 ) {
14917         int score = 0;
14918         int depth = 0;
14919         int time = -1, sec = 0, deci;
14920         char * s_eval = FindStr( text, "[%eval " );
14921         char * s_emt = FindStr( text, "[%emt " );
14922
14923         if( s_eval != NULL || s_emt != NULL ) {
14924             /* New style */
14925             char delim;
14926
14927             if( s_eval != NULL ) {
14928                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14929                     return text;
14930                 }
14931
14932                 if( delim != ']' ) {
14933                     return text;
14934                 }
14935             }
14936
14937             if( s_emt != NULL ) {
14938             }
14939                 return text;
14940         }
14941         else {
14942             /* We expect something like: [+|-]nnn.nn/dd */
14943             int score_lo = 0;
14944
14945             if(*text != '{') return text; // [HGM] braces: must be normal comment
14946
14947             sep = strchr( text, '/' );
14948             if( sep == NULL || sep < (text+4) ) {
14949                 return text;
14950             }
14951
14952             p = text;
14953             if(p[1] == '(') { // comment starts with PV
14954                p = strchr(p, ')'); // locate end of PV
14955                if(p == NULL || sep < p+5) return text;
14956                // at this point we have something like "{(.*) +0.23/6 ..."
14957                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14958                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14959                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14960             }
14961             time = -1; sec = -1; deci = -1;
14962             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14963                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14964                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14965                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14966                 return text;
14967             }
14968
14969             if( score_lo < 0 || score_lo >= 100 ) {
14970                 return text;
14971             }
14972
14973             if(sec >= 0) time = 600*time + 10*sec; else
14974             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14975
14976             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14977
14978             /* [HGM] PV time: now locate end of PV info */
14979             while( *++sep >= '0' && *sep <= '9'); // strip depth
14980             if(time >= 0)
14981             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14982             if(sec >= 0)
14983             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14984             if(deci >= 0)
14985             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14986             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14987         }
14988
14989         if( depth <= 0 ) {
14990             return text;
14991         }
14992
14993         if( time < 0 ) {
14994             time = -1;
14995         }
14996
14997         pvInfoList[index-1].depth = depth;
14998         pvInfoList[index-1].score = score;
14999         pvInfoList[index-1].time  = 10*time; // centi-sec
15000         if(*sep == '}') *sep = 0; else *--sep = '{';
15001         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15002     }
15003     return sep;
15004 }
15005
15006 void
15007 SendToProgram (char *message, ChessProgramState *cps)
15008 {
15009     int count, outCount, error;
15010     char buf[MSG_SIZ];
15011
15012     if (cps->pr == NoProc) return;
15013     Attention(cps);
15014
15015     if (appData.debugMode) {
15016         TimeMark now;
15017         GetTimeMark(&now);
15018         fprintf(debugFP, "%ld >%-6s: %s",
15019                 SubtractTimeMarks(&now, &programStartTime),
15020                 cps->which, message);
15021     }
15022
15023     count = strlen(message);
15024     outCount = OutputToProcess(cps->pr, message, count, &error);
15025     if (outCount < count && !exiting
15026                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15027       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15028       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15029         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15030             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15031                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15032                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15033                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15034             } else {
15035                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15036                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15037                 gameInfo.result = res;
15038             }
15039             gameInfo.resultDetails = StrSave(buf);
15040         }
15041         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15042         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15043     }
15044 }
15045
15046 void
15047 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15048 {
15049     char *end_str;
15050     char buf[MSG_SIZ];
15051     ChessProgramState *cps = (ChessProgramState *)closure;
15052
15053     if (isr != cps->isr) return; /* Killed intentionally */
15054     if (count <= 0) {
15055         if (count == 0) {
15056             RemoveInputSource(cps->isr);
15057             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15058             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15059                     _(cps->which), cps->program);
15060         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15061                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15062                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15063                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15064                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15065                 } else {
15066                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15067                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15068                     gameInfo.result = res;
15069                 }
15070                 gameInfo.resultDetails = StrSave(buf);
15071             }
15072             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15073             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15074         } else {
15075             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15076                     _(cps->which), cps->program);
15077             RemoveInputSource(cps->isr);
15078
15079             /* [AS] Program is misbehaving badly... kill it */
15080             if( count == -2 ) {
15081                 DestroyChildProcess( cps->pr, 9 );
15082                 cps->pr = NoProc;
15083             }
15084
15085             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15086         }
15087         return;
15088     }
15089
15090     if ((end_str = strchr(message, '\r')) != NULL)
15091       *end_str = NULLCHAR;
15092     if ((end_str = strchr(message, '\n')) != NULL)
15093       *end_str = NULLCHAR;
15094
15095     if (appData.debugMode) {
15096         TimeMark now; int print = 1;
15097         char *quote = ""; char c; int i;
15098
15099         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15100                 char start = message[0];
15101                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15102                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15103                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15104                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15105                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15106                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15107                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15108                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15109                    sscanf(message, "hint: %c", &c)!=1 && 
15110                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15111                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15112                     print = (appData.engineComments >= 2);
15113                 }
15114                 message[0] = start; // restore original message
15115         }
15116         if(print) {
15117                 GetTimeMark(&now);
15118                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15119                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15120                         quote,
15121                         message);
15122         }
15123     }
15124
15125     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15126     if (appData.icsEngineAnalyze) {
15127         if (strstr(message, "whisper") != NULL ||
15128              strstr(message, "kibitz") != NULL ||
15129             strstr(message, "tellics") != NULL) return;
15130     }
15131
15132     HandleMachineMove(message, cps);
15133 }
15134
15135
15136 void
15137 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15138 {
15139     char buf[MSG_SIZ];
15140     int seconds;
15141
15142     if( timeControl_2 > 0 ) {
15143         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15144             tc = timeControl_2;
15145         }
15146     }
15147     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15148     inc /= cps->timeOdds;
15149     st  /= cps->timeOdds;
15150
15151     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15152
15153     if (st > 0) {
15154       /* Set exact time per move, normally using st command */
15155       if (cps->stKludge) {
15156         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15157         seconds = st % 60;
15158         if (seconds == 0) {
15159           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15160         } else {
15161           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15162         }
15163       } else {
15164         snprintf(buf, MSG_SIZ, "st %d\n", st);
15165       }
15166     } else {
15167       /* Set conventional or incremental time control, using level command */
15168       if (seconds == 0) {
15169         /* Note old gnuchess bug -- minutes:seconds used to not work.
15170            Fixed in later versions, but still avoid :seconds
15171            when seconds is 0. */
15172         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15173       } else {
15174         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15175                  seconds, inc/1000.);
15176       }
15177     }
15178     SendToProgram(buf, cps);
15179
15180     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15181     /* Orthogonally, limit search to given depth */
15182     if (sd > 0) {
15183       if (cps->sdKludge) {
15184         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15185       } else {
15186         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15187       }
15188       SendToProgram(buf, cps);
15189     }
15190
15191     if(cps->nps >= 0) { /* [HGM] nps */
15192         if(cps->supportsNPS == FALSE)
15193           cps->nps = -1; // don't use if engine explicitly says not supported!
15194         else {
15195           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15196           SendToProgram(buf, cps);
15197         }
15198     }
15199 }
15200
15201 ChessProgramState *
15202 WhitePlayer ()
15203 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15204 {
15205     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15206        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15207         return &second;
15208     return &first;
15209 }
15210
15211 void
15212 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15213 {
15214     char message[MSG_SIZ];
15215     long time, otime;
15216
15217     /* Note: this routine must be called when the clocks are stopped
15218        or when they have *just* been set or switched; otherwise
15219        it will be off by the time since the current tick started.
15220     */
15221     if (machineWhite) {
15222         time = whiteTimeRemaining / 10;
15223         otime = blackTimeRemaining / 10;
15224     } else {
15225         time = blackTimeRemaining / 10;
15226         otime = whiteTimeRemaining / 10;
15227     }
15228     /* [HGM] translate opponent's time by time-odds factor */
15229     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15230
15231     if (time <= 0) time = 1;
15232     if (otime <= 0) otime = 1;
15233
15234     snprintf(message, MSG_SIZ, "time %ld\n", time);
15235     SendToProgram(message, cps);
15236
15237     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15238     SendToProgram(message, cps);
15239 }
15240
15241 int
15242 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15243 {
15244   char buf[MSG_SIZ];
15245   int len = strlen(name);
15246   int val;
15247
15248   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15249     (*p) += len + 1;
15250     sscanf(*p, "%d", &val);
15251     *loc = (val != 0);
15252     while (**p && **p != ' ')
15253       (*p)++;
15254     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15255     SendToProgram(buf, cps);
15256     return TRUE;
15257   }
15258   return FALSE;
15259 }
15260
15261 int
15262 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15263 {
15264   char buf[MSG_SIZ];
15265   int len = strlen(name);
15266   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15267     (*p) += len + 1;
15268     sscanf(*p, "%d", loc);
15269     while (**p && **p != ' ') (*p)++;
15270     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15271     SendToProgram(buf, cps);
15272     return TRUE;
15273   }
15274   return FALSE;
15275 }
15276
15277 int
15278 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15279 {
15280   char buf[MSG_SIZ];
15281   int len = strlen(name);
15282   if (strncmp((*p), name, len) == 0
15283       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15284     (*p) += len + 2;
15285     sscanf(*p, "%[^\"]", loc);
15286     while (**p && **p != '\"') (*p)++;
15287     if (**p == '\"') (*p)++;
15288     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15289     SendToProgram(buf, cps);
15290     return TRUE;
15291   }
15292   return FALSE;
15293 }
15294
15295 int
15296 ParseOption (Option *opt, ChessProgramState *cps)
15297 // [HGM] options: process the string that defines an engine option, and determine
15298 // name, type, default value, and allowed value range
15299 {
15300         char *p, *q, buf[MSG_SIZ];
15301         int n, min = (-1)<<31, max = 1<<31, def;
15302
15303         if(p = strstr(opt->name, " -spin ")) {
15304             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15305             if(max < min) max = min; // enforce consistency
15306             if(def < min) def = min;
15307             if(def > max) def = max;
15308             opt->value = def;
15309             opt->min = min;
15310             opt->max = max;
15311             opt->type = Spin;
15312         } else if((p = strstr(opt->name, " -slider "))) {
15313             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15314             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15315             if(max < min) max = min; // enforce consistency
15316             if(def < min) def = min;
15317             if(def > max) def = max;
15318             opt->value = def;
15319             opt->min = min;
15320             opt->max = max;
15321             opt->type = Spin; // Slider;
15322         } else if((p = strstr(opt->name, " -string "))) {
15323             opt->textValue = p+9;
15324             opt->type = TextBox;
15325         } else if((p = strstr(opt->name, " -file "))) {
15326             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15327             opt->textValue = p+7;
15328             opt->type = FileName; // FileName;
15329         } else if((p = strstr(opt->name, " -path "))) {
15330             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15331             opt->textValue = p+7;
15332             opt->type = PathName; // PathName;
15333         } else if(p = strstr(opt->name, " -check ")) {
15334             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15335             opt->value = (def != 0);
15336             opt->type = CheckBox;
15337         } else if(p = strstr(opt->name, " -combo ")) {
15338             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15339             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15340             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15341             opt->value = n = 0;
15342             while(q = StrStr(q, " /// ")) {
15343                 n++; *q = 0;    // count choices, and null-terminate each of them
15344                 q += 5;
15345                 if(*q == '*') { // remember default, which is marked with * prefix
15346                     q++;
15347                     opt->value = n;
15348                 }
15349                 cps->comboList[cps->comboCnt++] = q;
15350             }
15351             cps->comboList[cps->comboCnt++] = NULL;
15352             opt->max = n + 1;
15353             opt->type = ComboBox;
15354         } else if(p = strstr(opt->name, " -button")) {
15355             opt->type = Button;
15356         } else if(p = strstr(opt->name, " -save")) {
15357             opt->type = SaveButton;
15358         } else return FALSE;
15359         *p = 0; // terminate option name
15360         // now look if the command-line options define a setting for this engine option.
15361         if(cps->optionSettings && cps->optionSettings[0])
15362             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15363         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15364           snprintf(buf, MSG_SIZ, "option %s", p);
15365                 if(p = strstr(buf, ",")) *p = 0;
15366                 if(q = strchr(buf, '=')) switch(opt->type) {
15367                     case ComboBox:
15368                         for(n=0; n<opt->max; n++)
15369                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15370                         break;
15371                     case TextBox:
15372                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15373                         break;
15374                     case Spin:
15375                     case CheckBox:
15376                         opt->value = atoi(q+1);
15377                     default:
15378                         break;
15379                 }
15380                 strcat(buf, "\n");
15381                 SendToProgram(buf, cps);
15382         }
15383         return TRUE;
15384 }
15385
15386 void
15387 FeatureDone (ChessProgramState *cps, int val)
15388 {
15389   DelayedEventCallback cb = GetDelayedEvent();
15390   if ((cb == InitBackEnd3 && cps == &first) ||
15391       (cb == SettingsMenuIfReady && cps == &second) ||
15392       (cb == LoadEngine) ||
15393       (cb == TwoMachinesEventIfReady)) {
15394     CancelDelayedEvent();
15395     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15396   }
15397   cps->initDone = val;
15398 }
15399
15400 /* Parse feature command from engine */
15401 void
15402 ParseFeatures (char *args, ChessProgramState *cps)
15403 {
15404   char *p = args;
15405   char *q;
15406   int val;
15407   char buf[MSG_SIZ];
15408
15409   for (;;) {
15410     while (*p == ' ') p++;
15411     if (*p == NULLCHAR) return;
15412
15413     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15414     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15415     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15416     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15417     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15418     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15419     if (BoolFeature(&p, "reuse", &val, cps)) {
15420       /* Engine can disable reuse, but can't enable it if user said no */
15421       if (!val) cps->reuse = FALSE;
15422       continue;
15423     }
15424     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15425     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15426       if (gameMode == TwoMachinesPlay) {
15427         DisplayTwoMachinesTitle();
15428       } else {
15429         DisplayTitle("");
15430       }
15431       continue;
15432     }
15433     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15434     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15435     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15436     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15437     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15438     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15439     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15440     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15441     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15442     if (IntFeature(&p, "done", &val, cps)) {
15443       FeatureDone(cps, val);
15444       continue;
15445     }
15446     /* Added by Tord: */
15447     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15448     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15449     /* End of additions by Tord */
15450
15451     /* [HGM] added features: */
15452     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15453     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15454     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15455     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15456     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15457     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15458     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15459         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15460           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15461             SendToProgram(buf, cps);
15462             continue;
15463         }
15464         if(cps->nrOptions >= MAX_OPTIONS) {
15465             cps->nrOptions--;
15466             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15467             DisplayError(buf, 0);
15468         }
15469         continue;
15470     }
15471     /* End of additions by HGM */
15472
15473     /* unknown feature: complain and skip */
15474     q = p;
15475     while (*q && *q != '=') q++;
15476     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15477     SendToProgram(buf, cps);
15478     p = q;
15479     if (*p == '=') {
15480       p++;
15481       if (*p == '\"') {
15482         p++;
15483         while (*p && *p != '\"') p++;
15484         if (*p == '\"') p++;
15485       } else {
15486         while (*p && *p != ' ') p++;
15487       }
15488     }
15489   }
15490
15491 }
15492
15493 void
15494 PeriodicUpdatesEvent (int newState)
15495 {
15496     if (newState == appData.periodicUpdates)
15497       return;
15498
15499     appData.periodicUpdates=newState;
15500
15501     /* Display type changes, so update it now */
15502 //    DisplayAnalysis();
15503
15504     /* Get the ball rolling again... */
15505     if (newState) {
15506         AnalysisPeriodicEvent(1);
15507         StartAnalysisClock();
15508     }
15509 }
15510
15511 void
15512 PonderNextMoveEvent (int newState)
15513 {
15514     if (newState == appData.ponderNextMove) return;
15515     if (gameMode == EditPosition) EditPositionDone(TRUE);
15516     if (newState) {
15517         SendToProgram("hard\n", &first);
15518         if (gameMode == TwoMachinesPlay) {
15519             SendToProgram("hard\n", &second);
15520         }
15521     } else {
15522         SendToProgram("easy\n", &first);
15523         thinkOutput[0] = NULLCHAR;
15524         if (gameMode == TwoMachinesPlay) {
15525             SendToProgram("easy\n", &second);
15526         }
15527     }
15528     appData.ponderNextMove = newState;
15529 }
15530
15531 void
15532 NewSettingEvent (int option, int *feature, char *command, int value)
15533 {
15534     char buf[MSG_SIZ];
15535
15536     if (gameMode == EditPosition) EditPositionDone(TRUE);
15537     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15538     if(feature == NULL || *feature) SendToProgram(buf, &first);
15539     if (gameMode == TwoMachinesPlay) {
15540         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15541     }
15542 }
15543
15544 void
15545 ShowThinkingEvent ()
15546 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15547 {
15548     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15549     int newState = appData.showThinking
15550         // [HGM] thinking: other features now need thinking output as well
15551         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15552
15553     if (oldState == newState) return;
15554     oldState = newState;
15555     if (gameMode == EditPosition) EditPositionDone(TRUE);
15556     if (oldState) {
15557         SendToProgram("post\n", &first);
15558         if (gameMode == TwoMachinesPlay) {
15559             SendToProgram("post\n", &second);
15560         }
15561     } else {
15562         SendToProgram("nopost\n", &first);
15563         thinkOutput[0] = NULLCHAR;
15564         if (gameMode == TwoMachinesPlay) {
15565             SendToProgram("nopost\n", &second);
15566         }
15567     }
15568 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15569 }
15570
15571 void
15572 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15573 {
15574   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15575   if (pr == NoProc) return;
15576   AskQuestion(title, question, replyPrefix, pr);
15577 }
15578
15579 void
15580 TypeInEvent (char firstChar)
15581 {
15582     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15583         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15584         gameMode == AnalyzeMode || gameMode == EditGame || 
15585         gameMode == EditPosition || gameMode == IcsExamining ||
15586         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15587         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15588                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15589                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15590         gameMode == Training) PopUpMoveDialog(firstChar);
15591 }
15592
15593 void
15594 TypeInDoneEvent (char *move)
15595 {
15596         Board board;
15597         int n, fromX, fromY, toX, toY;
15598         char promoChar;
15599         ChessMove moveType;
15600
15601         // [HGM] FENedit
15602         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15603                 EditPositionPasteFEN(move);
15604                 return;
15605         }
15606         // [HGM] movenum: allow move number to be typed in any mode
15607         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15608           ToNrEvent(2*n-1);
15609           return;
15610         }
15611         // undocumented kludge: allow command-line option to be typed in!
15612         // (potentially fatal, and does not implement the effect of the option.)
15613         // should only be used for options that are values on which future decisions will be made,
15614         // and definitely not on options that would be used during initialization.
15615         if(strstr(move, "!!! -") == move) {
15616             ParseArgsFromString(move+4);
15617             return;
15618         }
15619
15620       if (gameMode != EditGame && currentMove != forwardMostMove && 
15621         gameMode != Training) {
15622         DisplayMoveError(_("Displayed move is not current"));
15623       } else {
15624         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15625           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15626         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15627         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15628           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15629           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15630         } else {
15631           DisplayMoveError(_("Could not parse move"));
15632         }
15633       }
15634 }
15635
15636 void
15637 DisplayMove (int moveNumber)
15638 {
15639     char message[MSG_SIZ];
15640     char res[MSG_SIZ];
15641     char cpThinkOutput[MSG_SIZ];
15642
15643     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15644
15645     if (moveNumber == forwardMostMove - 1 ||
15646         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15647
15648         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15649
15650         if (strchr(cpThinkOutput, '\n')) {
15651             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15652         }
15653     } else {
15654         *cpThinkOutput = NULLCHAR;
15655     }
15656
15657     /* [AS] Hide thinking from human user */
15658     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15659         *cpThinkOutput = NULLCHAR;
15660         if( thinkOutput[0] != NULLCHAR ) {
15661             int i;
15662
15663             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15664                 cpThinkOutput[i] = '.';
15665             }
15666             cpThinkOutput[i] = NULLCHAR;
15667             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15668         }
15669     }
15670
15671     if (moveNumber == forwardMostMove - 1 &&
15672         gameInfo.resultDetails != NULL) {
15673         if (gameInfo.resultDetails[0] == NULLCHAR) {
15674           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15675         } else {
15676           snprintf(res, MSG_SIZ, " {%s} %s",
15677                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15678         }
15679     } else {
15680         res[0] = NULLCHAR;
15681     }
15682
15683     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15684         DisplayMessage(res, cpThinkOutput);
15685     } else {
15686       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15687                 WhiteOnMove(moveNumber) ? " " : ".. ",
15688                 parseList[moveNumber], res);
15689         DisplayMessage(message, cpThinkOutput);
15690     }
15691 }
15692
15693 void
15694 DisplayComment (int moveNumber, char *text)
15695 {
15696     char title[MSG_SIZ];
15697
15698     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15699       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15700     } else {
15701       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15702               WhiteOnMove(moveNumber) ? " " : ".. ",
15703               parseList[moveNumber]);
15704     }
15705     if (text != NULL && (appData.autoDisplayComment || commentUp))
15706         CommentPopUp(title, text);
15707 }
15708
15709 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15710  * might be busy thinking or pondering.  It can be omitted if your
15711  * gnuchess is configured to stop thinking immediately on any user
15712  * input.  However, that gnuchess feature depends on the FIONREAD
15713  * ioctl, which does not work properly on some flavors of Unix.
15714  */
15715 void
15716 Attention (ChessProgramState *cps)
15717 {
15718 #if ATTENTION
15719     if (!cps->useSigint) return;
15720     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15721     switch (gameMode) {
15722       case MachinePlaysWhite:
15723       case MachinePlaysBlack:
15724       case TwoMachinesPlay:
15725       case IcsPlayingWhite:
15726       case IcsPlayingBlack:
15727       case AnalyzeMode:
15728       case AnalyzeFile:
15729         /* Skip if we know it isn't thinking */
15730         if (!cps->maybeThinking) return;
15731         if (appData.debugMode)
15732           fprintf(debugFP, "Interrupting %s\n", cps->which);
15733         InterruptChildProcess(cps->pr);
15734         cps->maybeThinking = FALSE;
15735         break;
15736       default:
15737         break;
15738     }
15739 #endif /*ATTENTION*/
15740 }
15741
15742 int
15743 CheckFlags ()
15744 {
15745     if (whiteTimeRemaining <= 0) {
15746         if (!whiteFlag) {
15747             whiteFlag = TRUE;
15748             if (appData.icsActive) {
15749                 if (appData.autoCallFlag &&
15750                     gameMode == IcsPlayingBlack && !blackFlag) {
15751                   SendToICS(ics_prefix);
15752                   SendToICS("flag\n");
15753                 }
15754             } else {
15755                 if (blackFlag) {
15756                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15757                 } else {
15758                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15759                     if (appData.autoCallFlag) {
15760                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15761                         return TRUE;
15762                     }
15763                 }
15764             }
15765         }
15766     }
15767     if (blackTimeRemaining <= 0) {
15768         if (!blackFlag) {
15769             blackFlag = TRUE;
15770             if (appData.icsActive) {
15771                 if (appData.autoCallFlag &&
15772                     gameMode == IcsPlayingWhite && !whiteFlag) {
15773                   SendToICS(ics_prefix);
15774                   SendToICS("flag\n");
15775                 }
15776             } else {
15777                 if (whiteFlag) {
15778                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15779                 } else {
15780                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15781                     if (appData.autoCallFlag) {
15782                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15783                         return TRUE;
15784                     }
15785                 }
15786             }
15787         }
15788     }
15789     return FALSE;
15790 }
15791
15792 void
15793 CheckTimeControl ()
15794 {
15795     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15796         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15797
15798     /*
15799      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15800      */
15801     if ( !WhiteOnMove(forwardMostMove) ) {
15802         /* White made time control */
15803         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15804         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15805         /* [HGM] time odds: correct new time quota for time odds! */
15806                                             / WhitePlayer()->timeOdds;
15807         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15808     } else {
15809         lastBlack -= blackTimeRemaining;
15810         /* Black made time control */
15811         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15812                                             / WhitePlayer()->other->timeOdds;
15813         lastWhite = whiteTimeRemaining;
15814     }
15815 }
15816
15817 void
15818 DisplayBothClocks ()
15819 {
15820     int wom = gameMode == EditPosition ?
15821       !blackPlaysFirst : WhiteOnMove(currentMove);
15822     DisplayWhiteClock(whiteTimeRemaining, wom);
15823     DisplayBlackClock(blackTimeRemaining, !wom);
15824 }
15825
15826
15827 /* Timekeeping seems to be a portability nightmare.  I think everyone
15828    has ftime(), but I'm really not sure, so I'm including some ifdefs
15829    to use other calls if you don't.  Clocks will be less accurate if
15830    you have neither ftime nor gettimeofday.
15831 */
15832
15833 /* VS 2008 requires the #include outside of the function */
15834 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15835 #include <sys/timeb.h>
15836 #endif
15837
15838 /* Get the current time as a TimeMark */
15839 void
15840 GetTimeMark (TimeMark *tm)
15841 {
15842 #if HAVE_GETTIMEOFDAY
15843
15844     struct timeval timeVal;
15845     struct timezone timeZone;
15846
15847     gettimeofday(&timeVal, &timeZone);
15848     tm->sec = (long) timeVal.tv_sec;
15849     tm->ms = (int) (timeVal.tv_usec / 1000L);
15850
15851 #else /*!HAVE_GETTIMEOFDAY*/
15852 #if HAVE_FTIME
15853
15854 // include <sys/timeb.h> / moved to just above start of function
15855     struct timeb timeB;
15856
15857     ftime(&timeB);
15858     tm->sec = (long) timeB.time;
15859     tm->ms = (int) timeB.millitm;
15860
15861 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15862     tm->sec = (long) time(NULL);
15863     tm->ms = 0;
15864 #endif
15865 #endif
15866 }
15867
15868 /* Return the difference in milliseconds between two
15869    time marks.  We assume the difference will fit in a long!
15870 */
15871 long
15872 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15873 {
15874     return 1000L*(tm2->sec - tm1->sec) +
15875            (long) (tm2->ms - tm1->ms);
15876 }
15877
15878
15879 /*
15880  * Code to manage the game clocks.
15881  *
15882  * In tournament play, black starts the clock and then white makes a move.
15883  * We give the human user a slight advantage if he is playing white---the
15884  * clocks don't run until he makes his first move, so it takes zero time.
15885  * Also, we don't account for network lag, so we could get out of sync
15886  * with GNU Chess's clock -- but then, referees are always right.
15887  */
15888
15889 static TimeMark tickStartTM;
15890 static long intendedTickLength;
15891
15892 long
15893 NextTickLength (long timeRemaining)
15894 {
15895     long nominalTickLength, nextTickLength;
15896
15897     if (timeRemaining > 0L && timeRemaining <= 10000L)
15898       nominalTickLength = 100L;
15899     else
15900       nominalTickLength = 1000L;
15901     nextTickLength = timeRemaining % nominalTickLength;
15902     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15903
15904     return nextTickLength;
15905 }
15906
15907 /* Adjust clock one minute up or down */
15908 void
15909 AdjustClock (Boolean which, int dir)
15910 {
15911     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15912     if(which) blackTimeRemaining += 60000*dir;
15913     else      whiteTimeRemaining += 60000*dir;
15914     DisplayBothClocks();
15915     adjustedClock = TRUE;
15916 }
15917
15918 /* Stop clocks and reset to a fresh time control */
15919 void
15920 ResetClocks ()
15921 {
15922     (void) StopClockTimer();
15923     if (appData.icsActive) {
15924         whiteTimeRemaining = blackTimeRemaining = 0;
15925     } else if (searchTime) {
15926         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15927         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15928     } else { /* [HGM] correct new time quote for time odds */
15929         whiteTC = blackTC = fullTimeControlString;
15930         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15931         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15932     }
15933     if (whiteFlag || blackFlag) {
15934         DisplayTitle("");
15935         whiteFlag = blackFlag = FALSE;
15936     }
15937     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15938     DisplayBothClocks();
15939     adjustedClock = FALSE;
15940 }
15941
15942 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15943
15944 /* Decrement running clock by amount of time that has passed */
15945 void
15946 DecrementClocks ()
15947 {
15948     long timeRemaining;
15949     long lastTickLength, fudge;
15950     TimeMark now;
15951
15952     if (!appData.clockMode) return;
15953     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15954
15955     GetTimeMark(&now);
15956
15957     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15958
15959     /* Fudge if we woke up a little too soon */
15960     fudge = intendedTickLength - lastTickLength;
15961     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15962
15963     if (WhiteOnMove(forwardMostMove)) {
15964         if(whiteNPS >= 0) lastTickLength = 0;
15965         timeRemaining = whiteTimeRemaining -= lastTickLength;
15966         if(timeRemaining < 0 && !appData.icsActive) {
15967             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15968             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15969                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15970                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15971             }
15972         }
15973         DisplayWhiteClock(whiteTimeRemaining - fudge,
15974                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15975     } else {
15976         if(blackNPS >= 0) lastTickLength = 0;
15977         timeRemaining = blackTimeRemaining -= lastTickLength;
15978         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15979             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15980             if(suddenDeath) {
15981                 blackStartMove = forwardMostMove;
15982                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15983             }
15984         }
15985         DisplayBlackClock(blackTimeRemaining - fudge,
15986                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15987     }
15988     if (CheckFlags()) return;
15989
15990     tickStartTM = now;
15991     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15992     StartClockTimer(intendedTickLength);
15993
15994     /* if the time remaining has fallen below the alarm threshold, sound the
15995      * alarm. if the alarm has sounded and (due to a takeback or time control
15996      * with increment) the time remaining has increased to a level above the
15997      * threshold, reset the alarm so it can sound again.
15998      */
15999
16000     if (appData.icsActive && appData.icsAlarm) {
16001
16002         /* make sure we are dealing with the user's clock */
16003         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16004                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16005            )) return;
16006
16007         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16008             alarmSounded = FALSE;
16009         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16010             PlayAlarmSound();
16011             alarmSounded = TRUE;
16012         }
16013     }
16014 }
16015
16016
16017 /* A player has just moved, so stop the previously running
16018    clock and (if in clock mode) start the other one.
16019    We redisplay both clocks in case we're in ICS mode, because
16020    ICS gives us an update to both clocks after every move.
16021    Note that this routine is called *after* forwardMostMove
16022    is updated, so the last fractional tick must be subtracted
16023    from the color that is *not* on move now.
16024 */
16025 void
16026 SwitchClocks (int newMoveNr)
16027 {
16028     long lastTickLength;
16029     TimeMark now;
16030     int flagged = FALSE;
16031
16032     GetTimeMark(&now);
16033
16034     if (StopClockTimer() && appData.clockMode) {
16035         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16036         if (!WhiteOnMove(forwardMostMove)) {
16037             if(blackNPS >= 0) lastTickLength = 0;
16038             blackTimeRemaining -= lastTickLength;
16039            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16040 //         if(pvInfoList[forwardMostMove].time == -1)
16041                  pvInfoList[forwardMostMove].time =               // use GUI time
16042                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16043         } else {
16044            if(whiteNPS >= 0) lastTickLength = 0;
16045            whiteTimeRemaining -= lastTickLength;
16046            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16047 //         if(pvInfoList[forwardMostMove].time == -1)
16048                  pvInfoList[forwardMostMove].time =
16049                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16050         }
16051         flagged = CheckFlags();
16052     }
16053     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16054     CheckTimeControl();
16055
16056     if (flagged || !appData.clockMode) return;
16057
16058     switch (gameMode) {
16059       case MachinePlaysBlack:
16060       case MachinePlaysWhite:
16061       case BeginningOfGame:
16062         if (pausing) return;
16063         break;
16064
16065       case EditGame:
16066       case PlayFromGameFile:
16067       case IcsExamining:
16068         return;
16069
16070       default:
16071         break;
16072     }
16073
16074     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16075         if(WhiteOnMove(forwardMostMove))
16076              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16077         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16078     }
16079
16080     tickStartTM = now;
16081     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16082       whiteTimeRemaining : blackTimeRemaining);
16083     StartClockTimer(intendedTickLength);
16084 }
16085
16086
16087 /* Stop both clocks */
16088 void
16089 StopClocks ()
16090 {
16091     long lastTickLength;
16092     TimeMark now;
16093
16094     if (!StopClockTimer()) return;
16095     if (!appData.clockMode) return;
16096
16097     GetTimeMark(&now);
16098
16099     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16100     if (WhiteOnMove(forwardMostMove)) {
16101         if(whiteNPS >= 0) lastTickLength = 0;
16102         whiteTimeRemaining -= lastTickLength;
16103         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16104     } else {
16105         if(blackNPS >= 0) lastTickLength = 0;
16106         blackTimeRemaining -= lastTickLength;
16107         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16108     }
16109     CheckFlags();
16110 }
16111
16112 /* Start clock of player on move.  Time may have been reset, so
16113    if clock is already running, stop and restart it. */
16114 void
16115 StartClocks ()
16116 {
16117     (void) StopClockTimer(); /* in case it was running already */
16118     DisplayBothClocks();
16119     if (CheckFlags()) return;
16120
16121     if (!appData.clockMode) return;
16122     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16123
16124     GetTimeMark(&tickStartTM);
16125     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16126       whiteTimeRemaining : blackTimeRemaining);
16127
16128    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16129     whiteNPS = blackNPS = -1;
16130     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16131        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16132         whiteNPS = first.nps;
16133     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16134        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16135         blackNPS = first.nps;
16136     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16137         whiteNPS = second.nps;
16138     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16139         blackNPS = second.nps;
16140     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16141
16142     StartClockTimer(intendedTickLength);
16143 }
16144
16145 char *
16146 TimeString (long ms)
16147 {
16148     long second, minute, hour, day;
16149     char *sign = "";
16150     static char buf[32];
16151
16152     if (ms > 0 && ms <= 9900) {
16153       /* convert milliseconds to tenths, rounding up */
16154       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16155
16156       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16157       return buf;
16158     }
16159
16160     /* convert milliseconds to seconds, rounding up */
16161     /* use floating point to avoid strangeness of integer division
16162        with negative dividends on many machines */
16163     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16164
16165     if (second < 0) {
16166         sign = "-";
16167         second = -second;
16168     }
16169
16170     day = second / (60 * 60 * 24);
16171     second = second % (60 * 60 * 24);
16172     hour = second / (60 * 60);
16173     second = second % (60 * 60);
16174     minute = second / 60;
16175     second = second % 60;
16176
16177     if (day > 0)
16178       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16179               sign, day, hour, minute, second);
16180     else if (hour > 0)
16181       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16182     else
16183       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16184
16185     return buf;
16186 }
16187
16188
16189 /*
16190  * This is necessary because some C libraries aren't ANSI C compliant yet.
16191  */
16192 char *
16193 StrStr (char *string, char *match)
16194 {
16195     int i, length;
16196
16197     length = strlen(match);
16198
16199     for (i = strlen(string) - length; i >= 0; i--, string++)
16200       if (!strncmp(match, string, length))
16201         return string;
16202
16203     return NULL;
16204 }
16205
16206 char *
16207 StrCaseStr (char *string, char *match)
16208 {
16209     int i, j, length;
16210
16211     length = strlen(match);
16212
16213     for (i = strlen(string) - length; i >= 0; i--, string++) {
16214         for (j = 0; j < length; j++) {
16215             if (ToLower(match[j]) != ToLower(string[j]))
16216               break;
16217         }
16218         if (j == length) return string;
16219     }
16220
16221     return NULL;
16222 }
16223
16224 #ifndef _amigados
16225 int
16226 StrCaseCmp (char *s1, char *s2)
16227 {
16228     char c1, c2;
16229
16230     for (;;) {
16231         c1 = ToLower(*s1++);
16232         c2 = ToLower(*s2++);
16233         if (c1 > c2) return 1;
16234         if (c1 < c2) return -1;
16235         if (c1 == NULLCHAR) return 0;
16236     }
16237 }
16238
16239
16240 int
16241 ToLower (int c)
16242 {
16243     return isupper(c) ? tolower(c) : c;
16244 }
16245
16246
16247 int
16248 ToUpper (int c)
16249 {
16250     return islower(c) ? toupper(c) : c;
16251 }
16252 #endif /* !_amigados    */
16253
16254 char *
16255 StrSave (char *s)
16256 {
16257   char *ret;
16258
16259   if ((ret = (char *) malloc(strlen(s) + 1)))
16260     {
16261       safeStrCpy(ret, s, strlen(s)+1);
16262     }
16263   return ret;
16264 }
16265
16266 char *
16267 StrSavePtr (char *s, char **savePtr)
16268 {
16269     if (*savePtr) {
16270         free(*savePtr);
16271     }
16272     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16273       safeStrCpy(*savePtr, s, strlen(s)+1);
16274     }
16275     return(*savePtr);
16276 }
16277
16278 char *
16279 PGNDate ()
16280 {
16281     time_t clock;
16282     struct tm *tm;
16283     char buf[MSG_SIZ];
16284
16285     clock = time((time_t *)NULL);
16286     tm = localtime(&clock);
16287     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16288             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16289     return StrSave(buf);
16290 }
16291
16292
16293 char *
16294 PositionToFEN (int move, char *overrideCastling)
16295 {
16296     int i, j, fromX, fromY, toX, toY;
16297     int whiteToPlay;
16298     char buf[MSG_SIZ];
16299     char *p, *q;
16300     int emptycount;
16301     ChessSquare piece;
16302
16303     whiteToPlay = (gameMode == EditPosition) ?
16304       !blackPlaysFirst : (move % 2 == 0);
16305     p = buf;
16306
16307     /* Piece placement data */
16308     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16309         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16310         emptycount = 0;
16311         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16312             if (boards[move][i][j] == EmptySquare) {
16313                 emptycount++;
16314             } else { ChessSquare piece = boards[move][i][j];
16315                 if (emptycount > 0) {
16316                     if(emptycount<10) /* [HGM] can be >= 10 */
16317                         *p++ = '0' + emptycount;
16318                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16319                     emptycount = 0;
16320                 }
16321                 if(PieceToChar(piece) == '+') {
16322                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16323                     *p++ = '+';
16324                     piece = (ChessSquare)(DEMOTED piece);
16325                 }
16326                 *p++ = PieceToChar(piece);
16327                 if(p[-1] == '~') {
16328                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16329                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16330                     *p++ = '~';
16331                 }
16332             }
16333         }
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         *p++ = '/';
16341     }
16342     *(p - 1) = ' ';
16343
16344     /* [HGM] print Crazyhouse or Shogi holdings */
16345     if( gameInfo.holdingsWidth ) {
16346         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16347         q = p;
16348         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16349             piece = boards[move][i][BOARD_WIDTH-1];
16350             if( piece != EmptySquare )
16351               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16352                   *p++ = PieceToChar(piece);
16353         }
16354         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16355             piece = boards[move][BOARD_HEIGHT-i-1][0];
16356             if( piece != EmptySquare )
16357               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16358                   *p++ = PieceToChar(piece);
16359         }
16360
16361         if( q == p ) *p++ = '-';
16362         *p++ = ']';
16363         *p++ = ' ';
16364     }
16365
16366     /* Active color */
16367     *p++ = whiteToPlay ? 'w' : 'b';
16368     *p++ = ' ';
16369
16370   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16371     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16372   } else {
16373   if(nrCastlingRights) {
16374      q = p;
16375      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16376        /* [HGM] write directly from rights */
16377            if(boards[move][CASTLING][2] != NoRights &&
16378               boards[move][CASTLING][0] != NoRights   )
16379                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16380            if(boards[move][CASTLING][2] != NoRights &&
16381               boards[move][CASTLING][1] != NoRights   )
16382                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16383            if(boards[move][CASTLING][5] != NoRights &&
16384               boards[move][CASTLING][3] != NoRights   )
16385                 *p++ = boards[move][CASTLING][3] + AAA;
16386            if(boards[move][CASTLING][5] != NoRights &&
16387               boards[move][CASTLING][4] != NoRights   )
16388                 *p++ = boards[move][CASTLING][4] + AAA;
16389      } else {
16390
16391         /* [HGM] write true castling rights */
16392         if( nrCastlingRights == 6 ) {
16393             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16394                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16395             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16396                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16397             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16398                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16399             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16400                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16401         }
16402      }
16403      if (q == p) *p++ = '-'; /* No castling rights */
16404      *p++ = ' ';
16405   }
16406
16407   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16408      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16409     /* En passant target square */
16410     if (move > backwardMostMove) {
16411         fromX = moveList[move - 1][0] - AAA;
16412         fromY = moveList[move - 1][1] - ONE;
16413         toX = moveList[move - 1][2] - AAA;
16414         toY = moveList[move - 1][3] - ONE;
16415         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16416             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16417             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16418             fromX == toX) {
16419             /* 2-square pawn move just happened */
16420             *p++ = toX + AAA;
16421             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16422         } else {
16423             *p++ = '-';
16424         }
16425     } else if(move == backwardMostMove) {
16426         // [HGM] perhaps we should always do it like this, and forget the above?
16427         if((signed char)boards[move][EP_STATUS] >= 0) {
16428             *p++ = boards[move][EP_STATUS] + AAA;
16429             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16430         } else {
16431             *p++ = '-';
16432         }
16433     } else {
16434         *p++ = '-';
16435     }
16436     *p++ = ' ';
16437   }
16438   }
16439
16440     /* [HGM] find reversible plies */
16441     {   int i = 0, j=move;
16442
16443         if (appData.debugMode) { int k;
16444             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16445             for(k=backwardMostMove; k<=forwardMostMove; k++)
16446                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16447
16448         }
16449
16450         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16451         if( j == backwardMostMove ) i += initialRulePlies;
16452         sprintf(p, "%d ", i);
16453         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16454     }
16455     /* Fullmove number */
16456     sprintf(p, "%d", (move / 2) + 1);
16457
16458     return StrSave(buf);
16459 }
16460
16461 Boolean
16462 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16463 {
16464     int i, j;
16465     char *p, c;
16466     int emptycount;
16467     ChessSquare piece;
16468
16469     p = fen;
16470
16471     /* [HGM] by default clear Crazyhouse holdings, if present */
16472     if(gameInfo.holdingsWidth) {
16473        for(i=0; i<BOARD_HEIGHT; i++) {
16474            board[i][0]             = EmptySquare; /* black holdings */
16475            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16476            board[i][1]             = (ChessSquare) 0; /* black counts */
16477            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16478        }
16479     }
16480
16481     /* Piece placement data */
16482     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16483         j = 0;
16484         for (;;) {
16485             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16486                 if (*p == '/') p++;
16487                 emptycount = gameInfo.boardWidth - j;
16488                 while (emptycount--)
16489                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16490                 break;
16491 #if(BOARD_FILES >= 10)
16492             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16493                 p++; emptycount=10;
16494                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16495                 while (emptycount--)
16496                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16497 #endif
16498             } else if (isdigit(*p)) {
16499                 emptycount = *p++ - '0';
16500                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16501                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16502                 while (emptycount--)
16503                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16504             } else if (*p == '+' || isalpha(*p)) {
16505                 if (j >= gameInfo.boardWidth) return FALSE;
16506                 if(*p=='+') {
16507                     piece = CharToPiece(*++p);
16508                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16509                     piece = (ChessSquare) (PROMOTED piece ); p++;
16510                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16511                 } else piece = CharToPiece(*p++);
16512
16513                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16514                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16515                     piece = (ChessSquare) (PROMOTED piece);
16516                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16517                     p++;
16518                 }
16519                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16520             } else {
16521                 return FALSE;
16522             }
16523         }
16524     }
16525     while (*p == '/' || *p == ' ') p++;
16526
16527     /* [HGM] look for Crazyhouse holdings here */
16528     while(*p==' ') p++;
16529     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16530         if(*p == '[') p++;
16531         if(*p == '-' ) p++; /* empty holdings */ else {
16532             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16533             /* if we would allow FEN reading to set board size, we would   */
16534             /* have to add holdings and shift the board read so far here   */
16535             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16536                 p++;
16537                 if((int) piece >= (int) BlackPawn ) {
16538                     i = (int)piece - (int)BlackPawn;
16539                     i = PieceToNumber((ChessSquare)i);
16540                     if( i >= gameInfo.holdingsSize ) return FALSE;
16541                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16542                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16543                 } else {
16544                     i = (int)piece - (int)WhitePawn;
16545                     i = PieceToNumber((ChessSquare)i);
16546                     if( i >= gameInfo.holdingsSize ) return FALSE;
16547                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16548                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16549                 }
16550             }
16551         }
16552         if(*p == ']') p++;
16553     }
16554
16555     while(*p == ' ') p++;
16556
16557     /* Active color */
16558     c = *p++;
16559     if(appData.colorNickNames) {
16560       if( c == appData.colorNickNames[0] ) c = 'w'; else
16561       if( c == appData.colorNickNames[1] ) c = 'b';
16562     }
16563     switch (c) {
16564       case 'w':
16565         *blackPlaysFirst = FALSE;
16566         break;
16567       case 'b':
16568         *blackPlaysFirst = TRUE;
16569         break;
16570       default:
16571         return FALSE;
16572     }
16573
16574     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16575     /* return the extra info in global variiables             */
16576
16577     /* set defaults in case FEN is incomplete */
16578     board[EP_STATUS] = EP_UNKNOWN;
16579     for(i=0; i<nrCastlingRights; i++ ) {
16580         board[CASTLING][i] =
16581             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16582     }   /* assume possible unless obviously impossible */
16583     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16584     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16585     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16586                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16587     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16588     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16589     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16590                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16591     FENrulePlies = 0;
16592
16593     while(*p==' ') p++;
16594     if(nrCastlingRights) {
16595       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16596           /* castling indicator present, so default becomes no castlings */
16597           for(i=0; i<nrCastlingRights; i++ ) {
16598                  board[CASTLING][i] = NoRights;
16599           }
16600       }
16601       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16602              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16603              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16604              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16605         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16606
16607         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16608             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16609             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16610         }
16611         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16612             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16613         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16614                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16615         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16616                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16617         switch(c) {
16618           case'K':
16619               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16620               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16621               board[CASTLING][2] = whiteKingFile;
16622               break;
16623           case'Q':
16624               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16625               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16626               board[CASTLING][2] = whiteKingFile;
16627               break;
16628           case'k':
16629               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16630               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16631               board[CASTLING][5] = blackKingFile;
16632               break;
16633           case'q':
16634               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16635               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16636               board[CASTLING][5] = blackKingFile;
16637           case '-':
16638               break;
16639           default: /* FRC castlings */
16640               if(c >= 'a') { /* black rights */
16641                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16642                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16643                   if(i == BOARD_RGHT) break;
16644                   board[CASTLING][5] = i;
16645                   c -= AAA;
16646                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16647                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16648                   if(c > i)
16649                       board[CASTLING][3] = c;
16650                   else
16651                       board[CASTLING][4] = c;
16652               } else { /* white rights */
16653                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16654                     if(board[0][i] == WhiteKing) break;
16655                   if(i == BOARD_RGHT) break;
16656                   board[CASTLING][2] = i;
16657                   c -= AAA - 'a' + 'A';
16658                   if(board[0][c] >= WhiteKing) break;
16659                   if(c > i)
16660                       board[CASTLING][0] = c;
16661                   else
16662                       board[CASTLING][1] = c;
16663               }
16664         }
16665       }
16666       for(i=0; i<nrCastlingRights; i++)
16667         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16668     if (appData.debugMode) {
16669         fprintf(debugFP, "FEN castling rights:");
16670         for(i=0; i<nrCastlingRights; i++)
16671         fprintf(debugFP, " %d", board[CASTLING][i]);
16672         fprintf(debugFP, "\n");
16673     }
16674
16675       while(*p==' ') p++;
16676     }
16677
16678     /* read e.p. field in games that know e.p. capture */
16679     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16680        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16681       if(*p=='-') {
16682         p++; board[EP_STATUS] = EP_NONE;
16683       } else {
16684          char c = *p++ - AAA;
16685
16686          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16687          if(*p >= '0' && *p <='9') p++;
16688          board[EP_STATUS] = c;
16689       }
16690     }
16691
16692
16693     if(sscanf(p, "%d", &i) == 1) {
16694         FENrulePlies = i; /* 50-move ply counter */
16695         /* (The move number is still ignored)    */
16696     }
16697
16698     return TRUE;
16699 }
16700
16701 void
16702 EditPositionPasteFEN (char *fen)
16703 {
16704   if (fen != NULL) {
16705     Board initial_position;
16706
16707     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16708       DisplayError(_("Bad FEN position in clipboard"), 0);
16709       return ;
16710     } else {
16711       int savedBlackPlaysFirst = blackPlaysFirst;
16712       EditPositionEvent();
16713       blackPlaysFirst = savedBlackPlaysFirst;
16714       CopyBoard(boards[0], initial_position);
16715       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16716       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16717       DisplayBothClocks();
16718       DrawPosition(FALSE, boards[currentMove]);
16719     }
16720   }
16721 }
16722
16723 static char cseq[12] = "\\   ";
16724
16725 Boolean
16726 set_cont_sequence (char *new_seq)
16727 {
16728     int len;
16729     Boolean ret;
16730
16731     // handle bad attempts to set the sequence
16732         if (!new_seq)
16733                 return 0; // acceptable error - no debug
16734
16735     len = strlen(new_seq);
16736     ret = (len > 0) && (len < sizeof(cseq));
16737     if (ret)
16738       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16739     else if (appData.debugMode)
16740       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16741     return ret;
16742 }
16743
16744 /*
16745     reformat a source message so words don't cross the width boundary.  internal
16746     newlines are not removed.  returns the wrapped size (no null character unless
16747     included in source message).  If dest is NULL, only calculate the size required
16748     for the dest buffer.  lp argument indicats line position upon entry, and it's
16749     passed back upon exit.
16750 */
16751 int
16752 wrap (char *dest, char *src, int count, int width, int *lp)
16753 {
16754     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16755
16756     cseq_len = strlen(cseq);
16757     old_line = line = *lp;
16758     ansi = len = clen = 0;
16759
16760     for (i=0; i < count; i++)
16761     {
16762         if (src[i] == '\033')
16763             ansi = 1;
16764
16765         // if we hit the width, back up
16766         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16767         {
16768             // store i & len in case the word is too long
16769             old_i = i, old_len = len;
16770
16771             // find the end of the last word
16772             while (i && src[i] != ' ' && src[i] != '\n')
16773             {
16774                 i--;
16775                 len--;
16776             }
16777
16778             // word too long?  restore i & len before splitting it
16779             if ((old_i-i+clen) >= width)
16780             {
16781                 i = old_i;
16782                 len = old_len;
16783             }
16784
16785             // extra space?
16786             if (i && src[i-1] == ' ')
16787                 len--;
16788
16789             if (src[i] != ' ' && src[i] != '\n')
16790             {
16791                 i--;
16792                 if (len)
16793                     len--;
16794             }
16795
16796             // now append the newline and continuation sequence
16797             if (dest)
16798                 dest[len] = '\n';
16799             len++;
16800             if (dest)
16801                 strncpy(dest+len, cseq, cseq_len);
16802             len += cseq_len;
16803             line = cseq_len;
16804             clen = cseq_len;
16805             continue;
16806         }
16807
16808         if (dest)
16809             dest[len] = src[i];
16810         len++;
16811         if (!ansi)
16812             line++;
16813         if (src[i] == '\n')
16814             line = 0;
16815         if (src[i] == 'm')
16816             ansi = 0;
16817     }
16818     if (dest && appData.debugMode)
16819     {
16820         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16821             count, width, line, len, *lp);
16822         show_bytes(debugFP, src, count);
16823         fprintf(debugFP, "\ndest: ");
16824         show_bytes(debugFP, dest, len);
16825         fprintf(debugFP, "\n");
16826     }
16827     *lp = dest ? line : old_line;
16828
16829     return len;
16830 }
16831
16832 // [HGM] vari: routines for shelving variations
16833 Boolean modeRestore = FALSE;
16834
16835 void
16836 PushInner (int firstMove, int lastMove)
16837 {
16838         int i, j, nrMoves = lastMove - firstMove;
16839
16840         // push current tail of game on stack
16841         savedResult[storedGames] = gameInfo.result;
16842         savedDetails[storedGames] = gameInfo.resultDetails;
16843         gameInfo.resultDetails = NULL;
16844         savedFirst[storedGames] = firstMove;
16845         savedLast [storedGames] = lastMove;
16846         savedFramePtr[storedGames] = framePtr;
16847         framePtr -= nrMoves; // reserve space for the boards
16848         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16849             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16850             for(j=0; j<MOVE_LEN; j++)
16851                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16852             for(j=0; j<2*MOVE_LEN; j++)
16853                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16854             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16855             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16856             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16857             pvInfoList[firstMove+i-1].depth = 0;
16858             commentList[framePtr+i] = commentList[firstMove+i];
16859             commentList[firstMove+i] = NULL;
16860         }
16861
16862         storedGames++;
16863         forwardMostMove = firstMove; // truncate game so we can start variation
16864 }
16865
16866 void
16867 PushTail (int firstMove, int lastMove)
16868 {
16869         if(appData.icsActive) { // only in local mode
16870                 forwardMostMove = currentMove; // mimic old ICS behavior
16871                 return;
16872         }
16873         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16874
16875         PushInner(firstMove, lastMove);
16876         if(storedGames == 1) GreyRevert(FALSE);
16877         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16878 }
16879
16880 void
16881 PopInner (Boolean annotate)
16882 {
16883         int i, j, nrMoves;
16884         char buf[8000], moveBuf[20];
16885
16886         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16887         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16888         nrMoves = savedLast[storedGames] - currentMove;
16889         if(annotate) {
16890                 int cnt = 10;
16891                 if(!WhiteOnMove(currentMove))
16892                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16893                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16894                 for(i=currentMove; i<forwardMostMove; i++) {
16895                         if(WhiteOnMove(i))
16896                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16897                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16898                         strcat(buf, moveBuf);
16899                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16900                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16901                 }
16902                 strcat(buf, ")");
16903         }
16904         for(i=1; i<=nrMoves; i++) { // copy last variation back
16905             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16906             for(j=0; j<MOVE_LEN; j++)
16907                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16908             for(j=0; j<2*MOVE_LEN; j++)
16909                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16910             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16911             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16912             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16913             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16914             commentList[currentMove+i] = commentList[framePtr+i];
16915             commentList[framePtr+i] = NULL;
16916         }
16917         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16918         framePtr = savedFramePtr[storedGames];
16919         gameInfo.result = savedResult[storedGames];
16920         if(gameInfo.resultDetails != NULL) {
16921             free(gameInfo.resultDetails);
16922       }
16923         gameInfo.resultDetails = savedDetails[storedGames];
16924         forwardMostMove = currentMove + nrMoves;
16925 }
16926
16927 Boolean
16928 PopTail (Boolean annotate)
16929 {
16930         if(appData.icsActive) return FALSE; // only in local mode
16931         if(!storedGames) return FALSE; // sanity
16932         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16933
16934         PopInner(annotate);
16935         if(currentMove < forwardMostMove) ForwardEvent(); else
16936         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16937
16938         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16939         return TRUE;
16940 }
16941
16942 void
16943 CleanupTail ()
16944 {       // remove all shelved variations
16945         int i;
16946         for(i=0; i<storedGames; i++) {
16947             if(savedDetails[i])
16948                 free(savedDetails[i]);
16949             savedDetails[i] = NULL;
16950         }
16951         for(i=framePtr; i<MAX_MOVES; i++) {
16952                 if(commentList[i]) free(commentList[i]);
16953                 commentList[i] = NULL;
16954         }
16955         framePtr = MAX_MOVES-1;
16956         storedGames = 0;
16957 }
16958
16959 void
16960 LoadVariation (int index, char *text)
16961 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16962         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16963         int level = 0, move;
16964
16965         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16966         // first find outermost bracketing variation
16967         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16968             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16969                 if(*p == '{') wait = '}'; else
16970                 if(*p == '[') wait = ']'; else
16971                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16972                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16973             }
16974             if(*p == wait) wait = NULLCHAR; // closing ]} found
16975             p++;
16976         }
16977         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16978         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16979         end[1] = NULLCHAR; // clip off comment beyond variation
16980         ToNrEvent(currentMove-1);
16981         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16982         // kludge: use ParsePV() to append variation to game
16983         move = currentMove;
16984         ParsePV(start, TRUE, TRUE);
16985         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16986         ClearPremoveHighlights();
16987         CommentPopDown();
16988         ToNrEvent(currentMove+1);
16989 }
16990