eee690b0a92d83e067283f8fe7b7d83aacfe028b
[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   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = _(engineNames[n]);
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091         if( (len >= MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 void
1489 InitBackEnd3 P((void))
1490 {
1491     GameMode initialMode;
1492     char buf[MSG_SIZ];
1493     int err, len;
1494
1495     InitChessProgram(&first, startedFromSetupPosition);
1496
1497     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1498         free(programVersion);
1499         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1500         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1501         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1502     }
1503
1504     if (appData.icsActive) {
1505 #ifdef WIN32
1506         /* [DM] Make a console window if needed [HGM] merged ifs */
1507         ConsoleCreate();
1508 #endif
1509         err = establish();
1510         if (err != 0)
1511           {
1512             if (*appData.icsCommPort != NULLCHAR)
1513               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1514                              appData.icsCommPort);
1515             else
1516               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1517                         appData.icsHost, appData.icsPort);
1518
1519             if( (len >= MSG_SIZ) && appData.debugMode )
1520               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1521
1522             DisplayFatalError(buf, err, 1);
1523             return;
1524         }
1525         SetICSMode();
1526         telnetISR =
1527           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1528         fromUserISR =
1529           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1530         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1531             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1532     } else if (appData.noChessProgram) {
1533         SetNCPMode();
1534     } else {
1535         SetGNUMode();
1536     }
1537
1538     if (*appData.cmailGameName != NULLCHAR) {
1539         SetCmailMode();
1540         OpenLoopback(&cmailPR);
1541         cmailISR =
1542           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1543     }
1544
1545     ThawUI();
1546     DisplayMessage("", "");
1547     if (StrCaseCmp(appData.initialMode, "") == 0) {
1548       initialMode = BeginningOfGame;
1549       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1550         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1551         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1552         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1553         ModeHighlight();
1554       }
1555     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1556       initialMode = TwoMachinesPlay;
1557     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1558       initialMode = AnalyzeFile;
1559     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1560       initialMode = AnalyzeMode;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1562       initialMode = MachinePlaysWhite;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1564       initialMode = MachinePlaysBlack;
1565     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1566       initialMode = EditGame;
1567     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1568       initialMode = EditPosition;
1569     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1570       initialMode = Training;
1571     } else {
1572       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1573       if( (len >= MSG_SIZ) && appData.debugMode )
1574         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1575
1576       DisplayFatalError(buf, 0, 2);
1577       return;
1578     }
1579
1580     if (appData.matchMode) {
1581         if(appData.tourneyFile[0]) { // start tourney from command line
1582             FILE *f;
1583             if(f = fopen(appData.tourneyFile, "r")) {
1584                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1585                 fclose(f);
1586                 appData.clockMode = TRUE;
1587                 SetGNUMode();
1588             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1589         }
1590         MatchEvent(TRUE);
1591     } else if (*appData.cmailGameName != NULLCHAR) {
1592         /* Set up cmail mode */
1593         ReloadCmailMsgEvent(TRUE);
1594     } else {
1595         /* Set up other modes */
1596         if (initialMode == AnalyzeFile) {
1597           if (*appData.loadGameFile == NULLCHAR) {
1598             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1599             return;
1600           }
1601         }
1602         if (*appData.loadGameFile != NULLCHAR) {
1603             (void) LoadGameFromFile(appData.loadGameFile,
1604                                     appData.loadGameIndex,
1605                                     appData.loadGameFile, TRUE);
1606         } else if (*appData.loadPositionFile != NULLCHAR) {
1607             (void) LoadPositionFromFile(appData.loadPositionFile,
1608                                         appData.loadPositionIndex,
1609                                         appData.loadPositionFile);
1610             /* [HGM] try to make self-starting even after FEN load */
1611             /* to allow automatic setup of fairy variants with wtm */
1612             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1613                 gameMode = BeginningOfGame;
1614                 setboardSpoiledMachineBlack = 1;
1615             }
1616             /* [HGM] loadPos: make that every new game uses the setup */
1617             /* from file as long as we do not switch variant          */
1618             if(!blackPlaysFirst) {
1619                 startedFromPositionFile = TRUE;
1620                 CopyBoard(filePosition, boards[0]);
1621             }
1622         }
1623         if (initialMode == AnalyzeMode) {
1624           if (appData.noChessProgram) {
1625             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1626             return;
1627           }
1628           if (appData.icsActive) {
1629             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1630             return;
1631           }
1632           AnalyzeModeEvent();
1633         } else if (initialMode == AnalyzeFile) {
1634           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1635           ShowThinkingEvent();
1636           AnalyzeFileEvent();
1637           AnalysisPeriodicEvent(1);
1638         } else if (initialMode == MachinePlaysWhite) {
1639           if (appData.noChessProgram) {
1640             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1641                               0, 2);
1642             return;
1643           }
1644           if (appData.icsActive) {
1645             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1646                               0, 2);
1647             return;
1648           }
1649           MachineWhiteEvent();
1650         } else if (initialMode == MachinePlaysBlack) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineBlackEvent();
1662         } else if (initialMode == TwoMachinesPlay) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           TwoMachinesEvent();
1674         } else if (initialMode == EditGame) {
1675           EditGameEvent();
1676         } else if (initialMode == EditPosition) {
1677           EditPositionEvent();
1678         } else if (initialMode == Training) {
1679           if (*appData.loadGameFile == NULLCHAR) {
1680             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1681             return;
1682           }
1683           TrainingEvent();
1684         }
1685     }
1686 }
1687
1688 void
1689 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1690 {
1691     DisplayBook(current+1);
1692
1693     MoveHistorySet( movelist, first, last, current, pvInfoList );
1694
1695     EvalGraphSet( first, last, current, pvInfoList );
1696
1697     MakeEngineOutputTitle();
1698 }
1699
1700 /*
1701  * Establish will establish a contact to a remote host.port.
1702  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1703  *  used to talk to the host.
1704  * Returns 0 if okay, error code if not.
1705  */
1706 int
1707 establish ()
1708 {
1709     char buf[MSG_SIZ];
1710
1711     if (*appData.icsCommPort != NULLCHAR) {
1712         /* Talk to the host through a serial comm port */
1713         return OpenCommPort(appData.icsCommPort, &icsPR);
1714
1715     } else if (*appData.gateway != NULLCHAR) {
1716         if (*appData.remoteShell == NULLCHAR) {
1717             /* Use the rcmd protocol to run telnet program on a gateway host */
1718             snprintf(buf, sizeof(buf), "%s %s %s",
1719                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1720             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1721
1722         } else {
1723             /* Use the rsh program to run telnet program on a gateway host */
1724             if (*appData.remoteUser == NULLCHAR) {
1725                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1726                         appData.gateway, appData.telnetProgram,
1727                         appData.icsHost, appData.icsPort);
1728             } else {
1729                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1730                         appData.remoteShell, appData.gateway,
1731                         appData.remoteUser, appData.telnetProgram,
1732                         appData.icsHost, appData.icsPort);
1733             }
1734             return StartChildProcess(buf, "", &icsPR);
1735
1736         }
1737     } else if (appData.useTelnet) {
1738         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1739
1740     } else {
1741         /* TCP socket interface differs somewhat between
1742            Unix and NT; handle details in the front end.
1743            */
1744         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1745     }
1746 }
1747
1748 void
1749 EscapeExpand (char *p, char *q)
1750 {       // [HGM] initstring: routine to shape up string arguments
1751         while(*p++ = *q++) if(p[-1] == '\\')
1752             switch(*q++) {
1753                 case 'n': p[-1] = '\n'; break;
1754                 case 'r': p[-1] = '\r'; break;
1755                 case 't': p[-1] = '\t'; break;
1756                 case '\\': p[-1] = '\\'; break;
1757                 case 0: *p = 0; return;
1758                 default: p[-1] = q[-1]; break;
1759             }
1760 }
1761
1762 void
1763 show_bytes (FILE *fp, char *buf, int count)
1764 {
1765     while (count--) {
1766         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1767             fprintf(fp, "\\%03o", *buf & 0xff);
1768         } else {
1769             putc(*buf, fp);
1770         }
1771         buf++;
1772     }
1773     fflush(fp);
1774 }
1775
1776 /* Returns an errno value */
1777 int
1778 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1779 {
1780     char buf[8192], *p, *q, *buflim;
1781     int left, newcount, outcount;
1782
1783     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1784         *appData.gateway != NULLCHAR) {
1785         if (appData.debugMode) {
1786             fprintf(debugFP, ">ICS: ");
1787             show_bytes(debugFP, message, count);
1788             fprintf(debugFP, "\n");
1789         }
1790         return OutputToProcess(pr, message, count, outError);
1791     }
1792
1793     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1794     p = message;
1795     q = buf;
1796     left = count;
1797     newcount = 0;
1798     while (left) {
1799         if (q >= buflim) {
1800             if (appData.debugMode) {
1801                 fprintf(debugFP, ">ICS: ");
1802                 show_bytes(debugFP, buf, newcount);
1803                 fprintf(debugFP, "\n");
1804             }
1805             outcount = OutputToProcess(pr, buf, newcount, outError);
1806             if (outcount < newcount) return -1; /* to be sure */
1807             q = buf;
1808             newcount = 0;
1809         }
1810         if (*p == '\n') {
1811             *q++ = '\r';
1812             newcount++;
1813         } else if (((unsigned char) *p) == TN_IAC) {
1814             *q++ = (char) TN_IAC;
1815             newcount ++;
1816         }
1817         *q++ = *p++;
1818         newcount++;
1819         left--;
1820     }
1821     if (appData.debugMode) {
1822         fprintf(debugFP, ">ICS: ");
1823         show_bytes(debugFP, buf, newcount);
1824         fprintf(debugFP, "\n");
1825     }
1826     outcount = OutputToProcess(pr, buf, newcount, outError);
1827     if (outcount < newcount) return -1; /* to be sure */
1828     return count;
1829 }
1830
1831 void
1832 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1833 {
1834     int outError, outCount;
1835     static int gotEof = 0;
1836
1837     /* Pass data read from player on to ICS */
1838     if (count > 0) {
1839         gotEof = 0;
1840         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1841         if (outCount < count) {
1842             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1843         }
1844     } else if (count < 0) {
1845         RemoveInputSource(isr);
1846         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1847     } else if (gotEof++ > 0) {
1848         RemoveInputSource(isr);
1849         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1850     }
1851 }
1852
1853 void
1854 KeepAlive ()
1855 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1856     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1857     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1858     SendToICS("date\n");
1859     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1860 }
1861
1862 /* added routine for printf style output to ics */
1863 void
1864 ics_printf (char *format, ...)
1865 {
1866     char buffer[MSG_SIZ];
1867     va_list args;
1868
1869     va_start(args, format);
1870     vsnprintf(buffer, sizeof(buffer), format, args);
1871     buffer[sizeof(buffer)-1] = '\0';
1872     SendToICS(buffer);
1873     va_end(args);
1874 }
1875
1876 void
1877 SendToICS (char *s)
1878 {
1879     int count, outCount, outError;
1880
1881     if (icsPR == NoProc) return;
1882
1883     count = strlen(s);
1884     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1885     if (outCount < count) {
1886         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887     }
1888 }
1889
1890 /* This is used for sending logon scripts to the ICS. Sending
1891    without a delay causes problems when using timestamp on ICC
1892    (at least on my machine). */
1893 void
1894 SendToICSDelayed (char *s, long msdelay)
1895 {
1896     int count, outCount, outError;
1897
1898     if (icsPR == NoProc) return;
1899
1900     count = strlen(s);
1901     if (appData.debugMode) {
1902         fprintf(debugFP, ">ICS: ");
1903         show_bytes(debugFP, s, count);
1904         fprintf(debugFP, "\n");
1905     }
1906     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1907                                       msdelay);
1908     if (outCount < count) {
1909         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910     }
1911 }
1912
1913
1914 /* Remove all highlighting escape sequences in s
1915    Also deletes any suffix starting with '('
1916    */
1917 char *
1918 StripHighlightAndTitle (char *s)
1919 {
1920     static char retbuf[MSG_SIZ];
1921     char *p = retbuf;
1922
1923     while (*s != NULLCHAR) {
1924         while (*s == '\033') {
1925             while (*s != NULLCHAR && !isalpha(*s)) s++;
1926             if (*s != NULLCHAR) s++;
1927         }
1928         while (*s != NULLCHAR && *s != '\033') {
1929             if (*s == '(' || *s == '[') {
1930                 *p = NULLCHAR;
1931                 return retbuf;
1932             }
1933             *p++ = *s++;
1934         }
1935     }
1936     *p = NULLCHAR;
1937     return retbuf;
1938 }
1939
1940 /* Remove all highlighting escape sequences in s */
1941 char *
1942 StripHighlight (char *s)
1943 {
1944     static char retbuf[MSG_SIZ];
1945     char *p = retbuf;
1946
1947     while (*s != NULLCHAR) {
1948         while (*s == '\033') {
1949             while (*s != NULLCHAR && !isalpha(*s)) s++;
1950             if (*s != NULLCHAR) s++;
1951         }
1952         while (*s != NULLCHAR && *s != '\033') {
1953             *p++ = *s++;
1954         }
1955     }
1956     *p = NULLCHAR;
1957     return retbuf;
1958 }
1959
1960 char *variantNames[] = VARIANT_NAMES;
1961 char *
1962 VariantName (VariantClass v)
1963 {
1964     return variantNames[v];
1965 }
1966
1967
1968 /* Identify a variant from the strings the chess servers use or the
1969    PGN Variant tag names we use. */
1970 VariantClass
1971 StringToVariant (char *e)
1972 {
1973     char *p;
1974     int wnum = -1;
1975     VariantClass v = VariantNormal;
1976     int i, found = FALSE;
1977     char buf[MSG_SIZ];
1978     int len;
1979
1980     if (!e) return v;
1981
1982     /* [HGM] skip over optional board-size prefixes */
1983     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1984         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1985         while( *e++ != '_');
1986     }
1987
1988     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1989         v = VariantNormal;
1990         found = TRUE;
1991     } else
1992     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1993       if (StrCaseStr(e, variantNames[i])) {
1994         v = (VariantClass) i;
1995         found = TRUE;
1996         break;
1997       }
1998     }
1999
2000     if (!found) {
2001       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2002           || StrCaseStr(e, "wild/fr")
2003           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2004         v = VariantFischeRandom;
2005       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2006                  (i = 1, p = StrCaseStr(e, "w"))) {
2007         p += i;
2008         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2009         if (isdigit(*p)) {
2010           wnum = atoi(p);
2011         } else {
2012           wnum = -1;
2013         }
2014         switch (wnum) {
2015         case 0: /* FICS only, actually */
2016         case 1:
2017           /* Castling legal even if K starts on d-file */
2018           v = VariantWildCastle;
2019           break;
2020         case 2:
2021         case 3:
2022         case 4:
2023           /* Castling illegal even if K & R happen to start in
2024              normal positions. */
2025           v = VariantNoCastle;
2026           break;
2027         case 5:
2028         case 7:
2029         case 8:
2030         case 10:
2031         case 11:
2032         case 12:
2033         case 13:
2034         case 14:
2035         case 15:
2036         case 18:
2037         case 19:
2038           /* Castling legal iff K & R start in normal positions */
2039           v = VariantNormal;
2040           break;
2041         case 6:
2042         case 20:
2043         case 21:
2044           /* Special wilds for position setup; unclear what to do here */
2045           v = VariantLoadable;
2046           break;
2047         case 9:
2048           /* Bizarre ICC game */
2049           v = VariantTwoKings;
2050           break;
2051         case 16:
2052           v = VariantKriegspiel;
2053           break;
2054         case 17:
2055           v = VariantLosers;
2056           break;
2057         case 22:
2058           v = VariantFischeRandom;
2059           break;
2060         case 23:
2061           v = VariantCrazyhouse;
2062           break;
2063         case 24:
2064           v = VariantBughouse;
2065           break;
2066         case 25:
2067           v = Variant3Check;
2068           break;
2069         case 26:
2070           /* Not quite the same as FICS suicide! */
2071           v = VariantGiveaway;
2072           break;
2073         case 27:
2074           v = VariantAtomic;
2075           break;
2076         case 28:
2077           v = VariantShatranj;
2078           break;
2079
2080         /* Temporary names for future ICC types.  The name *will* change in
2081            the next xboard/WinBoard release after ICC defines it. */
2082         case 29:
2083           v = Variant29;
2084           break;
2085         case 30:
2086           v = Variant30;
2087           break;
2088         case 31:
2089           v = Variant31;
2090           break;
2091         case 32:
2092           v = Variant32;
2093           break;
2094         case 33:
2095           v = Variant33;
2096           break;
2097         case 34:
2098           v = Variant34;
2099           break;
2100         case 35:
2101           v = Variant35;
2102           break;
2103         case 36:
2104           v = Variant36;
2105           break;
2106         case 37:
2107           v = VariantShogi;
2108           break;
2109         case 38:
2110           v = VariantXiangqi;
2111           break;
2112         case 39:
2113           v = VariantCourier;
2114           break;
2115         case 40:
2116           v = VariantGothic;
2117           break;
2118         case 41:
2119           v = VariantCapablanca;
2120           break;
2121         case 42:
2122           v = VariantKnightmate;
2123           break;
2124         case 43:
2125           v = VariantFairy;
2126           break;
2127         case 44:
2128           v = VariantCylinder;
2129           break;
2130         case 45:
2131           v = VariantFalcon;
2132           break;
2133         case 46:
2134           v = VariantCapaRandom;
2135           break;
2136         case 47:
2137           v = VariantBerolina;
2138           break;
2139         case 48:
2140           v = VariantJanus;
2141           break;
2142         case 49:
2143           v = VariantSuper;
2144           break;
2145         case 50:
2146           v = VariantGreat;
2147           break;
2148         case -1:
2149           /* Found "wild" or "w" in the string but no number;
2150              must assume it's normal chess. */
2151           v = VariantNormal;
2152           break;
2153         default:
2154           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2155           if( (len >= MSG_SIZ) && appData.debugMode )
2156             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2157
2158           DisplayError(buf, 0);
2159           v = VariantUnknown;
2160           break;
2161         }
2162       }
2163     }
2164     if (appData.debugMode) {
2165       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2166               e, wnum, VariantName(v));
2167     }
2168     return v;
2169 }
2170
2171 static int leftover_start = 0, leftover_len = 0;
2172 char star_match[STAR_MATCH_N][MSG_SIZ];
2173
2174 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2175    advance *index beyond it, and set leftover_start to the new value of
2176    *index; else return FALSE.  If pattern contains the character '*', it
2177    matches any sequence of characters not containing '\r', '\n', or the
2178    character following the '*' (if any), and the matched sequence(s) are
2179    copied into star_match.
2180    */
2181 int
2182 looking_at ( char *buf, int *index, char *pattern)
2183 {
2184     char *bufp = &buf[*index], *patternp = pattern;
2185     int star_count = 0;
2186     char *matchp = star_match[0];
2187
2188     for (;;) {
2189         if (*patternp == NULLCHAR) {
2190             *index = leftover_start = bufp - buf;
2191             *matchp = NULLCHAR;
2192             return TRUE;
2193         }
2194         if (*bufp == NULLCHAR) return FALSE;
2195         if (*patternp == '*') {
2196             if (*bufp == *(patternp + 1)) {
2197                 *matchp = NULLCHAR;
2198                 matchp = star_match[++star_count];
2199                 patternp += 2;
2200                 bufp++;
2201                 continue;
2202             } else if (*bufp == '\n' || *bufp == '\r') {
2203                 patternp++;
2204                 if (*patternp == NULLCHAR)
2205                   continue;
2206                 else
2207                   return FALSE;
2208             } else {
2209                 *matchp++ = *bufp++;
2210                 continue;
2211             }
2212         }
2213         if (*patternp != *bufp) return FALSE;
2214         patternp++;
2215         bufp++;
2216     }
2217 }
2218
2219 void
2220 SendToPlayer (char *data, int length)
2221 {
2222     int error, outCount;
2223     outCount = OutputToProcess(NoProc, data, length, &error);
2224     if (outCount < length) {
2225         DisplayFatalError(_("Error writing to display"), error, 1);
2226     }
2227 }
2228
2229 void
2230 PackHolding (char packed[], char *holding)
2231 {
2232     char *p = holding;
2233     char *q = packed;
2234     int runlength = 0;
2235     int curr = 9999;
2236     do {
2237         if (*p == curr) {
2238             runlength++;
2239         } else {
2240             switch (runlength) {
2241               case 0:
2242                 break;
2243               case 1:
2244                 *q++ = curr;
2245                 break;
2246               case 2:
2247                 *q++ = curr;
2248                 *q++ = curr;
2249                 break;
2250               default:
2251                 sprintf(q, "%d", runlength);
2252                 while (*q) q++;
2253                 *q++ = curr;
2254                 break;
2255             }
2256             runlength = 1;
2257             curr = *p;
2258         }
2259     } while (*p++);
2260     *q = NULLCHAR;
2261 }
2262
2263 /* Telnet protocol requests from the front end */
2264 void
2265 TelnetRequest (unsigned char ddww, unsigned char option)
2266 {
2267     unsigned char msg[3];
2268     int outCount, outError;
2269
2270     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2271
2272     if (appData.debugMode) {
2273         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2274         switch (ddww) {
2275           case TN_DO:
2276             ddwwStr = "DO";
2277             break;
2278           case TN_DONT:
2279             ddwwStr = "DONT";
2280             break;
2281           case TN_WILL:
2282             ddwwStr = "WILL";
2283             break;
2284           case TN_WONT:
2285             ddwwStr = "WONT";
2286             break;
2287           default:
2288             ddwwStr = buf1;
2289             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290             break;
2291         }
2292         switch (option) {
2293           case TN_ECHO:
2294             optionStr = "ECHO";
2295             break;
2296           default:
2297             optionStr = buf2;
2298             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299             break;
2300         }
2301         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302     }
2303     msg[0] = TN_IAC;
2304     msg[1] = ddww;
2305     msg[2] = option;
2306     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2307     if (outCount < 3) {
2308         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309     }
2310 }
2311
2312 void
2313 DoEcho ()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DO, TN_ECHO);
2317 }
2318
2319 void
2320 DontEcho ()
2321 {
2322     if (!appData.icsActive) return;
2323     TelnetRequest(TN_DONT, TN_ECHO);
2324 }
2325
2326 void
2327 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2328 {
2329     /* put the holdings sent to us by the server on the board holdings area */
2330     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2331     char p;
2332     ChessSquare piece;
2333
2334     if(gameInfo.holdingsWidth < 2)  return;
2335     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336         return; // prevent overwriting by pre-board holdings
2337
2338     if( (int)lowestPiece >= BlackPawn ) {
2339         holdingsColumn = 0;
2340         countsColumn = 1;
2341         holdingsStartRow = BOARD_HEIGHT-1;
2342         direction = -1;
2343     } else {
2344         holdingsColumn = BOARD_WIDTH-1;
2345         countsColumn = BOARD_WIDTH-2;
2346         holdingsStartRow = 0;
2347         direction = 1;
2348     }
2349
2350     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351         board[i][holdingsColumn] = EmptySquare;
2352         board[i][countsColumn]   = (ChessSquare) 0;
2353     }
2354     while( (p=*holdings++) != NULLCHAR ) {
2355         piece = CharToPiece( ToUpper(p) );
2356         if(piece == EmptySquare) continue;
2357         /*j = (int) piece - (int) WhitePawn;*/
2358         j = PieceToNumber(piece);
2359         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360         if(j < 0) continue;               /* should not happen */
2361         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363         board[holdingsStartRow+j*direction][countsColumn]++;
2364     }
2365 }
2366
2367
2368 void
2369 VariantSwitch (Board board, VariantClass newVariant)
2370 {
2371    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372    static Board oldBoard;
2373
2374    startedFromPositionFile = FALSE;
2375    if(gameInfo.variant == newVariant) return;
2376
2377    /* [HGM] This routine is called each time an assignment is made to
2378     * gameInfo.variant during a game, to make sure the board sizes
2379     * are set to match the new variant. If that means adding or deleting
2380     * holdings, we shift the playing board accordingly
2381     * This kludge is needed because in ICS observe mode, we get boards
2382     * of an ongoing game without knowing the variant, and learn about the
2383     * latter only later. This can be because of the move list we requested,
2384     * in which case the game history is refilled from the beginning anyway,
2385     * but also when receiving holdings of a crazyhouse game. In the latter
2386     * case we want to add those holdings to the already received position.
2387     */
2388
2389
2390    if (appData.debugMode) {
2391      fprintf(debugFP, "Switch board from %s to %s\n",
2392              VariantName(gameInfo.variant), VariantName(newVariant));
2393      setbuf(debugFP, NULL);
2394    }
2395    shuffleOpenings = 0;       /* [HGM] shuffle */
2396    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2397    switch(newVariant)
2398      {
2399      case VariantShogi:
2400        newWidth = 9;  newHeight = 9;
2401        gameInfo.holdingsSize = 7;
2402      case VariantBughouse:
2403      case VariantCrazyhouse:
2404        newHoldingsWidth = 2; break;
2405      case VariantGreat:
2406        newWidth = 10;
2407      case VariantSuper:
2408        newHoldingsWidth = 2;
2409        gameInfo.holdingsSize = 8;
2410        break;
2411      case VariantGothic:
2412      case VariantCapablanca:
2413      case VariantCapaRandom:
2414        newWidth = 10;
2415      default:
2416        newHoldingsWidth = gameInfo.holdingsSize = 0;
2417      };
2418
2419    if(newWidth  != gameInfo.boardWidth  ||
2420       newHeight != gameInfo.boardHeight ||
2421       newHoldingsWidth != gameInfo.holdingsWidth ) {
2422
2423      /* shift position to new playing area, if needed */
2424      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429        for(i=0; i<newHeight; i++) {
2430          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2432        }
2433      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438      }
2439      gameInfo.boardWidth  = newWidth;
2440      gameInfo.boardHeight = newHeight;
2441      gameInfo.holdingsWidth = newHoldingsWidth;
2442      gameInfo.variant = newVariant;
2443      InitDrawingSizes(-2, 0);
2444    } else gameInfo.variant = newVariant;
2445    CopyBoard(oldBoard, board);   // remember correctly formatted board
2446      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2447    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 }
2449
2450 static int loggedOn = FALSE;
2451
2452 /*-- Game start info cache: --*/
2453 int gs_gamenum;
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\   ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2461
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2464
2465 // [HGM] seekgraph
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2469 #define SQUARE 0x80
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2478
2479 void
2480 PlotSeekAd (int i)
2481 {
2482         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484         if(r < minRating+100 && r >=0 ) r = minRating+100;
2485         if(r > maxRating) r = maxRating;
2486         if(tc < 1.) tc = 1.;
2487         if(tc > 95.) tc = 95.;
2488         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489         y = ((double)r - minRating)/(maxRating - minRating)
2490             * (h-vMargin-squareSize/8-1) + vMargin;
2491         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492         if(strstr(seekAdList[i], " u ")) color = 1;
2493         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494            !strstr(seekAdList[i], "bullet") &&
2495            !strstr(seekAdList[i], "blitz") &&
2496            !strstr(seekAdList[i], "standard") ) color = 2;
2497         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2499 }
2500
2501 void
2502 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2503 {
2504         char buf[MSG_SIZ], *ext = "";
2505         VariantClass v = StringToVariant(type);
2506         if(strstr(type, "wild")) {
2507             ext = type + 4; // append wild number
2508             if(v == VariantFischeRandom) type = "chess960"; else
2509             if(v == VariantLoadable) type = "setup"; else
2510             type = VariantName(v);
2511         }
2512         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518             seekNrList[nrOfSeekAds] = nr;
2519             zList[nrOfSeekAds] = 0;
2520             seekAdList[nrOfSeekAds++] = StrSave(buf);
2521             if(plot) PlotSeekAd(nrOfSeekAds-1);
2522         }
2523 }
2524
2525 void
2526 EraseSeekDot (int i)
2527 {
2528     int x = xList[i], y = yList[i], d=squareSize/4, k;
2529     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531     // now replot every dot that overlapped
2532     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533         int xx = xList[k], yy = yList[k];
2534         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535             DrawSeekDot(xx, yy, colorList[k]);
2536     }
2537 }
2538
2539 void
2540 RemoveSeekAd (int nr)
2541 {
2542         int i;
2543         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2544             EraseSeekDot(i);
2545             if(seekAdList[i]) free(seekAdList[i]);
2546             seekAdList[i] = seekAdList[--nrOfSeekAds];
2547             seekNrList[i] = seekNrList[nrOfSeekAds];
2548             ratingList[i] = ratingList[nrOfSeekAds];
2549             colorList[i]  = colorList[nrOfSeekAds];
2550             tcList[i] = tcList[nrOfSeekAds];
2551             xList[i]  = xList[nrOfSeekAds];
2552             yList[i]  = yList[nrOfSeekAds];
2553             zList[i]  = zList[nrOfSeekAds];
2554             seekAdList[nrOfSeekAds] = NULL;
2555             break;
2556         }
2557 }
2558
2559 Boolean
2560 MatchSoughtLine (char *line)
2561 {
2562     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563     int nr, base, inc, u=0; char dummy;
2564
2565     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2567        (u=1) &&
2568        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2570         // match: compact and save the line
2571         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572         return TRUE;
2573     }
2574     return FALSE;
2575 }
2576
2577 int
2578 DrawSeekGraph ()
2579 {
2580     int i;
2581     if(!seekGraphUp) return FALSE;
2582     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2584
2585     DrawSeekBackground(0, 0, w, h);
2586     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2590         yy = h-1-yy;
2591         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592         if(i%500 == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596         }
2597     }
2598     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599     for(i=1; i<100; i+=(i<10?1:5)) {
2600         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606         }
2607     }
2608     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609     return TRUE;
2610 }
2611
2612 int
2613 SeekGraphClick (ClickType click, int x, int y, int moving)
2614 {
2615     static int lastDown = 0, displayed = 0, lastSecond;
2616     if(y < 0) return FALSE;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2667 {
2668 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2669 #define STARTED_NONE 0
2670 #define STARTED_MOVES 1
2671 #define STARTED_BOARD 2
2672 #define STARTED_OBSERVE 3
2673 #define STARTED_HOLDINGS 4
2674 #define STARTED_CHATTER 5
2675 #define STARTED_COMMENT 6
2676 #define STARTED_MOVES_NOHIDE 7
2677
2678     static int started = STARTED_NONE;
2679     static char parse[20000];
2680     static int parse_pos = 0;
2681     static char buf[BUF_SIZE + 1];
2682     static int firstTime = TRUE, intfSet = FALSE;
2683     static ColorClass prevColor = ColorNormal;
2684     static int savingComment = FALSE;
2685     static int cmatch = 0; // continuation sequence match
2686     char *bp;
2687     char str[MSG_SIZ];
2688     int i, oldi;
2689     int buf_len;
2690     int next_out;
2691     int tkind;
2692     int backup;    /* [DM] For zippy color lines */
2693     char *p;
2694     char talker[MSG_SIZ]; // [HGM] chat
2695     int channel;
2696
2697     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2698
2699     if (appData.debugMode) {
2700       if (!error) {
2701         fprintf(debugFP, "<ICS: ");
2702         show_bytes(debugFP, data, count);
2703         fprintf(debugFP, "\n");
2704       }
2705     }
2706
2707     if (appData.debugMode) { int f = forwardMostMove;
2708         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2709                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2710                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2711     }
2712     if (count > 0) {
2713         /* If last read ended with a partial line that we couldn't parse,
2714            prepend it to the new read and try again. */
2715         if (leftover_len > 0) {
2716             for (i=0; i<leftover_len; i++)
2717               buf[i] = buf[leftover_start + i];
2718         }
2719
2720     /* copy new characters into the buffer */
2721     bp = buf + leftover_len;
2722     buf_len=leftover_len;
2723     for (i=0; i<count; i++)
2724     {
2725         // ignore these
2726         if (data[i] == '\r')
2727             continue;
2728
2729         // join lines split by ICS?
2730         if (!appData.noJoin)
2731         {
2732             /*
2733                 Joining just consists of finding matches against the
2734                 continuation sequence, and discarding that sequence
2735                 if found instead of copying it.  So, until a match
2736                 fails, there's nothing to do since it might be the
2737                 complete sequence, and thus, something we don't want
2738                 copied.
2739             */
2740             if (data[i] == cont_seq[cmatch])
2741             {
2742                 cmatch++;
2743                 if (cmatch == strlen(cont_seq))
2744                 {
2745                     cmatch = 0; // complete match.  just reset the counter
2746
2747                     /*
2748                         it's possible for the ICS to not include the space
2749                         at the end of the last word, making our [correct]
2750                         join operation fuse two separate words.  the server
2751                         does this when the space occurs at the width setting.
2752                     */
2753                     if (!buf_len || buf[buf_len-1] != ' ')
2754                     {
2755                         *bp++ = ' ';
2756                         buf_len++;
2757                     }
2758                 }
2759                 continue;
2760             }
2761             else if (cmatch)
2762             {
2763                 /*
2764                     match failed, so we have to copy what matched before
2765                     falling through and copying this character.  In reality,
2766                     this will only ever be just the newline character, but
2767                     it doesn't hurt to be precise.
2768                 */
2769                 strncpy(bp, cont_seq, cmatch);
2770                 bp += cmatch;
2771                 buf_len += cmatch;
2772                 cmatch = 0;
2773             }
2774         }
2775
2776         // copy this char
2777         *bp++ = data[i];
2778         buf_len++;
2779     }
2780
2781         buf[buf_len] = NULLCHAR;
2782 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2783         next_out = 0;
2784         leftover_start = 0;
2785
2786         i = 0;
2787         while (i < buf_len) {
2788             /* Deal with part of the TELNET option negotiation
2789                protocol.  We refuse to do anything beyond the
2790                defaults, except that we allow the WILL ECHO option,
2791                which ICS uses to turn off password echoing when we are
2792                directly connected to it.  We reject this option
2793                if localLineEditing mode is on (always on in xboard)
2794                and we are talking to port 23, which might be a real
2795                telnet server that will try to keep WILL ECHO on permanently.
2796              */
2797             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2798                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2799                 unsigned char option;
2800                 oldi = i;
2801                 switch ((unsigned char) buf[++i]) {
2802                   case TN_WILL:
2803                     if (appData.debugMode)
2804                       fprintf(debugFP, "\n<WILL ");
2805                     switch (option = (unsigned char) buf[++i]) {
2806                       case TN_ECHO:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "ECHO ");
2809                         /* Reply only if this is a change, according
2810                            to the protocol rules. */
2811                         if (remoteEchoOption) break;
2812                         if (appData.localLineEditing &&
2813                             atoi(appData.icsPort) == TN_PORT) {
2814                             TelnetRequest(TN_DONT, TN_ECHO);
2815                         } else {
2816                             EchoOff();
2817                             TelnetRequest(TN_DO, TN_ECHO);
2818                             remoteEchoOption = TRUE;
2819                         }
2820                         break;
2821                       default:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "%d ", option);
2824                         /* Whatever this is, we don't want it. */
2825                         TelnetRequest(TN_DONT, option);
2826                         break;
2827                     }
2828                     break;
2829                   case TN_WONT:
2830                     if (appData.debugMode)
2831                       fprintf(debugFP, "\n<WONT ");
2832                     switch (option = (unsigned char) buf[++i]) {
2833                       case TN_ECHO:
2834                         if (appData.debugMode)
2835                           fprintf(debugFP, "ECHO ");
2836                         /* Reply only if this is a change, according
2837                            to the protocol rules. */
2838                         if (!remoteEchoOption) break;
2839                         EchoOn();
2840                         TelnetRequest(TN_DONT, TN_ECHO);
2841                         remoteEchoOption = FALSE;
2842                         break;
2843                       default:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", (unsigned char) option);
2846                         /* Whatever this is, it must already be turned
2847                            off, because we never agree to turn on
2848                            anything non-default, so according to the
2849                            protocol rules, we don't reply. */
2850                         break;
2851                     }
2852                     break;
2853                   case TN_DO:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<DO ");
2856                     switch (option = (unsigned char) buf[++i]) {
2857                       default:
2858                         /* Whatever this is, we refuse to do it. */
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "%d ", option);
2861                         TelnetRequest(TN_WONT, option);
2862                         break;
2863                     }
2864                     break;
2865                   case TN_DONT:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<DONT ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       default:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", option);
2872                         /* Whatever this is, we are already not doing
2873                            it, because we never agree to do anything
2874                            non-default, so according to the protocol
2875                            rules, we don't reply. */
2876                         break;
2877                     }
2878                     break;
2879                   case TN_IAC:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<IAC ");
2882                     /* Doubled IAC; pass it through */
2883                     i--;
2884                     break;
2885                   default:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2888                     /* Drop all other telnet commands on the floor */
2889                     break;
2890                 }
2891                 if (oldi > next_out)
2892                   SendToPlayer(&buf[next_out], oldi - next_out);
2893                 if (++i > next_out)
2894                   next_out = i;
2895                 continue;
2896             }
2897
2898             /* OK, this at least will *usually* work */
2899             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2900                 loggedOn = TRUE;
2901             }
2902
2903             if (loggedOn && !intfSet) {
2904                 if (ics_type == ICS_ICC) {
2905                   snprintf(str, MSG_SIZ,
2906                           "/set-quietly interface %s\n/set-quietly style 12\n",
2907                           programVersion);
2908                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2909                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2910                 } else if (ics_type == ICS_CHESSNET) {
2911                   snprintf(str, MSG_SIZ, "/style 12\n");
2912                 } else {
2913                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2914                   strcat(str, programVersion);
2915                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2916                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2917                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2918 #ifdef WIN32
2919                   strcat(str, "$iset nohighlight 1\n");
2920 #endif
2921                   strcat(str, "$iset lock 1\n$style 12\n");
2922                 }
2923                 SendToICS(str);
2924                 NotifyFrontendLogin();
2925                 intfSet = TRUE;
2926             }
2927
2928             if (started == STARTED_COMMENT) {
2929                 /* Accumulate characters in comment */
2930                 parse[parse_pos++] = buf[i];
2931                 if (buf[i] == '\n') {
2932                     parse[parse_pos] = NULLCHAR;
2933                     if(chattingPartner>=0) {
2934                         char mess[MSG_SIZ];
2935                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2936                         OutputChatMessage(chattingPartner, mess);
2937                         chattingPartner = -1;
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     } else
2940                     if(!suppressKibitz) // [HGM] kibitz
2941                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2942                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2943                         int nrDigit = 0, nrAlph = 0, j;
2944                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2945                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2946                         parse[parse_pos] = NULLCHAR;
2947                         // try to be smart: if it does not look like search info, it should go to
2948                         // ICS interaction window after all, not to engine-output window.
2949                         for(j=0; j<parse_pos; j++) { // count letters and digits
2950                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2951                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2952                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2953                         }
2954                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2955                             int depth=0; float score;
2956                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2957                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2958                                 pvInfoList[forwardMostMove-1].depth = depth;
2959                                 pvInfoList[forwardMostMove-1].score = 100*score;
2960                             }
2961                             OutputKibitz(suppressKibitz, parse);
2962                         } else {
2963                             char tmp[MSG_SIZ];
2964                             if(gameMode == IcsObserving) // restore original ICS messages
2965                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2966                             else
2967                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968                             SendToPlayer(tmp, strlen(tmp));
2969                         }
2970                         next_out = i+1; // [HGM] suppress printing in ICS window
2971                     }
2972                     started = STARTED_NONE;
2973                 } else {
2974                     /* Don't match patterns against characters in comment */
2975                     i++;
2976                     continue;
2977                 }
2978             }
2979             if (started == STARTED_CHATTER) {
2980                 if (buf[i] != '\n') {
2981                     /* Don't match patterns against characters in chatter */
2982                     i++;
2983                     continue;
2984                 }
2985                 started = STARTED_NONE;
2986                 if(suppressKibitz) next_out = i+1;
2987             }
2988
2989             /* Kludge to deal with rcmd protocol */
2990             if (firstTime && looking_at(buf, &i, "\001*")) {
2991                 DisplayFatalError(&buf[1], 0, 1);
2992                 continue;
2993             } else {
2994                 firstTime = FALSE;
2995             }
2996
2997             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998                 ics_type = ICS_ICC;
2999                 ics_prefix = "/";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005                 ics_type = ICS_FICS;
3006                 ics_prefix = "$";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012                 ics_type = ICS_CHESSNET;
3013                 ics_prefix = "/";
3014                 if (appData.debugMode)
3015                   fprintf(debugFP, "ics_type %d\n", ics_type);
3016                 continue;
3017             }
3018
3019             if (!loggedOn &&
3020                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3022                  looking_at(buf, &i, "will be \"*\""))) {
3023               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3024               continue;
3025             }
3026
3027             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3028               char buf[MSG_SIZ];
3029               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030               DisplayIcsInteractionTitle(buf);
3031               have_set_title = TRUE;
3032             }
3033
3034             /* skip finger notes */
3035             if (started == STARTED_NONE &&
3036                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037                  (buf[i] == '1' && buf[i+1] == '0')) &&
3038                 buf[i+2] == ':' && buf[i+3] == ' ') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             oldi = i;
3045             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046             if(appData.seekGraph) {
3047                 if(soughtPending && MatchSoughtLine(buf+i)) {
3048                     i = strstr(buf+i, "rated") - buf;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     next_out = leftover_start = i;
3051                     started = STARTED_CHATTER;
3052                     suppressKibitz = TRUE;
3053                     continue;
3054                 }
3055                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056                         && looking_at(buf, &i, "* ads displayed")) {
3057                     soughtPending = FALSE;
3058                     seekGraphUp = TRUE;
3059                     DrawSeekGraph();
3060                     continue;
3061                 }
3062                 if(appData.autoRefresh) {
3063                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064                         int s = (ics_type == ICS_ICC); // ICC format differs
3065                         if(seekGraphUp)
3066                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068                         looking_at(buf, &i, "*% "); // eat prompt
3069                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071                         next_out = i; // suppress
3072                         continue;
3073                     }
3074                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075                         char *p = star_match[0];
3076                         while(*p) {
3077                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3078                             while(*p && *p++ != ' '); // next
3079                         }
3080                         looking_at(buf, &i, "*% "); // eat prompt
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i;
3083                         continue;
3084                     }
3085                 }
3086             }
3087
3088             /* skip formula vars */
3089             if (started == STARTED_NONE &&
3090                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097             if (appData.autoKibitz && started == STARTED_NONE &&
3098                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3099                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3101                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3104                         suppressKibitz = TRUE;
3105                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                         next_out = i;
3107                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108                                 && (gameMode == IcsPlayingWhite)) ||
3109                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3111                             started = STARTED_CHATTER; // own kibitz we simply discard
3112                         else {
3113                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114                             parse_pos = 0; parse[0] = NULLCHAR;
3115                             savingComment = TRUE;
3116                             suppressKibitz = gameMode != IcsObserving ? 2 :
3117                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3118                         }
3119                         continue;
3120                 } else
3121                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123                          && atoi(star_match[0])) {
3124                     // suppress the acknowledgements of our own autoKibitz
3125                     char *p;
3126                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128                     SendToPlayer(star_match[0], strlen(star_match[0]));
3129                     if(looking_at(buf, &i, "*% ")) // eat prompt
3130                         suppressKibitz = FALSE;
3131                     next_out = i;
3132                     continue;
3133                 }
3134             } // [HGM] kibitz: end of patch
3135
3136             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3137
3138             // [HGM] chat: intercept tells by users for which we have an open chat window
3139             channel = -1;
3140             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141                                            looking_at(buf, &i, "* whispers:") ||
3142                                            looking_at(buf, &i, "* kibitzes:") ||
3143                                            looking_at(buf, &i, "* shouts:") ||
3144                                            looking_at(buf, &i, "* c-shouts:") ||
3145                                            looking_at(buf, &i, "--> * ") ||
3146                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3150                 int p;
3151                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152                 chattingPartner = -1;
3153
3154                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155                 for(p=0; p<MAX_CHAT; p++) {
3156                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157                     talker[0] = '['; strcat(talker, "] ");
3158                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159                     chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("kibitzes", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(!strcmp("whispers", chatPartner[p])) {
3172                         talker[0] = '['; strcat(talker, "] ");
3173                         chattingPartner = p; break;
3174                     }
3175                 } else
3176                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177                   if(buf[i-8] == '-' && buf[i-3] == 't')
3178                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179                     if(!strcmp("c-shouts", chatPartner[p])) {
3180                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                   if(chattingPartner < 0)
3185                   for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("shouts", chatPartner[p])) {
3187                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                 }
3194                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196                     talker[0] = 0; Colorize(ColorTell, FALSE);
3197                     chattingPartner = p; break;
3198                 }
3199                 if(chattingPartner<0) i = oldi; else {
3200                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203                     started = STARTED_COMMENT;
3204                     parse_pos = 0; parse[0] = NULLCHAR;
3205                     savingComment = 3 + chattingPartner; // counts as TRUE
3206                     suppressKibitz = TRUE;
3207                     continue;
3208                 }
3209             } // [HGM] chat: end of patch
3210
3211           backup = i;
3212             if (appData.zippyTalk || appData.zippyPlay) {
3213                 /* [DM] Backup address for color zippy lines */
3214 #if ZIPPY
3215                if (loggedOn == TRUE)
3216                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3218 #endif
3219             } // [DM] 'else { ' deleted
3220                 if (
3221                     /* Regular tells and says */
3222                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3224                     looking_at(buf, &i, "* says: ") ||
3225                     /* Don't color "message" or "messages" output */
3226                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227                     looking_at(buf, &i, "*. * at *:*: ") ||
3228                     looking_at(buf, &i, "--* (*:*): ") ||
3229                     /* Message notifications (same color as tells) */
3230                     looking_at(buf, &i, "* has left a message ") ||
3231                     looking_at(buf, &i, "* just sent you a message:\n") ||
3232                     /* Whispers and kibitzes */
3233                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234                     looking_at(buf, &i, "* kibitzes: ") ||
3235                     /* Channel tells */
3236                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3237
3238                   if (tkind == 1 && strchr(star_match[0], ':')) {
3239                       /* Avoid "tells you:" spoofs in channels */
3240                      tkind = 3;
3241                   }
3242                   if (star_match[0][0] == NULLCHAR ||
3243                       strchr(star_match[0], ' ') ||
3244                       (tkind == 3 && strchr(star_match[1], ' '))) {
3245                     /* Reject bogus matches */
3246                     i = oldi;
3247                   } else {
3248                     if (appData.colorize) {
3249                       if (oldi > next_out) {
3250                         SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = oldi;
3252                       }
3253                       switch (tkind) {
3254                       case 1:
3255                         Colorize(ColorTell, FALSE);
3256                         curColor = ColorTell;
3257                         break;
3258                       case 2:
3259                         Colorize(ColorKibitz, FALSE);
3260                         curColor = ColorKibitz;
3261                         break;
3262                       case 3:
3263                         p = strrchr(star_match[1], '(');
3264                         if (p == NULL) {
3265                           p = star_match[1];
3266                         } else {
3267                           p++;
3268                         }
3269                         if (atoi(p) == 1) {
3270                           Colorize(ColorChannel1, FALSE);
3271                           curColor = ColorChannel1;
3272                         } else {
3273                           Colorize(ColorChannel, FALSE);
3274                           curColor = ColorChannel;
3275                         }
3276                         break;
3277                       case 5:
3278                         curColor = ColorNormal;
3279                         break;
3280                       }
3281                     }
3282                     if (started == STARTED_NONE && appData.autoComment &&
3283                         (gameMode == IcsObserving ||
3284                          gameMode == IcsPlayingWhite ||
3285                          gameMode == IcsPlayingBlack)) {
3286                       parse_pos = i - oldi;
3287                       memcpy(parse, &buf[oldi], parse_pos);
3288                       parse[parse_pos] = NULLCHAR;
3289                       started = STARTED_COMMENT;
3290                       savingComment = TRUE;
3291                     } else {
3292                       started = STARTED_CHATTER;
3293                       savingComment = FALSE;
3294                     }
3295                     loggedOn = TRUE;
3296                     continue;
3297                   }
3298                 }
3299
3300                 if (looking_at(buf, &i, "* s-shouts: ") ||
3301                     looking_at(buf, &i, "* c-shouts: ")) {
3302                     if (appData.colorize) {
3303                         if (oldi > next_out) {
3304                             SendToPlayer(&buf[next_out], oldi - next_out);
3305                             next_out = oldi;
3306                         }
3307                         Colorize(ColorSShout, FALSE);
3308                         curColor = ColorSShout;
3309                     }
3310                     loggedOn = TRUE;
3311                     started = STARTED_CHATTER;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "--->")) {
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* shouts: ") ||
3321                     looking_at(buf, &i, "--> ")) {
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorShout, FALSE);
3328                         curColor = ColorShout;
3329                     }
3330                     loggedOn = TRUE;
3331                     started = STARTED_CHATTER;
3332                     continue;
3333                 }
3334
3335                 if (looking_at( buf, &i, "Challenge:")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorChallenge, FALSE);
3342                         curColor = ColorChallenge;
3343                     }
3344                     loggedOn = TRUE;
3345                     continue;
3346                 }
3347
3348                 if (looking_at(buf, &i, "* offers you") ||
3349                     looking_at(buf, &i, "* offers to be") ||
3350                     looking_at(buf, &i, "* would like to") ||
3351                     looking_at(buf, &i, "* requests to") ||
3352                     looking_at(buf, &i, "Your opponent offers") ||
3353                     looking_at(buf, &i, "Your opponent requests")) {
3354
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorRequest, FALSE);
3361                         curColor = ColorRequest;
3362                     }
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* (*) seeking")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSeek, FALSE);
3373                         curColor = ColorSeek;
3374                     }
3375                     continue;
3376             }
3377
3378           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3379
3380             if (looking_at(buf, &i, "\\   ")) {
3381                 if (prevColor != ColorNormal) {
3382                     if (oldi > next_out) {
3383                         SendToPlayer(&buf[next_out], oldi - next_out);
3384                         next_out = oldi;
3385                     }
3386                     Colorize(prevColor, TRUE);
3387                     curColor = prevColor;
3388                 }
3389                 if (savingComment) {
3390                     parse_pos = i - oldi;
3391                     memcpy(parse, &buf[oldi], parse_pos);
3392                     parse[parse_pos] = NULLCHAR;
3393                     started = STARTED_COMMENT;
3394                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395                         chattingPartner = savingComment - 3; // kludge to remember the box
3396                 } else {
3397                     started = STARTED_CHATTER;
3398                 }
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "Black Strength :") ||
3403                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404                 looking_at(buf, &i, "<10>") ||
3405                 looking_at(buf, &i, "#@#")) {
3406                 /* Wrong board style */
3407                 loggedOn = TRUE;
3408                 SendToICS(ics_prefix);
3409                 SendToICS("set style 12\n");
3410                 SendToICS(ics_prefix);
3411                 SendToICS("refresh\n");
3412                 continue;
3413             }
3414
3415             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3416                 ICSInitScript();
3417                 have_sent_ICS_logon = 1;
3418                 continue;
3419             }
3420
3421             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422                 (looking_at(buf, &i, "\n<12> ") ||
3423                  looking_at(buf, &i, "<12> "))) {
3424                 loggedOn = TRUE;
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_BOARD;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435                 looking_at(buf, &i, "<b1> ")) {
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_HOLDINGS;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3446                 loggedOn = TRUE;
3447                 /* Header for a move list -- first line */
3448
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     switch (gameMode) {
3452                       case IcsIdle:
3453                       case BeginningOfGame:
3454                         /* User typed "moves" or "oldmoves" while we
3455                            were idle.  Pretend we asked for these
3456                            moves and soak them up so user can step
3457                            through them and/or save them.
3458                            */
3459                         Reset(FALSE, TRUE);
3460                         gameMode = IcsObserving;
3461                         ModeHighlight();
3462                         ics_gamenum = -1;
3463                         ics_getting_history = H_GOT_UNREQ_HEADER;
3464                         break;
3465                       case EditGame: /*?*/
3466                       case EditPosition: /*?*/
3467                         /* Should above feature work in these modes too? */
3468                         /* For now it doesn't */
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                       default:
3472                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3473                         break;
3474                     }
3475                     break;
3476                   case H_REQUESTED:
3477                     /* Is this the right one? */
3478                     if (gameInfo.white && gameInfo.black &&
3479                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3480                         strcmp(gameInfo.black, star_match[2]) == 0) {
3481                         /* All is well */
3482                         ics_getting_history = H_GOT_REQ_HEADER;
3483                     }
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                   case H_GOT_UNREQ_HEADER:
3487                   case H_GOT_UNWANTED_HEADER:
3488                   case H_GETTING_MOVES:
3489                     /* Should not happen */
3490                     DisplayError(_("Error gathering move list: two headers"), 0);
3491                     ics_getting_history = H_FALSE;
3492                     break;
3493                 }
3494
3495                 /* Save player ratings into gameInfo if needed */
3496                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498                     (gameInfo.whiteRating == -1 ||
3499                      gameInfo.blackRating == -1)) {
3500
3501                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3502                     gameInfo.blackRating = string_to_rating(star_match[3]);
3503                     if (appData.debugMode)
3504                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505                               gameInfo.whiteRating, gameInfo.blackRating);
3506                 }
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i,
3511               "* * match, initial time: * minute*, increment: * second")) {
3512                 /* Header for a move list -- second line */
3513                 /* Initial board will follow if this is a wild game */
3514                 if (gameInfo.event != NULL) free(gameInfo.event);
3515                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516                 gameInfo.event = StrSave(str);
3517                 /* [HGM] we switched variant. Translate boards if needed. */
3518                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Move  ")) {
3523                 /* Beginning of a move list */
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     /* Normally should not happen */
3527                     /* Maybe user hit reset while we were parsing */
3528                     break;
3529                   case H_REQUESTED:
3530                     /* Happens if we are ignoring a move list that is not
3531                      * the one we just requested.  Common if the user
3532                      * tries to observe two games without turning off
3533                      * getMoveList */
3534                     break;
3535                   case H_GETTING_MOVES:
3536                     /* Should not happen */
3537                     DisplayError(_("Error gathering move list: nested"), 0);
3538                     ics_getting_history = H_FALSE;
3539                     break;
3540                   case H_GOT_REQ_HEADER:
3541                     ics_getting_history = H_GETTING_MOVES;
3542                     started = STARTED_MOVES;
3543                     parse_pos = 0;
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                     }
3547                     break;
3548                   case H_GOT_UNREQ_HEADER:
3549                     ics_getting_history = H_GETTING_MOVES;
3550                     started = STARTED_MOVES_NOHIDE;
3551                     parse_pos = 0;
3552                     break;
3553                   case H_GOT_UNWANTED_HEADER:
3554                     ics_getting_history = H_FALSE;
3555                     break;
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "% ") ||
3561                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564                     soughtPending = FALSE;
3565                     seekGraphUp = TRUE;
3566                     DrawSeekGraph();
3567                 }
3568                 if(suppressKibitz) next_out = i;
3569                 savingComment = FALSE;
3570                 suppressKibitz = 0;
3571                 switch (started) {
3572                   case STARTED_MOVES:
3573                   case STARTED_MOVES_NOHIDE:
3574                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575                     parse[parse_pos + i - oldi] = NULLCHAR;
3576                     ParseGameHistory(parse);
3577 #if ZIPPY
3578                     if (appData.zippyPlay && first.initDone) {
3579                         FeedMovesToProgram(&first, forwardMostMove);
3580                         if (gameMode == IcsPlayingWhite) {
3581                             if (WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("black\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, TRUE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, TRUE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         } else if (gameMode == IcsPlayingBlack) {
3605                             if (!WhiteOnMove(forwardMostMove)) {
3606                                 if (first.sendTime) {
3607                                   if (first.useColors) {
3608                                     SendToProgram("white\n", &first);
3609                                   }
3610                                   SendTimeRemaining(&first, FALSE);
3611                                 }
3612                                 if (first.useColors) {
3613                                   SendToProgram("black\n", &first);
3614                                 }
3615                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616                                 first.maybeThinking = TRUE;
3617                             } else {
3618                                 if (first.usePlayother) {
3619                                   if (first.sendTime) {
3620                                     SendTimeRemaining(&first, FALSE);
3621                                   }
3622                                   SendToProgram("playother\n", &first);
3623                                   firstMove = FALSE;
3624                                 } else {
3625                                   firstMove = TRUE;
3626                                 }
3627                             }
3628                         }
3629                     }
3630 #endif
3631                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3632                         /* Moves came from oldmoves or moves command
3633                            while we weren't doing anything else.
3634                            */
3635                         currentMove = forwardMostMove;
3636                         ClearHighlights();/*!!could figure this out*/
3637                         flipView = appData.flipView;
3638                         DrawPosition(TRUE, boards[currentMove]);
3639                         DisplayBothClocks();
3640                         snprintf(str, MSG_SIZ, "%s %s %s",
3641                                 gameInfo.white, _("vs."),  gameInfo.black);
3642                         DisplayTitle(str);
3643                         gameMode = IcsIdle;
3644                     } else {
3645                         /* Moves were history of an active game */
3646                         if (gameInfo.resultDetails != NULL) {
3647                             free(gameInfo.resultDetails);
3648                             gameInfo.resultDetails = NULL;
3649                         }
3650                     }
3651                     HistorySet(parseList, backwardMostMove,
3652                                forwardMostMove, currentMove-1);
3653                     DisplayMove(currentMove - 1);
3654                     if (started == STARTED_MOVES) next_out = i;
3655                     started = STARTED_NONE;
3656                     ics_getting_history = H_FALSE;
3657                     break;
3658
3659                   case STARTED_OBSERVE:
3660                     started = STARTED_NONE;
3661                     SendToICS(ics_prefix);
3662                     SendToICS("refresh\n");
3663                     break;
3664
3665                   default:
3666                     break;
3667                 }
3668                 if(bookHit) { // [HGM] book: simulate book reply
3669                     static char bookMove[MSG_SIZ]; // a bit generous?
3670
3671                     programStats.nodes = programStats.depth = programStats.time =
3672                     programStats.score = programStats.got_only_move = 0;
3673                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3674
3675                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676                     strcat(bookMove, bookHit);
3677                     HandleMachineMove(bookMove, &first);
3678                 }
3679                 continue;
3680             }
3681
3682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683                  started == STARTED_HOLDINGS ||
3684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685                 /* Accumulate characters in move list or board */
3686                 parse[parse_pos++] = buf[i];
3687             }
3688
3689             /* Start of game messages.  Mostly we detect start of game
3690                when the first board image arrives.  On some versions
3691                of the ICS, though, we need to do a "refresh" after starting
3692                to observe in order to get the current board right away. */
3693             if (looking_at(buf, &i, "Adding game * to observation list")) {
3694                 started = STARTED_OBSERVE;
3695                 continue;
3696             }
3697
3698             /* Handle auto-observe */
3699             if (appData.autoObserve &&
3700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3702                 char *player;
3703                 /* Choose the player that was highlighted, if any. */
3704                 if (star_match[0][0] == '\033' ||
3705                     star_match[1][0] != '\033') {
3706                     player = star_match[0];
3707                 } else {
3708                     player = star_match[2];
3709                 }
3710                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711                         ics_prefix, StripHighlightAndTitle(player));
3712                 SendToICS(str);
3713
3714                 /* Save ratings from notify string */
3715                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716                 player1Rating = string_to_rating(star_match[1]);
3717                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718                 player2Rating = string_to_rating(star_match[3]);
3719
3720                 if (appData.debugMode)
3721                   fprintf(debugFP,
3722                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3723                           player1Name, player1Rating,
3724                           player2Name, player2Rating);
3725
3726                 continue;
3727             }
3728
3729             /* Deal with automatic examine mode after a game,
3730                and with IcsObserving -> IcsExamining transition */
3731             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732                 looking_at(buf, &i, "has made you an examiner of game *")) {
3733
3734                 int gamenum = atoi(star_match[0]);
3735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736                     gamenum == ics_gamenum) {
3737                     /* We were already playing or observing this game;
3738                        no need to refetch history */
3739                     gameMode = IcsExamining;
3740                     if (pausing) {
3741                         pauseExamForwardMostMove = forwardMostMove;
3742                     } else if (currentMove < forwardMostMove) {
3743                         ForwardInner(forwardMostMove);
3744                     }
3745                 } else {
3746                     /* I don't think this case really can happen */
3747                     SendToICS(ics_prefix);
3748                     SendToICS("refresh\n");
3749                 }
3750                 continue;
3751             }
3752
3753             /* Error messages */
3754 //          if (ics_user_moved) {
3755             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756                 if (looking_at(buf, &i, "Illegal move") ||
3757                     looking_at(buf, &i, "Not a legal move") ||
3758                     looking_at(buf, &i, "Your king is in check") ||
3759                     looking_at(buf, &i, "It isn't your turn") ||
3760                     looking_at(buf, &i, "It is not your move")) {
3761                     /* Illegal move */
3762                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763                         currentMove = forwardMostMove-1;
3764                         DisplayMove(currentMove - 1); /* before DMError */
3765                         DrawPosition(FALSE, boards[currentMove]);
3766                         SwitchClocks(forwardMostMove-1); // [HGM] race
3767                         DisplayBothClocks();
3768                     }
3769                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3770                     ics_user_moved = 0;
3771                     continue;
3772                 }
3773             }
3774
3775             if (looking_at(buf, &i, "still have time") ||
3776                 looking_at(buf, &i, "not out of time") ||
3777                 looking_at(buf, &i, "either player is out of time") ||
3778                 looking_at(buf, &i, "has timeseal; checking")) {
3779                 /* We must have called his flag a little too soon */
3780                 whiteFlag = blackFlag = FALSE;
3781                 continue;
3782             }
3783
3784             if (looking_at(buf, &i, "added * seconds to") ||
3785                 looking_at(buf, &i, "seconds were added to")) {
3786                 /* Update the clocks */
3787                 SendToICS(ics_prefix);
3788                 SendToICS("refresh\n");
3789                 continue;
3790             }
3791
3792             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793                 ics_clock_paused = TRUE;
3794                 StopClocks();
3795                 continue;
3796             }
3797
3798             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799                 ics_clock_paused = FALSE;
3800                 StartClocks();
3801                 continue;
3802             }
3803
3804             /* Grab player ratings from the Creating: message.
3805                Note we have to check for the special case when
3806                the ICS inserts things like [white] or [black]. */
3807             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3809                 /* star_matches:
3810                    0    player 1 name (not necessarily white)
3811                    1    player 1 rating
3812                    2    empty, white, or black (IGNORED)
3813                    3    player 2 name (not necessarily black)
3814                    4    player 2 rating
3815
3816                    The names/ratings are sorted out when the game
3817                    actually starts (below).
3818                 */
3819                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820                 player1Rating = string_to_rating(star_match[1]);
3821                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822                 player2Rating = string_to_rating(star_match[4]);
3823
3824                 if (appData.debugMode)
3825                   fprintf(debugFP,
3826                           "Ratings from 'Creating:' %s %d, %s %d\n",
3827                           player1Name, player1Rating,
3828                           player2Name, player2Rating);
3829
3830                 continue;
3831             }
3832
3833             /* Improved generic start/end-of-game messages */
3834             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836                 /* If tkind == 0: */
3837                 /* star_match[0] is the game number */
3838                 /*           [1] is the white player's name */
3839                 /*           [2] is the black player's name */
3840                 /* For end-of-game: */
3841                 /*           [3] is the reason for the game end */
3842                 /*           [4] is a PGN end game-token, preceded by " " */
3843                 /* For start-of-game: */
3844                 /*           [3] begins with "Creating" or "Continuing" */
3845                 /*           [4] is " *" or empty (don't care). */
3846                 int gamenum = atoi(star_match[0]);
3847                 char *whitename, *blackname, *why, *endtoken;
3848                 ChessMove endtype = EndOfFile;
3849
3850                 if (tkind == 0) {
3851                   whitename = star_match[1];
3852                   blackname = star_match[2];
3853                   why = star_match[3];
3854                   endtoken = star_match[4];
3855                 } else {
3856                   whitename = star_match[1];
3857                   blackname = star_match[3];
3858                   why = star_match[5];
3859                   endtoken = star_match[6];
3860                 }
3861
3862                 /* Game start messages */
3863                 if (strncmp(why, "Creating ", 9) == 0 ||
3864                     strncmp(why, "Continuing ", 11) == 0) {
3865                     gs_gamenum = gamenum;
3866                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3867                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3868                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3869 #if ZIPPY
3870                     if (appData.zippyPlay) {
3871                         ZippyGameStart(whitename, blackname);
3872                     }
3873 #endif /*ZIPPY*/
3874                     partnerBoardValid = FALSE; // [HGM] bughouse
3875                     continue;
3876                 }
3877
3878                 /* Game end messages */
3879                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3880                     ics_gamenum != gamenum) {
3881                     continue;
3882                 }
3883                 while (endtoken[0] == ' ') endtoken++;
3884                 switch (endtoken[0]) {
3885                   case '*':
3886                   default:
3887                     endtype = GameUnfinished;
3888                     break;
3889                   case '0':
3890                     endtype = BlackWins;
3891                     break;
3892                   case '1':
3893                     if (endtoken[1] == '/')
3894                       endtype = GameIsDrawn;
3895                     else
3896                       endtype = WhiteWins;
3897                     break;
3898                 }
3899                 GameEnds(endtype, why, GE_ICS);
3900 #if ZIPPY
3901                 if (appData.zippyPlay && first.initDone) {
3902                     ZippyGameEnd(endtype, why);
3903                     if (first.pr == NoProc) {
3904                       /* Start the next process early so that we'll
3905                          be ready for the next challenge */
3906                       StartChessProgram(&first);
3907                     }
3908                     /* Send "new" early, in case this command takes
3909                        a long time to finish, so that we'll be ready
3910                        for the next challenge. */
3911                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3912                     Reset(TRUE, TRUE);
3913                 }
3914 #endif /*ZIPPY*/
3915                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3916                 continue;
3917             }
3918
3919             if (looking_at(buf, &i, "Removing game * from observation") ||
3920                 looking_at(buf, &i, "no longer observing game *") ||
3921                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3922                 if (gameMode == IcsObserving &&
3923                     atoi(star_match[0]) == ics_gamenum)
3924                   {
3925                       /* icsEngineAnalyze */
3926                       if (appData.icsEngineAnalyze) {
3927                             ExitAnalyzeMode();
3928                             ModeHighlight();
3929                       }
3930                       StopClocks();
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             if (looking_at(buf, &i, "no longer examining game *")) {
3939                 if (gameMode == IcsExamining &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       gameMode = IcsIdle;
3943                       ics_gamenum = -1;
3944                       ics_user_moved = FALSE;
3945                   }
3946                 continue;
3947             }
3948
3949             /* Advance leftover_start past any newlines we find,
3950                so only partial lines can get reparsed */
3951             if (looking_at(buf, &i, "\n")) {
3952                 prevColor = curColor;
3953                 if (curColor != ColorNormal) {
3954                     if (oldi > next_out) {
3955                         SendToPlayer(&buf[next_out], oldi - next_out);
3956                         next_out = oldi;
3957                     }
3958                     Colorize(ColorNormal, FALSE);
3959                     curColor = ColorNormal;
3960                 }
3961                 if (started == STARTED_BOARD) {
3962                     started = STARTED_NONE;
3963                     parse[parse_pos] = NULLCHAR;
3964                     ParseBoard12(parse);
3965                     ics_user_moved = 0;
3966
3967                     /* Send premove here */
3968                     if (appData.premove) {
3969                       char str[MSG_SIZ];
3970                       if (currentMove == 0 &&
3971                           gameMode == IcsPlayingWhite &&
3972                           appData.premoveWhite) {
3973                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                         SendToICS(str);
3977                       } else if (currentMove == 1 &&
3978                                  gameMode == IcsPlayingBlack &&
3979                                  appData.premoveBlack) {
3980                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3981                         if (appData.debugMode)
3982                           fprintf(debugFP, "Sending premove:\n");
3983                         SendToICS(str);
3984                       } else if (gotPremove) {
3985                         gotPremove = 0;
3986                         ClearPremoveHighlights();
3987                         if (appData.debugMode)
3988                           fprintf(debugFP, "Sending premove:\n");
3989                           UserMoveEvent(premoveFromX, premoveFromY,
3990                                         premoveToX, premoveToY,
3991                                         premovePromoChar);
3992                       }
3993                     }
3994
3995                     /* Usually suppress following prompt */
3996                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3997                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3998                         if (looking_at(buf, &i, "*% ")) {
3999                             savingComment = FALSE;
4000                             suppressKibitz = 0;
4001                         }
4002                     }
4003                     next_out = i;
4004                 } else if (started == STARTED_HOLDINGS) {
4005                     int gamenum;
4006                     char new_piece[MSG_SIZ];
4007                     started = STARTED_NONE;
4008                     parse[parse_pos] = NULLCHAR;
4009                     if (appData.debugMode)
4010                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4011                                                         parse, currentMove);
4012                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4013                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4014                         if (gameInfo.variant == VariantNormal) {
4015                           /* [HGM] We seem to switch variant during a game!
4016                            * Presumably no holdings were displayed, so we have
4017                            * to move the position two files to the right to
4018                            * create room for them!
4019                            */
4020                           VariantClass newVariant;
4021                           switch(gameInfo.boardWidth) { // base guess on board width
4022                                 case 9:  newVariant = VariantShogi; break;
4023                                 case 10: newVariant = VariantGreat; break;
4024                                 default: newVariant = VariantCrazyhouse; break;
4025                           }
4026                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4027                           /* Get a move list just to see the header, which
4028                              will tell us whether this is really bug or zh */
4029                           if (ics_getting_history == H_FALSE) {
4030                             ics_getting_history = H_REQUESTED;
4031                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4032                             SendToICS(str);
4033                           }
4034                         }
4035                         new_piece[0] = NULLCHAR;
4036                         sscanf(parse, "game %d white [%s black [%s <- %s",
4037                                &gamenum, white_holding, black_holding,
4038                                new_piece);
4039                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4040                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4041                         /* [HGM] copy holdings to board holdings area */
4042                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4043                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4044                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4045 #if ZIPPY
4046                         if (appData.zippyPlay && first.initDone) {
4047                             ZippyHoldings(white_holding, black_holding,
4048                                           new_piece);
4049                         }
4050 #endif /*ZIPPY*/
4051                         if (tinyLayout || smallLayout) {
4052                             char wh[16], bh[16];
4053                             PackHolding(wh, white_holding);
4054                             PackHolding(bh, black_holding);
4055                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4056                                     gameInfo.white, gameInfo.black);
4057                         } else {
4058                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4059                                     gameInfo.white, white_holding, _("vs."),
4060                                     gameInfo.black, black_holding);
4061                         }
4062                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4063                         DrawPosition(FALSE, boards[currentMove]);
4064                         DisplayTitle(str);
4065                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4066                         sscanf(parse, "game %d white [%s black [%s <- %s",
4067                                &gamenum, white_holding, black_holding,
4068                                new_piece);
4069                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4070                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4071                         /* [HGM] copy holdings to partner-board holdings area */
4072                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4073                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4074                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4075                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4076                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4077                       }
4078                     }
4079                     /* Suppress following prompt */
4080                     if (looking_at(buf, &i, "*% ")) {
4081                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4082                         savingComment = FALSE;
4083                         suppressKibitz = 0;
4084                     }
4085                     next_out = i;
4086                 }
4087                 continue;
4088             }
4089
4090             i++;                /* skip unparsed character and loop back */
4091         }
4092
4093         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4094 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4095 //          SendToPlayer(&buf[next_out], i - next_out);
4096             started != STARTED_HOLDINGS && leftover_start > next_out) {
4097             SendToPlayer(&buf[next_out], leftover_start - next_out);
4098             next_out = i;
4099         }
4100
4101         leftover_len = buf_len - leftover_start;
4102         /* if buffer ends with something we couldn't parse,
4103            reparse it after appending the next read */
4104
4105     } else if (count == 0) {
4106         RemoveInputSource(isr);
4107         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4108     } else {
4109         DisplayFatalError(_("Error reading from ICS"), error, 1);
4110     }
4111 }
4112
4113
4114 /* Board style 12 looks like this:
4115
4116    <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
4117
4118  * The "<12> " is stripped before it gets to this routine.  The two
4119  * trailing 0's (flip state and clock ticking) are later addition, and
4120  * some chess servers may not have them, or may have only the first.
4121  * Additional trailing fields may be added in the future.
4122  */
4123
4124 #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"
4125
4126 #define RELATION_OBSERVING_PLAYED    0
4127 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4128 #define RELATION_PLAYING_MYMOVE      1
4129 #define RELATION_PLAYING_NOTMYMOVE  -1
4130 #define RELATION_EXAMINING           2
4131 #define RELATION_ISOLATED_BOARD     -3
4132 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4133
4134 void
4135 ParseBoard12 (char *string)
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4443       board[5][BOARD_RGHT+1] = WhiteAngel;
4444       board[6][BOARD_RGHT+1] = WhiteMarshall;
4445       board[1][0] = BlackMarshall;
4446       board[2][0] = BlackAngel;
4447       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4448     }
4449     CopyBoard(boards[moveNum], board);
4450     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4451     if (moveNum == 0) {
4452         startedFromSetupPosition =
4453           !CompareBoards(board, initialPosition);
4454         if(startedFromSetupPosition)
4455             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4456     }
4457
4458     /* [HGM] Set castling rights. Take the outermost Rooks,
4459        to make it also work for FRC opening positions. Note that board12
4460        is really defective for later FRC positions, as it has no way to
4461        indicate which Rook can castle if they are on the same side of King.
4462        For the initial position we grant rights to the outermost Rooks,
4463        and remember thos rights, and we then copy them on positions
4464        later in an FRC game. This means WB might not recognize castlings with
4465        Rooks that have moved back to their original position as illegal,
4466        but in ICS mode that is not its job anyway.
4467     */
4468     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4469     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4470
4471         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472             if(board[0][i] == WhiteRook) j = i;
4473         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475             if(board[0][i] == WhiteRook) j = i;
4476         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4477         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4478             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4479         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4480         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4481             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4482         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4483
4484         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4485         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4486         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4487             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4488         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489             if(board[BOARD_HEIGHT-1][k] == bKing)
4490                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4491         if(gameInfo.variant == VariantTwoKings) {
4492             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4493             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4494             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4495         }
4496     } else { int r;
4497         r = boards[moveNum][CASTLING][0] = initialRights[0];
4498         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4499         r = boards[moveNum][CASTLING][1] = initialRights[1];
4500         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4501         r = boards[moveNum][CASTLING][3] = initialRights[3];
4502         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4503         r = boards[moveNum][CASTLING][4] = initialRights[4];
4504         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4505         /* wildcastle kludge: always assume King has rights */
4506         r = boards[moveNum][CASTLING][2] = initialRights[2];
4507         r = boards[moveNum][CASTLING][5] = initialRights[5];
4508     }
4509     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4510     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4511
4512
4513     if (ics_getting_history == H_GOT_REQ_HEADER ||
4514         ics_getting_history == H_GOT_UNREQ_HEADER) {
4515         /* This was an initial position from a move list, not
4516            the current position */
4517         return;
4518     }
4519
4520     /* Update currentMove and known move number limits */
4521     newMove = newGame || moveNum > forwardMostMove;
4522
4523     if (newGame) {
4524         forwardMostMove = backwardMostMove = currentMove = moveNum;
4525         if (gameMode == IcsExamining && moveNum == 0) {
4526           /* Workaround for ICS limitation: we are not told the wild
4527              type when starting to examine a game.  But if we ask for
4528              the move list, the move list header will tell us */
4529             ics_getting_history = H_REQUESTED;
4530             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4531             SendToICS(str);
4532         }
4533     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4534                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4535 #if ZIPPY
4536         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4537         /* [HGM] applied this also to an engine that is silently watching        */
4538         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4539             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4540             gameInfo.variant == currentlyInitializedVariant) {
4541           takeback = forwardMostMove - moveNum;
4542           for (i = 0; i < takeback; i++) {
4543             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4544             SendToProgram("undo\n", &first);
4545           }
4546         }
4547 #endif
4548
4549         forwardMostMove = moveNum;
4550         if (!pausing || currentMove > forwardMostMove)
4551           currentMove = forwardMostMove;
4552     } else {
4553         /* New part of history that is not contiguous with old part */
4554         if (pausing && gameMode == IcsExamining) {
4555             pauseExamInvalid = TRUE;
4556             forwardMostMove = pauseExamForwardMostMove;
4557             return;
4558         }
4559         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4560 #if ZIPPY
4561             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4562                 // [HGM] when we will receive the move list we now request, it will be
4563                 // fed to the engine from the first move on. So if the engine is not
4564                 // in the initial position now, bring it there.
4565                 InitChessProgram(&first, 0);
4566             }
4567 #endif
4568             ics_getting_history = H_REQUESTED;
4569             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4570             SendToICS(str);
4571         }
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573     }
4574
4575     /* Update the clocks */
4576     if (strchr(elapsed_time, '.')) {
4577       /* Time is in ms */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4580     } else {
4581       /* Time is in seconds */
4582       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4583       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4584     }
4585
4586
4587 #if ZIPPY
4588     if (appData.zippyPlay && newGame &&
4589         gameMode != IcsObserving && gameMode != IcsIdle &&
4590         gameMode != IcsExamining)
4591       ZippyFirstBoard(moveNum, basetime, increment);
4592 #endif
4593
4594     /* Put the move on the move list, first converting
4595        to canonical algebraic form. */
4596     if (moveNum > 0) {
4597   if (appData.debugMode) {
4598     if (appData.debugMode) { int f = forwardMostMove;
4599         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4600                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4601                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4602     }
4603     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4604     fprintf(debugFP, "moveNum = %d\n", moveNum);
4605     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4606     setbuf(debugFP, NULL);
4607   }
4608         if (moveNum <= backwardMostMove) {
4609             /* We don't know what the board looked like before
4610                this move.  Punt. */
4611           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4612             strcat(parseList[moveNum - 1], " ");
4613             strcat(parseList[moveNum - 1], elapsed_time);
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615         } else if (strcmp(move_str, "none") == 0) {
4616             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4617             /* Again, we don't know what the board looked like;
4618                this is really the start of the game. */
4619             parseList[moveNum - 1][0] = NULLCHAR;
4620             moveList[moveNum - 1][0] = NULLCHAR;
4621             backwardMostMove = moveNum;
4622             startedFromSetupPosition = TRUE;
4623             fromX = fromY = toX = toY = -1;
4624         } else {
4625           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4626           //                 So we parse the long-algebraic move string in stead of the SAN move
4627           int valid; char buf[MSG_SIZ], *prom;
4628
4629           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4630                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4631           // str looks something like "Q/a1-a2"; kill the slash
4632           if(str[1] == '/')
4633             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4634           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4635           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4636                 strcat(buf, prom); // long move lacks promo specification!
4637           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4638                 if(appData.debugMode)
4639                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4640                 safeStrCpy(move_str, buf, MSG_SIZ);
4641           }
4642           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4643                                 &fromX, &fromY, &toX, &toY, &promoChar)
4644                || ParseOneMove(buf, moveNum - 1, &moveType,
4645                                 &fromX, &fromY, &toX, &toY, &promoChar);
4646           // end of long SAN patch
4647           if (valid) {
4648             (void) CoordsToAlgebraic(boards[moveNum - 1],
4649                                      PosFlags(moveNum - 1),
4650                                      fromY, fromX, toY, toX, promoChar,
4651                                      parseList[moveNum-1]);
4652             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4653               case MT_NONE:
4654               case MT_STALEMATE:
4655               default:
4656                 break;
4657               case MT_CHECK:
4658                 if(gameInfo.variant != VariantShogi)
4659                     strcat(parseList[moveNum - 1], "+");
4660                 break;
4661               case MT_CHECKMATE:
4662               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4663                 strcat(parseList[moveNum - 1], "#");
4664                 break;
4665             }
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             /* currentMoveString is set as a side-effect of ParseOneMove */
4669             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4670             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4671             strcat(moveList[moveNum - 1], "\n");
4672
4673             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4674                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4675               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4676                 ChessSquare old, new = boards[moveNum][k][j];
4677                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4678                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4679                   if(old == new) continue;
4680                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4681                   else if(new == WhiteWazir || new == BlackWazir) {
4682                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4683                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4684                       else boards[moveNum][k][j] = old; // preserve type of Gold
4685                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4686                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4687               }
4688           } else {
4689             /* Move from ICS was illegal!?  Punt. */
4690             if (appData.debugMode) {
4691               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4692               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4693             }
4694             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4695             strcat(parseList[moveNum - 1], " ");
4696             strcat(parseList[moveNum - 1], elapsed_time);
4697             moveList[moveNum - 1][0] = NULLCHAR;
4698             fromX = fromY = toX = toY = -1;
4699           }
4700         }
4701   if (appData.debugMode) {
4702     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4703     setbuf(debugFP, NULL);
4704   }
4705
4706 #if ZIPPY
4707         /* Send move to chess program (BEFORE animating it). */
4708         if (appData.zippyPlay && !newGame && newMove &&
4709            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4710
4711             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4712                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4713                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4714                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4715                             move_str);
4716                     DisplayError(str, 0);
4717                 } else {
4718                     if (first.sendTime) {
4719                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4720                     }
4721                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4722                     if (firstMove && !bookHit) {
4723                         firstMove = FALSE;
4724                         if (first.useColors) {
4725                           SendToProgram(gameMode == IcsPlayingWhite ?
4726                                         "white\ngo\n" :
4727                                         "black\ngo\n", &first);
4728                         } else {
4729                           SendToProgram("go\n", &first);
4730                         }
4731                         first.maybeThinking = TRUE;
4732                     }
4733                 }
4734             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4735               if (moveList[moveNum - 1][0] == NULLCHAR) {
4736                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4737                 DisplayError(str, 0);
4738               } else {
4739                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4740                 SendMoveToProgram(moveNum - 1, &first);
4741               }
4742             }
4743         }
4744 #endif
4745     }
4746
4747     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4748         /* If move comes from a remote source, animate it.  If it
4749            isn't remote, it will have already been animated. */
4750         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4751             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4752         }
4753         if (!pausing && appData.highlightLastMove) {
4754             SetHighlights(fromX, fromY, toX, toY);
4755         }
4756     }
4757
4758     /* Start the clocks */
4759     whiteFlag = blackFlag = FALSE;
4760     appData.clockMode = !(basetime == 0 && increment == 0);
4761     if (ticking == 0) {
4762       ics_clock_paused = TRUE;
4763       StopClocks();
4764     } else if (ticking == 1) {
4765       ics_clock_paused = FALSE;
4766     }
4767     if (gameMode == IcsIdle ||
4768         relation == RELATION_OBSERVING_STATIC ||
4769         relation == RELATION_EXAMINING ||
4770         ics_clock_paused)
4771       DisplayBothClocks();
4772     else
4773       StartClocks();
4774
4775     /* Display opponents and material strengths */
4776     if (gameInfo.variant != VariantBughouse &&
4777         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4778         if (tinyLayout || smallLayout) {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment);
4783             else
4784               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, (int) gameInfo.variant);
4787         } else {
4788             if(gameInfo.variant == VariantNormal)
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment);
4792             else
4793               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4794                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4795                     basetime, increment, VariantName(gameInfo.variant));
4796         }
4797         DisplayTitle(str);
4798   if (appData.debugMode) {
4799     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4800   }
4801     }
4802
4803
4804     /* Display the board */
4805     if (!pausing && !appData.noGUI) {
4806
4807       if (appData.premove)
4808           if (!gotPremove ||
4809              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4810              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4811               ClearPremoveHighlights();
4812
4813       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4814         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4815       DrawPosition(j, boards[currentMove]);
4816
4817       DisplayMove(moveNum - 1);
4818       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4819             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4820               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4821         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4822       }
4823     }
4824
4825     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4826 #if ZIPPY
4827     if(bookHit) { // [HGM] book: simulate book reply
4828         static char bookMove[MSG_SIZ]; // a bit generous?
4829
4830         programStats.nodes = programStats.depth = programStats.time =
4831         programStats.score = programStats.got_only_move = 0;
4832         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4833
4834         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4835         strcat(bookMove, bookHit);
4836         HandleMachineMove(bookMove, &first);
4837     }
4838 #endif
4839 }
4840
4841 void
4842 GetMoveListEvent ()
4843 {
4844     char buf[MSG_SIZ];
4845     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4846         ics_getting_history = H_REQUESTED;
4847         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4848         SendToICS(buf);
4849     }
4850 }
4851
4852 void
4853 AnalysisPeriodicEvent (int force)
4854 {
4855     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4856          && !force) || !appData.periodicUpdates)
4857       return;
4858
4859     /* Send . command to Crafty to collect stats */
4860     SendToProgram(".\n", &first);
4861
4862     /* Don't send another until we get a response (this makes
4863        us stop sending to old Crafty's which don't understand
4864        the "." command (sending illegal cmds resets node count & time,
4865        which looks bad)) */
4866     programStats.ok_to_send = 0;
4867 }
4868
4869 void
4870 ics_update_width (int new_width)
4871 {
4872         ics_printf("set width %d\n", new_width);
4873 }
4874
4875 void
4876 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4877 {
4878     char buf[MSG_SIZ];
4879
4880     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4881         // null move in variant where engine does not understand it (for analysis purposes)
4882         SendBoard(cps, moveNum + 1); // send position after move in stead.
4883         return;
4884     }
4885     if (cps->useUsermove) {
4886       SendToProgram("usermove ", cps);
4887     }
4888     if (cps->useSAN) {
4889       char *space;
4890       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4891         int len = space - parseList[moveNum];
4892         memcpy(buf, parseList[moveNum], len);
4893         buf[len++] = '\n';
4894         buf[len] = NULLCHAR;
4895       } else {
4896         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4897       }
4898       SendToProgram(buf, cps);
4899     } else {
4900       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4901         AlphaRank(moveList[moveNum], 4);
4902         SendToProgram(moveList[moveNum], cps);
4903         AlphaRank(moveList[moveNum], 4); // and back
4904       } else
4905       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4906        * the engine. It would be nice to have a better way to identify castle
4907        * moves here. */
4908       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4909                                                                          && cps->useOOCastle) {
4910         int fromX = moveList[moveNum][0] - AAA;
4911         int fromY = moveList[moveNum][1] - ONE;
4912         int toX = moveList[moveNum][2] - AAA;
4913         int toY = moveList[moveNum][3] - ONE;
4914         if((boards[moveNum][fromY][fromX] == WhiteKing
4915             && boards[moveNum][toY][toX] == WhiteRook)
4916            || (boards[moveNum][fromY][fromX] == BlackKing
4917                && boards[moveNum][toY][toX] == BlackRook)) {
4918           if(toX > fromX) SendToProgram("O-O\n", cps);
4919           else SendToProgram("O-O-O\n", cps);
4920         }
4921         else SendToProgram(moveList[moveNum], cps);
4922       } else
4923       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4924         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4925           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4926           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4927                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4928         } else
4929           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4930                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4931         SendToProgram(buf, cps);
4932       }
4933       else SendToProgram(moveList[moveNum], cps);
4934       /* End of additions by Tord */
4935     }
4936
4937     /* [HGM] setting up the opening has brought engine in force mode! */
4938     /*       Send 'go' if we are in a mode where machine should play. */
4939     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4940         (gameMode == TwoMachinesPlay   ||
4941 #if ZIPPY
4942          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4943 #endif
4944          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4945         SendToProgram("go\n", cps);
4946   if (appData.debugMode) {
4947     fprintf(debugFP, "(extra)\n");
4948   }
4949     }
4950     setboardSpoiledMachineBlack = 0;
4951 }
4952
4953 void
4954 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4955 {
4956     char user_move[MSG_SIZ];
4957     char suffix[4];
4958
4959     if(gameInfo.variant == VariantSChess && promoChar) {
4960         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4961         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4962     } else suffix[0] = NULLCHAR;
4963
4964     switch (moveType) {
4965       default:
4966         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4967                 (int)moveType, fromX, fromY, toX, toY);
4968         DisplayError(user_move + strlen("say "), 0);
4969         break;
4970       case WhiteKingSideCastle:
4971       case BlackKingSideCastle:
4972       case WhiteQueenSideCastleWild:
4973       case BlackQueenSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteHSideCastleFR:
4976       case BlackHSideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4979         break;
4980       case WhiteQueenSideCastle:
4981       case BlackQueenSideCastle:
4982       case WhiteKingSideCastleWild:
4983       case BlackKingSideCastleWild:
4984       /* PUSH Fabien */
4985       case WhiteASideCastleFR:
4986       case BlackASideCastleFR:
4987       /* POP Fabien */
4988         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4989         break;
4990       case WhiteNonPromotion:
4991       case BlackNonPromotion:
4992         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994       case WhitePromotion:
4995       case BlackPromotion:
4996         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteFerz));
5000         else if(gameInfo.variant == VariantGreat)
5001           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 PieceToChar(WhiteMan));
5004         else
5005           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5006                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5007                 promoChar);
5008         break;
5009       case WhiteDrop:
5010       case BlackDrop:
5011       drop:
5012         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5013                  ToUpper(PieceToChar((ChessSquare) fromX)),
5014                  AAA + toX, ONE + toY);
5015         break;
5016       case IllegalMove:  /* could be a variant we don't quite understand */
5017         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5018       case NormalMove:
5019       case WhiteCapturesEnPassant:
5020       case BlackCapturesEnPassant:
5021         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024     }
5025     SendToICS(user_move);
5026     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5027         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5028 }
5029
5030 void
5031 UploadGameEvent ()
5032 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5033     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5034     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5035     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5036       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5037       return;
5038     }
5039     if(gameMode != IcsExamining) { // is this ever not the case?
5040         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5041
5042         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5043           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5044         } else { // on FICS we must first go to general examine mode
5045           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5046         }
5047         if(gameInfo.variant != VariantNormal) {
5048             // try figure out wild number, as xboard names are not always valid on ICS
5049             for(i=1; i<=36; i++) {
5050               snprintf(buf, MSG_SIZ, "wild/%d", i);
5051                 if(StringToVariant(buf) == gameInfo.variant) break;
5052             }
5053             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5054             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5055             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5056         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5057         SendToICS(ics_prefix);
5058         SendToICS(buf);
5059         if(startedFromSetupPosition || backwardMostMove != 0) {
5060           fen = PositionToFEN(backwardMostMove, NULL);
5061           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5062             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5063             SendToICS(buf);
5064           } else { // FICS: everything has to set by separate bsetup commands
5065             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5066             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5067             SendToICS(buf);
5068             if(!WhiteOnMove(backwardMostMove)) {
5069                 SendToICS("bsetup tomove black\n");
5070             }
5071             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5072             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5075             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5076             SendToICS(buf);
5077             i = boards[backwardMostMove][EP_STATUS];
5078             if(i >= 0) { // set e.p.
5079               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5080                 SendToICS(buf);
5081             }
5082             bsetup++;
5083           }
5084         }
5085       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5086             SendToICS("bsetup done\n"); // switch to normal examining.
5087     }
5088     for(i = backwardMostMove; i<last; i++) {
5089         char buf[20];
5090         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5091         SendToICS(buf);
5092     }
5093     SendToICS(ics_prefix);
5094     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5095 }
5096
5097 void
5098 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5099 {
5100     if (rf == DROP_RANK) {
5101       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5102       sprintf(move, "%c@%c%c\n",
5103                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5104     } else {
5105         if (promoChar == 'x' || promoChar == NULLCHAR) {
5106           sprintf(move, "%c%c%c%c\n",
5107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5108         } else {
5109             sprintf(move, "%c%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5111         }
5112     }
5113 }
5114
5115 void
5116 ProcessICSInitScript (FILE *f)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     while (fgets(buf, MSG_SIZ, f)) {
5121         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5122     }
5123
5124     fclose(f);
5125 }
5126
5127
5128 static int lastX, lastY, selectFlag, dragging;
5129
5130 void
5131 Sweep (int step)
5132 {
5133     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5134     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5135     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5136     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5137     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5138     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5139     do {
5140         promoSweep -= step;
5141         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5142         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5143         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5144         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5145         if(!step) step = -1;
5146     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5147             appData.testLegality && (promoSweep == king ||
5148             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5149     ChangeDragPiece(promoSweep);
5150 }
5151
5152 int
5153 PromoScroll (int x, int y)
5154 {
5155   int step = 0;
5156
5157   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5158   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5159   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5160   if(!step) return FALSE;
5161   lastX = x; lastY = y;
5162   if((promoSweep < BlackPawn) == flipView) step = -step;
5163   if(step > 0) selectFlag = 1;
5164   if(!selectFlag) Sweep(step);
5165   return FALSE;
5166 }
5167
5168 void
5169 NextPiece (int step)
5170 {
5171     ChessSquare piece = boards[currentMove][toY][toX];
5172     do {
5173         pieceSweep -= step;
5174         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5175         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5176         if(!step) step = -1;
5177     } while(PieceToChar(pieceSweep) == '.');
5178     boards[currentMove][toY][toX] = pieceSweep;
5179     DrawPosition(FALSE, boards[currentMove]);
5180     boards[currentMove][toY][toX] = piece;
5181 }
5182 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5183 void
5184 AlphaRank (char *move, int n)
5185 {
5186 //    char *p = move, c; int x, y;
5187
5188     if (appData.debugMode) {
5189         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5190     }
5191
5192     if(move[1]=='*' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         move[1] = '@';
5196         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5197         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5198     } else
5199     if(move[0]>='0' && move[0]<='9' &&
5200        move[1]>='a' && move[1]<='x' &&
5201        move[2]>='0' && move[2]<='9' &&
5202        move[3]>='a' && move[3]<='x'    ) {
5203         /* input move, Shogi -> normal */
5204         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5205         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5208     } else
5209     if(move[1]=='@' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212         move[1] = '*';
5213         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5215     } else
5216     if(
5217        move[0]>='a' && move[0]<='x' &&
5218        move[3]>='0' && move[3]<='9' &&
5219        move[2]>='a' && move[2]<='x'    ) {
5220          /* output move, normal -> Shogi */
5221         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5225         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5226     }
5227     if (appData.debugMode) {
5228         fprintf(debugFP, "   out = '%s'\n", move);
5229     }
5230 }
5231
5232 char yy_textstr[8000];
5233
5234 /* Parser for moves from gnuchess, ICS, or user typein box */
5235 Boolean
5236 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336     if(!valid && nr == 0 &&
5337        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5338         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5339         // Hande case where played move is different from leading PV move
5340         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5341         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5343         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5344           endPV += 2; // if position different, keep this
5345           moveList[endPV-1][0] = fromX + AAA;
5346           moveList[endPV-1][1] = fromY + ONE;
5347           moveList[endPV-1][2] = toX + AAA;
5348           moveList[endPV-1][3] = toY + ONE;
5349           parseList[endPV-1][0] = NULLCHAR;
5350           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5351         }
5352       }
5353     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5354     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5355     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5356     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5357         valid++; // allow comments in PV
5358         continue;
5359     }
5360     nr++;
5361     if(endPV+1 > framePtr) break; // no space, truncate
5362     if(!valid) break;
5363     endPV++;
5364     CopyBoard(boards[endPV], boards[endPV-1]);
5365     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5367     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5368     CoordsToAlgebraic(boards[endPV - 1],
5369                              PosFlags(endPV - 1),
5370                              fromY, fromX, toY, toX, promoChar,
5371                              parseList[endPV - 1]);
5372   } while(valid);
5373   if(atEnd == 2) return; // used hidden, for PV conversion
5374   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5375   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5376   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5377                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5378   DrawPosition(TRUE, boards[currentMove]);
5379 }
5380
5381 int
5382 MultiPV (ChessProgramState *cps)
5383 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5384         int i;
5385         for(i=0; i<cps->nrOptions; i++)
5386             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5387                 return i;
5388         return -1;
5389 }
5390
5391 Boolean
5392 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5393 {
5394         int startPV, multi, lineStart, origIndex = index;
5395         char *p, buf2[MSG_SIZ];
5396
5397         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5398         lastX = x; lastY = y;
5399         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5400         lineStart = startPV = index;
5401         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5402         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5403         index = startPV;
5404         do{ while(buf[index] && buf[index] != '\n') index++;
5405         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5406         buf[index] = 0;
5407         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5408                 int n = first.option[multi].value;
5409                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5410                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5411                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5412                 first.option[multi].value = n;
5413                 *start = *end = 0;
5414                 return FALSE;
5415         }
5416         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5417         *start = startPV; *end = index-1;
5418         return TRUE;
5419 }
5420
5421 char *
5422 PvToSAN (char *pv)
5423 {
5424         static char buf[10*MSG_SIZ];
5425         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5426         *buf = NULLCHAR;
5427         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5428         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5429         for(i = forwardMostMove; i<endPV; i++){
5430             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5431             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5432             k += strlen(buf+k);
5433         }
5434         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5435         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5436         endPV = savedEnd;
5437         return buf;
5438 }
5439
5440 Boolean
5441 LoadPV (int x, int y)
5442 { // called on right mouse click to load PV
5443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5444   lastX = x; lastY = y;
5445   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5446   return TRUE;
5447 }
5448
5449 void
5450 UnLoadPV ()
5451 {
5452   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5453   if(endPV < 0) return;
5454   endPV = -1;
5455   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5456         Boolean saveAnimate = appData.animate;
5457         if(pushed) {
5458             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5459                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5460             } else storedGames--; // abandon shelved tail of original game
5461         }
5462         pushed = FALSE;
5463         forwardMostMove = currentMove;
5464         currentMove = oldFMM;
5465         appData.animate = FALSE;
5466         ToNrEvent(forwardMostMove);
5467         appData.animate = saveAnimate;
5468   }
5469   currentMove = forwardMostMove;
5470   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5471   ClearPremoveHighlights();
5472   DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 MovePV (int x, int y, int h)
5477 { // step through PV based on mouse coordinates (called on mouse move)
5478   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5479
5480   // we must somehow check if right button is still down (might be released off board!)
5481   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5482   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5483   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5484   if(!step) return;
5485   lastX = x; lastY = y;
5486
5487   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5488   if(endPV < 0) return;
5489   if(y < margin) step = 1; else
5490   if(y > h - margin) step = -1;
5491   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5492   currentMove += step;
5493   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5494   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5495                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5496   DrawPosition(FALSE, boards[currentMove]);
5497 }
5498
5499
5500 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5501 // All positions will have equal probability, but the current method will not provide a unique
5502 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5503 #define DARK 1
5504 #define LITE 2
5505 #define ANY 3
5506
5507 int squaresLeft[4];
5508 int piecesLeft[(int)BlackPawn];
5509 int seed, nrOfShuffles;
5510
5511 void
5512 GetPositionNumber ()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int
5525 put (Board board, int pieceType, int rank, int n, int shade)
5526 // put the piece on the (n-1)-th empty squares of the given shade
5527 {
5528         int i;
5529
5530         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5531                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5532                         board[rank][i] = (ChessSquare) pieceType;
5533                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5534                         squaresLeft[ANY]--;
5535                         piecesLeft[pieceType]--;
5536                         return i;
5537                 }
5538         }
5539         return -1;
5540 }
5541
5542
5543 void
5544 AddOnePiece (Board board, int pieceType, int rank, int shade)
5545 // calculate where the next piece goes, (any empty square), and put it there
5546 {
5547         int i;
5548
5549         i = seed % squaresLeft[shade];
5550         nrOfShuffles *= squaresLeft[shade];
5551         seed /= squaresLeft[shade];
5552         put(board, pieceType, rank, i, shade);
5553 }
5554
5555 void
5556 AddTwoPieces (Board board, int pieceType, int rank)
5557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5558 {
5559         int i, n=squaresLeft[ANY], j=n-1, k;
5560
5561         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5562         i = seed % k;  // pick one
5563         nrOfShuffles *= k;
5564         seed /= k;
5565         while(i >= j) i -= j--;
5566         j = n - 1 - j; i += j;
5567         put(board, pieceType, rank, j, ANY);
5568         put(board, pieceType, rank, i, ANY);
5569 }
5570
5571 void
5572 SetUpShuffle (Board board, int number)
5573 {
5574         int i, p, first=1;
5575
5576         GetPositionNumber(); nrOfShuffles = 1;
5577
5578         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5579         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5580         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5581
5582         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5583
5584         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5585             p = (int) board[0][i];
5586             if(p < (int) BlackPawn) piecesLeft[p] ++;
5587             board[0][i] = EmptySquare;
5588         }
5589
5590         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5591             // shuffles restricted to allow normal castling put KRR first
5592             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5593                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5594             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5595                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5596             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5597                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5598             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5599                 put(board, WhiteRook, 0, 0, ANY);
5600             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5601         }
5602
5603         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5604             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5605             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5606                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5607                 while(piecesLeft[p] >= 2) {
5608                     AddOnePiece(board, p, 0, LITE);
5609                     AddOnePiece(board, p, 0, DARK);
5610                 }
5611                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5612             }
5613
5614         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5615             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5616             // but we leave King and Rooks for last, to possibly obey FRC restriction
5617             if(p == (int)WhiteRook) continue;
5618             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5619             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5620         }
5621
5622         // now everything is placed, except perhaps King (Unicorn) and Rooks
5623
5624         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5625             // Last King gets castling rights
5626             while(piecesLeft[(int)WhiteUnicorn]) {
5627                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5628                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5629             }
5630
5631             while(piecesLeft[(int)WhiteKing]) {
5632                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5633                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5634             }
5635
5636
5637         } else {
5638             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5639             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5640         }
5641
5642         // Only Rooks can be left; simply place them all
5643         while(piecesLeft[(int)WhiteRook]) {
5644                 i = put(board, WhiteRook, 0, 0, ANY);
5645                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5646                         if(first) {
5647                                 first=0;
5648                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5649                         }
5650                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5651                 }
5652         }
5653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5654             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5655         }
5656
5657         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5658 }
5659
5660 int
5661 SetCharTable (char *table, const char * map)
5662 /* [HGM] moved here from winboard.c because of its general usefulness */
5663 /*       Basically a safe strcpy that uses the last character as King */
5664 {
5665     int result = FALSE; int NrPieces;
5666
5667     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5668                     && NrPieces >= 12 && !(NrPieces&1)) {
5669         int i; /* [HGM] Accept even length from 12 to 34 */
5670
5671         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5672         for( i=0; i<NrPieces/2-1; i++ ) {
5673             table[i] = map[i];
5674             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5675         }
5676         table[(int) WhiteKing]  = map[NrPieces/2-1];
5677         table[(int) BlackKing]  = map[NrPieces-1];
5678
5679         result = TRUE;
5680     }
5681
5682     return result;
5683 }
5684
5685 void
5686 Prelude (Board board)
5687 {       // [HGM] superchess: random selection of exo-pieces
5688         int i, j, k; ChessSquare p;
5689         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5690
5691         GetPositionNumber(); // use FRC position number
5692
5693         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5694             SetCharTable(pieceToChar, appData.pieceToCharTable);
5695             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5696                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5697         }
5698
5699         j = seed%4;                 seed /= 4;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3 + (seed%3 >= j); seed /= 3;
5704         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%3;                 seed /= 3;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%2 + (seed%2 >= j); seed /= 2;
5712         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5713         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5714         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5715         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5716         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5717         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5718         put(board, exoPieces[0],    0, 0, ANY);
5719         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5720 }
5721
5722 void
5723 InitPosition (int redraw)
5724 {
5725     ChessSquare (* pieces)[BOARD_FILES];
5726     int i, j, pawnRow, overrule,
5727     oldx = gameInfo.boardWidth,
5728     oldy = gameInfo.boardHeight,
5729     oldh = gameInfo.holdingsWidth;
5730     static int oldv;
5731
5732     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5733
5734     /* [AS] Initialize pv info list [HGM] and game status */
5735     {
5736         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5737             pvInfoList[i].depth = 0;
5738             boards[i][EP_STATUS] = EP_NONE;
5739             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5740         }
5741
5742         initialRulePlies = 0; /* 50-move counter start */
5743
5744         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5745         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5746     }
5747
5748
5749     /* [HGM] logic here is completely changed. In stead of full positions */
5750     /* the initialized data only consist of the two backranks. The switch */
5751     /* selects which one we will use, which is than copied to the Board   */
5752     /* initialPosition, which for the rest is initialized by Pawns and    */
5753     /* empty squares. This initial position is then copied to boards[0],  */
5754     /* possibly after shuffling, so that it remains available.            */
5755
5756     gameInfo.holdingsWidth = 0; /* default board sizes */
5757     gameInfo.boardWidth    = 8;
5758     gameInfo.boardHeight   = 8;
5759     gameInfo.holdingsSize  = 0;
5760     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5761     for(i=0; i<BOARD_FILES-2; i++)
5762       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5763     initialPosition[EP_STATUS] = EP_NONE;
5764     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5765     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5766          SetCharTable(pieceNickName, appData.pieceNickNames);
5767     else SetCharTable(pieceNickName, "............");
5768     pieces = FIDEArray;
5769
5770     switch (gameInfo.variant) {
5771     case VariantFischeRandom:
5772       shuffleOpenings = TRUE;
5773     default:
5774       break;
5775     case VariantShatranj:
5776       pieces = ShatranjArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5779       break;
5780     case VariantMakruk:
5781       pieces = makrukArray;
5782       nrCastlingRights = 0;
5783       startedFromSetupPosition = TRUE;
5784       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5785       break;
5786     case VariantTwoKings:
5787       pieces = twoKingsArray;
5788       break;
5789     case VariantGrand:
5790       pieces = GrandArray;
5791       nrCastlingRights = 0;
5792       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5793       gameInfo.boardWidth = 10;
5794       gameInfo.boardHeight = 10;
5795       gameInfo.holdingsSize = 7;
5796       break;
5797     case VariantCapaRandom:
5798       shuffleOpenings = TRUE;
5799     case VariantCapablanca:
5800       pieces = CapablancaArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantGothic:
5805       pieces = GothicArray;
5806       gameInfo.boardWidth = 10;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       break;
5809     case VariantSChess:
5810       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5811       gameInfo.holdingsSize = 7;
5812       break;
5813     case VariantJanus:
5814       pieces = JanusArray;
5815       gameInfo.boardWidth = 10;
5816       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5817       nrCastlingRights = 6;
5818         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5819         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5820         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5821         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5822         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5823         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5824       break;
5825     case VariantFalcon:
5826       pieces = FalconArray;
5827       gameInfo.boardWidth = 10;
5828       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5829       break;
5830     case VariantXiangqi:
5831       pieces = XiangqiArray;
5832       gameInfo.boardWidth  = 9;
5833       gameInfo.boardHeight = 10;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5836       break;
5837     case VariantShogi:
5838       pieces = ShogiArray;
5839       gameInfo.boardWidth  = 9;
5840       gameInfo.boardHeight = 9;
5841       gameInfo.holdingsSize = 7;
5842       nrCastlingRights = 0;
5843       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5844       break;
5845     case VariantCourier:
5846       pieces = CourierArray;
5847       gameInfo.boardWidth  = 12;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5850       break;
5851     case VariantKnightmate:
5852       pieces = KnightmateArray;
5853       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5854       break;
5855     case VariantSpartan:
5856       pieces = SpartanArray;
5857       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5858       break;
5859     case VariantFairy:
5860       pieces = fairyArray;
5861       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5862       break;
5863     case VariantGreat:
5864       pieces = GreatArray;
5865       gameInfo.boardWidth = 10;
5866       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5867       gameInfo.holdingsSize = 8;
5868       break;
5869     case VariantSuper:
5870       pieces = FIDEArray;
5871       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5872       gameInfo.holdingsSize = 8;
5873       startedFromSetupPosition = TRUE;
5874       break;
5875     case VariantCrazyhouse:
5876     case VariantBughouse:
5877       pieces = FIDEArray;
5878       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5879       gameInfo.holdingsSize = 5;
5880       break;
5881     case VariantWildCastle:
5882       pieces = FIDEArray;
5883       /* !!?shuffle with kings guaranteed to be on d or e file */
5884       shuffleOpenings = 1;
5885       break;
5886     case VariantNoCastle:
5887       pieces = FIDEArray;
5888       nrCastlingRights = 0;
5889       /* !!?unconstrained back-rank shuffle */
5890       shuffleOpenings = 1;
5891       break;
5892     }
5893
5894     overrule = 0;
5895     if(appData.NrFiles >= 0) {
5896         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5897         gameInfo.boardWidth = appData.NrFiles;
5898     }
5899     if(appData.NrRanks >= 0) {
5900         gameInfo.boardHeight = appData.NrRanks;
5901     }
5902     if(appData.holdingsSize >= 0) {
5903         i = appData.holdingsSize;
5904         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5905         gameInfo.holdingsSize = i;
5906     }
5907     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5908     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5909         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5910
5911     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5912     if(pawnRow < 1) pawnRow = 1;
5913     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5914
5915     /* User pieceToChar list overrules defaults */
5916     if(appData.pieceToCharTable != NULL)
5917         SetCharTable(pieceToChar, appData.pieceToCharTable);
5918
5919     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5920
5921         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5922             s = (ChessSquare) 0; /* account holding counts in guard band */
5923         for( i=0; i<BOARD_HEIGHT; i++ )
5924             initialPosition[i][j] = s;
5925
5926         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5927         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5928         initialPosition[pawnRow][j] = WhitePawn;
5929         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5930         if(gameInfo.variant == VariantXiangqi) {
5931             if(j&1) {
5932                 initialPosition[pawnRow][j] =
5933                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5934                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5935                    initialPosition[2][j] = WhiteCannon;
5936                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5937                 }
5938             }
5939         }
5940         if(gameInfo.variant == VariantGrand) {
5941             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5942                initialPosition[0][j] = WhiteRook;
5943                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5944             }
5945         }
5946         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5947     }
5948     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5949
5950             j=BOARD_LEFT+1;
5951             initialPosition[1][j] = WhiteBishop;
5952             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5953             j=BOARD_RGHT-2;
5954             initialPosition[1][j] = WhiteRook;
5955             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5956     }
5957
5958     if( nrCastlingRights == -1) {
5959         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5960         /*       This sets default castling rights from none to normal corners   */
5961         /* Variants with other castling rights must set them themselves above    */
5962         nrCastlingRights = 6;
5963
5964         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5966         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5967         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5968         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5969         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5970      }
5971
5972      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5973      if(gameInfo.variant == VariantGreat) { // promotion commoners
5974         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5976         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5978      }
5979      if( gameInfo.variant == VariantSChess ) {
5980       initialPosition[1][0] = BlackMarshall;
5981       initialPosition[2][0] = BlackAngel;
5982       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5983       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5984       initialPosition[1][1] = initialPosition[2][1] = 
5985       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5986      }
5987   if (appData.debugMode) {
5988     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5989   }
5990     if(shuffleOpenings) {
5991         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5992         startedFromSetupPosition = TRUE;
5993     }
5994     if(startedFromPositionFile) {
5995       /* [HGM] loadPos: use PositionFile for every new game */
5996       CopyBoard(initialPosition, filePosition);
5997       for(i=0; i<nrCastlingRights; i++)
5998           initialRights[i] = filePosition[CASTLING][i];
5999       startedFromSetupPosition = TRUE;
6000     }
6001
6002     CopyBoard(boards[0], initialPosition);
6003
6004     if(oldx != gameInfo.boardWidth ||
6005        oldy != gameInfo.boardHeight ||
6006        oldv != gameInfo.variant ||
6007        oldh != gameInfo.holdingsWidth
6008                                          )
6009             InitDrawingSizes(-2 ,0);
6010
6011     oldv = gameInfo.variant;
6012     if (redraw)
6013       DrawPosition(TRUE, boards[currentMove]);
6014 }
6015
6016 void
6017 SendBoard (ChessProgramState *cps, int moveNum)
6018 {
6019     char message[MSG_SIZ];
6020
6021     if (cps->useSetboard) {
6022       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6023       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6024       SendToProgram(message, cps);
6025       free(fen);
6026
6027     } else {
6028       ChessSquare *bp;
6029       int i, j, left=0, right=BOARD_WIDTH;
6030       /* Kludge to set black to move, avoiding the troublesome and now
6031        * deprecated "black" command.
6032        */
6033       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6034         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6035
6036       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6037
6038       SendToProgram("edit\n", cps);
6039       SendToProgram("#\n", cps);
6040       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041         bp = &boards[moveNum][i][left];
6042         for (j = left; j < right; j++, bp++) {
6043           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6044           if ((int) *bp < (int) BlackPawn) {
6045             if(j == BOARD_RGHT+1)
6046                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6047             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6048             if(message[0] == '+' || message[0] == '~') {
6049               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6050                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6051                         AAA + j, ONE + i);
6052             }
6053             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6054                 message[1] = BOARD_RGHT   - 1 - j + '1';
6055                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6056             }
6057             SendToProgram(message, cps);
6058           }
6059         }
6060       }
6061
6062       SendToProgram("c\n", cps);
6063       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6064         bp = &boards[moveNum][i][left];
6065         for (j = left; j < right; j++, bp++) {
6066           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6067           if (((int) *bp != (int) EmptySquare)
6068               && ((int) *bp >= (int) BlackPawn)) {
6069             if(j == BOARD_LEFT-2)
6070                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6071             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6072                     AAA + j, ONE + i);
6073             if(message[0] == '+' || message[0] == '~') {
6074               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6075                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6076                         AAA + j, ONE + i);
6077             }
6078             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6079                 message[1] = BOARD_RGHT   - 1 - j + '1';
6080                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6081             }
6082             SendToProgram(message, cps);
6083           }
6084         }
6085       }
6086
6087       SendToProgram(".\n", cps);
6088     }
6089     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6090 }
6091
6092 ChessSquare
6093 DefaultPromoChoice (int white)
6094 {
6095     ChessSquare result;
6096     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6097         result = WhiteFerz; // no choice
6098     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6099         result= WhiteKing; // in Suicide Q is the last thing we want
6100     else if(gameInfo.variant == VariantSpartan)
6101         result = white ? WhiteQueen : WhiteAngel;
6102     else result = WhiteQueen;
6103     if(!white) result = WHITE_TO_BLACK result;
6104     return result;
6105 }
6106
6107 static int autoQueen; // [HGM] oneclick
6108
6109 int
6110 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6111 {
6112     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6113     /* [HGM] add Shogi promotions */
6114     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6115     ChessSquare piece;
6116     ChessMove moveType;
6117     Boolean premove;
6118
6119     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6120     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6121
6122     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6123       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6124         return FALSE;
6125
6126     piece = boards[currentMove][fromY][fromX];
6127     if(gameInfo.variant == VariantShogi) {
6128         promotionZoneSize = BOARD_HEIGHT/3;
6129         highestPromotingPiece = (int)WhiteFerz;
6130     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6131         promotionZoneSize = 3;
6132     }
6133
6134     // Treat Lance as Pawn when it is not representing Amazon
6135     if(gameInfo.variant != VariantSuper) {
6136         if(piece == WhiteLance) piece = WhitePawn; else
6137         if(piece == BlackLance) piece = BlackPawn;
6138     }
6139
6140     // next weed out all moves that do not touch the promotion zone at all
6141     if((int)piece >= BlackPawn) {
6142         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6143              return FALSE;
6144         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6145     } else {
6146         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6147            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6148     }
6149
6150     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6151
6152     // weed out mandatory Shogi promotions
6153     if(gameInfo.variant == VariantShogi) {
6154         if(piece >= BlackPawn) {
6155             if(toY == 0 && piece == BlackPawn ||
6156                toY == 0 && piece == BlackQueen ||
6157                toY <= 1 && piece == BlackKnight) {
6158                 *promoChoice = '+';
6159                 return FALSE;
6160             }
6161         } else {
6162             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6163                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6164                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6165                 *promoChoice = '+';
6166                 return FALSE;
6167             }
6168         }
6169     }
6170
6171     // weed out obviously illegal Pawn moves
6172     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6173         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6174         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6175         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6176         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6177         // note we are not allowed to test for valid (non-)capture, due to premove
6178     }
6179
6180     // we either have a choice what to promote to, or (in Shogi) whether to promote
6181     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6182         *promoChoice = PieceToChar(BlackFerz);  // no choice
6183         return FALSE;
6184     }
6185     // no sense asking what we must promote to if it is going to explode...
6186     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6187         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6188         return FALSE;
6189     }
6190     // give caller the default choice even if we will not make it
6191     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6192     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6193     if(        sweepSelect && gameInfo.variant != VariantGreat
6194                            && gameInfo.variant != VariantGrand
6195                            && gameInfo.variant != VariantSuper) return FALSE;
6196     if(autoQueen) return FALSE; // predetermined
6197
6198     // suppress promotion popup on illegal moves that are not premoves
6199     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6200               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6201     if(appData.testLegality && !premove) {
6202         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6203                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6204         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6205             return FALSE;
6206     }
6207
6208     return TRUE;
6209 }
6210
6211 int
6212 InPalace (int row, int column)
6213 {   /* [HGM] for Xiangqi */
6214     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6215          column < (BOARD_WIDTH + 4)/2 &&
6216          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6217     return FALSE;
6218 }
6219
6220 int
6221 PieceForSquare (int x, int y)
6222 {
6223   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6224      return -1;
6225   else
6226      return boards[currentMove][y][x];
6227 }
6228
6229 int
6230 OKToStartUserMove (int x, int y)
6231 {
6232     ChessSquare from_piece;
6233     int white_piece;
6234
6235     if (matchMode) return FALSE;
6236     if (gameMode == EditPosition) return TRUE;
6237
6238     if (x >= 0 && y >= 0)
6239       from_piece = boards[currentMove][y][x];
6240     else
6241       from_piece = EmptySquare;
6242
6243     if (from_piece == EmptySquare) return FALSE;
6244
6245     white_piece = (int)from_piece >= (int)WhitePawn &&
6246       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6247
6248     switch (gameMode) {
6249       case AnalyzeFile:
6250       case TwoMachinesPlay:
6251       case EndOfGame:
6252         return FALSE;
6253
6254       case IcsObserving:
6255       case IcsIdle:
6256         return FALSE;
6257
6258       case MachinePlaysWhite:
6259       case IcsPlayingBlack:
6260         if (appData.zippyPlay) return FALSE;
6261         if (white_piece) {
6262             DisplayMoveError(_("You are playing Black"));
6263             return FALSE;
6264         }
6265         break;
6266
6267       case MachinePlaysBlack:
6268       case IcsPlayingWhite:
6269         if (appData.zippyPlay) return FALSE;
6270         if (!white_piece) {
6271             DisplayMoveError(_("You are playing White"));
6272             return FALSE;
6273         }
6274         break;
6275
6276       case PlayFromGameFile:
6277             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6278       case EditGame:
6279         if (!white_piece && WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is White's turn"));
6281             return FALSE;
6282         }
6283         if (white_piece && !WhiteOnMove(currentMove)) {
6284             DisplayMoveError(_("It is Black's turn"));
6285             return FALSE;
6286         }
6287         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6288             /* Editing correspondence game history */
6289             /* Could disallow this or prompt for confirmation */
6290             cmailOldMove = -1;
6291         }
6292         break;
6293
6294       case BeginningOfGame:
6295         if (appData.icsActive) return FALSE;
6296         if (!appData.noChessProgram) {
6297             if (!white_piece) {
6298                 DisplayMoveError(_("You are playing White"));
6299                 return FALSE;
6300             }
6301         }
6302         break;
6303
6304       case Training:
6305         if (!white_piece && WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is White's turn"));
6307             return FALSE;
6308         }
6309         if (white_piece && !WhiteOnMove(currentMove)) {
6310             DisplayMoveError(_("It is Black's turn"));
6311             return FALSE;
6312         }
6313         break;
6314
6315       default:
6316       case IcsExamining:
6317         break;
6318     }
6319     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6320         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6321         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6322         && gameMode != AnalyzeFile && gameMode != Training) {
6323         DisplayMoveError(_("Displayed position is not current"));
6324         return FALSE;
6325     }
6326     return TRUE;
6327 }
6328
6329 Boolean
6330 OnlyMove (int *x, int *y, Boolean captures) 
6331 {
6332     DisambiguateClosure cl;
6333     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6334     switch(gameMode) {
6335       case MachinePlaysBlack:
6336       case IcsPlayingWhite:
6337       case BeginningOfGame:
6338         if(!WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case MachinePlaysWhite:
6341       case IcsPlayingBlack:
6342         if(WhiteOnMove(currentMove)) return FALSE;
6343         break;
6344       case EditGame:
6345         break;
6346       default:
6347         return FALSE;
6348     }
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = *y;
6351     cl.ffIn = *x;
6352     cl.rtIn = -1;
6353     cl.ftIn = -1;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       return TRUE;
6365     }
6366     if(cl.kind != ImpossibleMove) return FALSE;
6367     cl.pieceIn = EmptySquare;
6368     cl.rfIn = -1;
6369     cl.ffIn = -1;
6370     cl.rtIn = *y;
6371     cl.ftIn = *x;
6372     cl.promoCharIn = NULLCHAR;
6373     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6374     if( cl.kind == NormalMove ||
6375         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6376         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6377         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378       fromX = cl.ff;
6379       fromY = cl.rf;
6380       *x = cl.ft;
6381       *y = cl.rt;
6382       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6383       return TRUE;
6384     }
6385     return FALSE;
6386 }
6387
6388 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6389 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6390 int lastLoadGameUseList = FALSE;
6391 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6392 ChessMove lastLoadGameStart = EndOfFile;
6393
6394 void
6395 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6396 {
6397     ChessMove moveType;
6398     ChessSquare pdown, pup;
6399
6400     /* Check if the user is playing in turn.  This is complicated because we
6401        let the user "pick up" a piece before it is his turn.  So the piece he
6402        tried to pick up may have been captured by the time he puts it down!
6403        Therefore we use the color the user is supposed to be playing in this
6404        test, not the color of the piece that is currently on the starting
6405        square---except in EditGame mode, where the user is playing both
6406        sides; fortunately there the capture race can't happen.  (It can
6407        now happen in IcsExamining mode, but that's just too bad.  The user
6408        will get a somewhat confusing message in that case.)
6409        */
6410
6411     switch (gameMode) {
6412       case AnalyzeFile:
6413       case TwoMachinesPlay:
6414       case EndOfGame:
6415       case IcsObserving:
6416       case IcsIdle:
6417         /* We switched into a game mode where moves are not accepted,
6418            perhaps while the mouse button was down. */
6419         return;
6420
6421       case MachinePlaysWhite:
6422         /* User is moving for Black */
6423         if (WhiteOnMove(currentMove)) {
6424             DisplayMoveError(_("It is White's turn"));
6425             return;
6426         }
6427         break;
6428
6429       case MachinePlaysBlack:
6430         /* User is moving for White */
6431         if (!WhiteOnMove(currentMove)) {
6432             DisplayMoveError(_("It is Black's turn"));
6433             return;
6434         }
6435         break;
6436
6437       case PlayFromGameFile:
6438             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6439       case EditGame:
6440       case IcsExamining:
6441       case BeginningOfGame:
6442       case AnalyzeMode:
6443       case Training:
6444         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6445         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6446             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6447             /* User is moving for Black */
6448             if (WhiteOnMove(currentMove)) {
6449                 DisplayMoveError(_("It is White's turn"));
6450                 return;
6451             }
6452         } else {
6453             /* User is moving for White */
6454             if (!WhiteOnMove(currentMove)) {
6455                 DisplayMoveError(_("It is Black's turn"));
6456                 return;
6457             }
6458         }
6459         break;
6460
6461       case IcsPlayingBlack:
6462         /* User is moving for Black */
6463         if (WhiteOnMove(currentMove)) {
6464             if (!appData.premove) {
6465                 DisplayMoveError(_("It is White's turn"));
6466             } else if (toX >= 0 && toY >= 0) {
6467                 premoveToX = toX;
6468                 premoveToY = toY;
6469                 premoveFromX = fromX;
6470                 premoveFromY = fromY;
6471                 premovePromoChar = promoChar;
6472                 gotPremove = 1;
6473                 if (appData.debugMode)
6474                     fprintf(debugFP, "Got premove: fromX %d,"
6475                             "fromY %d, toX %d, toY %d\n",
6476                             fromX, fromY, toX, toY);
6477             }
6478             return;
6479         }
6480         break;
6481
6482       case IcsPlayingWhite:
6483         /* User is moving for White */
6484         if (!WhiteOnMove(currentMove)) {
6485             if (!appData.premove) {
6486                 DisplayMoveError(_("It is Black's turn"));
6487             } else if (toX >= 0 && toY >= 0) {
6488                 premoveToX = toX;
6489                 premoveToY = toY;
6490                 premoveFromX = fromX;
6491                 premoveFromY = fromY;
6492                 premovePromoChar = promoChar;
6493                 gotPremove = 1;
6494                 if (appData.debugMode)
6495                     fprintf(debugFP, "Got premove: fromX %d,"
6496                             "fromY %d, toX %d, toY %d\n",
6497                             fromX, fromY, toX, toY);
6498             }
6499             return;
6500         }
6501         break;
6502
6503       default:
6504         break;
6505
6506       case EditPosition:
6507         /* EditPosition, empty square, or different color piece;
6508            click-click move is possible */
6509         if (toX == -2 || toY == -2) {
6510             boards[0][fromY][fromX] = EmptySquare;
6511             DrawPosition(FALSE, boards[currentMove]);
6512             return;
6513         } else if (toX >= 0 && toY >= 0) {
6514             boards[0][toY][toX] = boards[0][fromY][fromX];
6515             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6516                 if(boards[0][fromY][0] != EmptySquare) {
6517                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6518                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6519                 }
6520             } else
6521             if(fromX == BOARD_RGHT+1) {
6522                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6523                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6524                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6525                 }
6526             } else
6527             boards[0][fromY][fromX] = EmptySquare;
6528             DrawPosition(FALSE, boards[currentMove]);
6529             return;
6530         }
6531         return;
6532     }
6533
6534     if(toX < 0 || toY < 0) return;
6535     pdown = boards[currentMove][fromY][fromX];
6536     pup = boards[currentMove][toY][toX];
6537
6538     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6539     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6540          if( pup != EmptySquare ) return;
6541          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6542            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6543                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6544            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6545            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6546            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6547            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6548          fromY = DROP_RANK;
6549     }
6550
6551     /* [HGM] always test for legality, to get promotion info */
6552     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6553                                          fromY, fromX, toY, toX, promoChar);
6554
6555     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6556
6557     /* [HGM] but possibly ignore an IllegalMove result */
6558     if (appData.testLegality) {
6559         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6560             DisplayMoveError(_("Illegal move"));
6561             return;
6562         }
6563     }
6564
6565     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6566 }
6567
6568 /* Common tail of UserMoveEvent and DropMenuEvent */
6569 int
6570 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6571 {
6572     char *bookHit = 0;
6573
6574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6575         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6576         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6577         if(WhiteOnMove(currentMove)) {
6578             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6579         } else {
6580             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6581         }
6582     }
6583
6584     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6585        move type in caller when we know the move is a legal promotion */
6586     if(moveType == NormalMove && promoChar)
6587         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6588
6589     /* [HGM] <popupFix> The following if has been moved here from
6590        UserMoveEvent(). Because it seemed to belong here (why not allow
6591        piece drops in training games?), and because it can only be
6592        performed after it is known to what we promote. */
6593     if (gameMode == Training) {
6594       /* compare the move played on the board to the next move in the
6595        * game. If they match, display the move and the opponent's response.
6596        * If they don't match, display an error message.
6597        */
6598       int saveAnimate;
6599       Board testBoard;
6600       CopyBoard(testBoard, boards[currentMove]);
6601       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6602
6603       if (CompareBoards(testBoard, boards[currentMove+1])) {
6604         ForwardInner(currentMove+1);
6605
6606         /* Autoplay the opponent's response.
6607          * if appData.animate was TRUE when Training mode was entered,
6608          * the response will be animated.
6609          */
6610         saveAnimate = appData.animate;
6611         appData.animate = animateTraining;
6612         ForwardInner(currentMove+1);
6613         appData.animate = saveAnimate;
6614
6615         /* check for the end of the game */
6616         if (currentMove >= forwardMostMove) {
6617           gameMode = PlayFromGameFile;
6618           ModeHighlight();
6619           SetTrainingModeOff();
6620           DisplayInformation(_("End of game"));
6621         }
6622       } else {
6623         DisplayError(_("Incorrect move"), 0);
6624       }
6625       return 1;
6626     }
6627
6628   /* Ok, now we know that the move is good, so we can kill
6629      the previous line in Analysis Mode */
6630   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6631                                 && currentMove < forwardMostMove) {
6632     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6633     else forwardMostMove = currentMove;
6634   }
6635
6636   /* If we need the chess program but it's dead, restart it */
6637   ResurrectChessProgram();
6638
6639   /* A user move restarts a paused game*/
6640   if (pausing)
6641     PauseEvent();
6642
6643   thinkOutput[0] = NULLCHAR;
6644
6645   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6646
6647   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6648     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649     return 1;
6650   }
6651
6652   if (gameMode == BeginningOfGame) {
6653     if (appData.noChessProgram) {
6654       gameMode = EditGame;
6655       SetGameInfo();
6656     } else {
6657       char buf[MSG_SIZ];
6658       gameMode = MachinePlaysBlack;
6659       StartClocks();
6660       SetGameInfo();
6661       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6662       DisplayTitle(buf);
6663       if (first.sendName) {
6664         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6665         SendToProgram(buf, &first);
6666       }
6667       StartClocks();
6668     }
6669     ModeHighlight();
6670   }
6671
6672   /* Relay move to ICS or chess engine */
6673   if (appData.icsActive) {
6674     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6675         gameMode == IcsExamining) {
6676       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6677         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6678         SendToICS("draw ");
6679         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       }
6681       // also send plain move, in case ICS does not understand atomic claims
6682       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       ics_user_moved = 1;
6684     }
6685   } else {
6686     if (first.sendTime && (gameMode == BeginningOfGame ||
6687                            gameMode == MachinePlaysWhite ||
6688                            gameMode == MachinePlaysBlack)) {
6689       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6690     }
6691     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6692          // [HGM] book: if program might be playing, let it use book
6693         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6694         first.maybeThinking = TRUE;
6695     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6696         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6697         SendBoard(&first, currentMove+1);
6698     } else SendMoveToProgram(forwardMostMove-1, &first);
6699     if (currentMove == cmailOldMove + 1) {
6700       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6701     }
6702   }
6703
6704   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705
6706   switch (gameMode) {
6707   case EditGame:
6708     if(appData.testLegality)
6709     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6710     case MT_NONE:
6711     case MT_CHECK:
6712       break;
6713     case MT_CHECKMATE:
6714     case MT_STAINMATE:
6715       if (WhiteOnMove(currentMove)) {
6716         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6717       } else {
6718         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6719       }
6720       break;
6721     case MT_STALEMATE:
6722       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6723       break;
6724     }
6725     break;
6726
6727   case MachinePlaysBlack:
6728   case MachinePlaysWhite:
6729     /* disable certain menu options while machine is thinking */
6730     SetMachineThinkingEnables();
6731     break;
6732
6733   default:
6734     break;
6735   }
6736
6737   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6738   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6739
6740   if(bookHit) { // [HGM] book: simulate book reply
6741         static char bookMove[MSG_SIZ]; // a bit generous?
6742
6743         programStats.nodes = programStats.depth = programStats.time =
6744         programStats.score = programStats.got_only_move = 0;
6745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6746
6747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6748         strcat(bookMove, bookHit);
6749         HandleMachineMove(bookMove, &first);
6750   }
6751   return 1;
6752 }
6753
6754 void
6755 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6756 {
6757     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6758     Markers *m = (Markers *) closure;
6759     if(rf == fromY && ff == fromX)
6760         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6761                          || kind == WhiteCapturesEnPassant
6762                          || kind == BlackCapturesEnPassant);
6763     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6764 }
6765
6766 void
6767 MarkTargetSquares (int clear)
6768 {
6769   int x, y;
6770   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6771      !appData.testLegality || gameMode == EditPosition) return;
6772   if(clear) {
6773     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6774   } else {
6775     int capt = 0;
6776     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6777     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6778       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6779       if(capt)
6780       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6781     }
6782   }
6783   DrawPosition(TRUE, NULL);
6784 }
6785
6786 int
6787 Explode (Board board, int fromX, int fromY, int toX, int toY)
6788 {
6789     if(gameInfo.variant == VariantAtomic &&
6790        (board[toY][toX] != EmptySquare ||                     // capture?
6791         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6792                          board[fromY][fromX] == BlackPawn   )
6793       )) {
6794         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6795         return TRUE;
6796     }
6797     return FALSE;
6798 }
6799
6800 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6801
6802 int
6803 CanPromote (ChessSquare piece, int y)
6804 {
6805         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6806         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6807         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6808            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6809            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6810                                                   gameInfo.variant == VariantMakruk) return FALSE;
6811         return (piece == BlackPawn && y == 1 ||
6812                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6813                 piece == BlackLance && y == 1 ||
6814                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6815 }
6816
6817 void
6818 LeftClick (ClickType clickType, int xPix, int yPix)
6819 {
6820     int x, y;
6821     Boolean saveAnimate;
6822     static int second = 0, promotionChoice = 0, clearFlag = 0;
6823     char promoChoice = NULLCHAR;
6824     ChessSquare piece;
6825
6826     if(appData.seekGraph && appData.icsActive && loggedOn &&
6827         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6828         SeekGraphClick(clickType, xPix, yPix, 0);
6829         return;
6830     }
6831
6832     if (clickType == Press) ErrorPopDown();
6833
6834     x = EventToSquare(xPix, BOARD_WIDTH);
6835     y = EventToSquare(yPix, BOARD_HEIGHT);
6836     if (!flipView && y >= 0) {
6837         y = BOARD_HEIGHT - 1 - y;
6838     }
6839     if (flipView && x >= 0) {
6840         x = BOARD_WIDTH - 1 - x;
6841     }
6842
6843     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6844         defaultPromoChoice = promoSweep;
6845         promoSweep = EmptySquare;   // terminate sweep
6846         promoDefaultAltered = TRUE;
6847         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6848     }
6849
6850     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6851         if(clickType == Release) return; // ignore upclick of click-click destination
6852         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6853         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6854         if(gameInfo.holdingsWidth &&
6855                 (WhiteOnMove(currentMove)
6856                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6857                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6858             // click in right holdings, for determining promotion piece
6859             ChessSquare p = boards[currentMove][y][x];
6860             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6861             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6862             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6863                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6864                 fromX = fromY = -1;
6865                 return;
6866             }
6867         }
6868         DrawPosition(FALSE, boards[currentMove]);
6869         return;
6870     }
6871
6872     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6873     if(clickType == Press
6874             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6875               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6876               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6877         return;
6878
6879     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6880         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6881
6882     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6883         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6884                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6885         defaultPromoChoice = DefaultPromoChoice(side);
6886     }
6887
6888     autoQueen = appData.alwaysPromoteToQueen;
6889
6890     if (fromX == -1) {
6891       int originalY = y;
6892       gatingPiece = EmptySquare;
6893       if (clickType != Press) {
6894         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6895             DragPieceEnd(xPix, yPix); dragging = 0;
6896             DrawPosition(FALSE, NULL);
6897         }
6898         return;
6899       }
6900       fromX = x; fromY = y; toX = toY = -1;
6901       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6902          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6903          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6904             /* First square */
6905             if (OKToStartUserMove(fromX, fromY)) {
6906                 second = 0;
6907                 MarkTargetSquares(0);
6908                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6909                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6910                     promoSweep = defaultPromoChoice;
6911                     selectFlag = 0; lastX = xPix; lastY = yPix;
6912                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6913                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6914                 }
6915                 if (appData.highlightDragging) {
6916                     SetHighlights(fromX, fromY, -1, -1);
6917                 }
6918             } else fromX = fromY = -1;
6919             return;
6920         }
6921     }
6922
6923     /* fromX != -1 */
6924     if (clickType == Press && gameMode != EditPosition) {
6925         ChessSquare fromP;
6926         ChessSquare toP;
6927         int frc;
6928
6929         // ignore off-board to clicks
6930         if(y < 0 || x < 0) return;
6931
6932         /* Check if clicking again on the same color piece */
6933         fromP = boards[currentMove][fromY][fromX];
6934         toP = boards[currentMove][y][x];
6935         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6936         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6937              WhitePawn <= toP && toP <= WhiteKing &&
6938              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6939              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6940             (BlackPawn <= fromP && fromP <= BlackKing &&
6941              BlackPawn <= toP && toP <= BlackKing &&
6942              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6943              !(fromP == BlackKing && toP == BlackRook && frc))) {
6944             /* Clicked again on same color piece -- changed his mind */
6945             second = (x == fromX && y == fromY);
6946             promoDefaultAltered = FALSE;
6947             MarkTargetSquares(1);
6948            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6949             if (appData.highlightDragging) {
6950                 SetHighlights(x, y, -1, -1);
6951             } else {
6952                 ClearHighlights();
6953             }
6954             if (OKToStartUserMove(x, y)) {
6955                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6956                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6957                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6958                  gatingPiece = boards[currentMove][fromY][fromX];
6959                 else gatingPiece = EmptySquare;
6960                 fromX = x;
6961                 fromY = y; dragging = 1;
6962                 MarkTargetSquares(0);
6963                 DragPieceBegin(xPix, yPix, FALSE);
6964                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6965                     promoSweep = defaultPromoChoice;
6966                     selectFlag = 0; lastX = xPix; lastY = yPix;
6967                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6968                 }
6969             }
6970            }
6971            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6972            second = FALSE; 
6973         }
6974         // ignore clicks on holdings
6975         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6976     }
6977
6978     if (clickType == Release && x == fromX && y == fromY) {
6979         DragPieceEnd(xPix, yPix); dragging = 0;
6980         if(clearFlag) {
6981             // a deferred attempt to click-click move an empty square on top of a piece
6982             boards[currentMove][y][x] = EmptySquare;
6983             ClearHighlights();
6984             DrawPosition(FALSE, boards[currentMove]);
6985             fromX = fromY = -1; clearFlag = 0;
6986             return;
6987         }
6988         if (appData.animateDragging) {
6989             /* Undo animation damage if any */
6990             DrawPosition(FALSE, NULL);
6991         }
6992         if (second) {
6993             /* Second up/down in same square; just abort move */
6994             second = 0;
6995             fromX = fromY = -1;
6996             gatingPiece = EmptySquare;
6997             ClearHighlights();
6998             gotPremove = 0;
6999             ClearPremoveHighlights();
7000         } else {
7001             /* First upclick in same square; start click-click mode */
7002             SetHighlights(x, y, -1, -1);
7003         }
7004         return;
7005     }
7006
7007     clearFlag = 0;
7008
7009     /* we now have a different from- and (possibly off-board) to-square */
7010     /* Completed move */
7011     toX = x;
7012     toY = y;
7013     saveAnimate = appData.animate;
7014     MarkTargetSquares(1);
7015     if (clickType == Press) {
7016         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7017             // must be Edit Position mode with empty-square selected
7018             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7019             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7020             return;
7021         }
7022         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7023             ChessSquare piece = boards[currentMove][fromY][fromX];
7024             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7025             promoSweep = defaultPromoChoice;
7026             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7027             selectFlag = 0; lastX = xPix; lastY = yPix;
7028             Sweep(0); // Pawn that is going to promote: preview promotion piece
7029             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7030             DrawPosition(FALSE, boards[currentMove]);
7031             return;
7032         }
7033         /* Finish clickclick move */
7034         if (appData.animate || appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039     } else {
7040         /* Finish drag move */
7041         if (appData.highlightLastMove) {
7042             SetHighlights(fromX, fromY, toX, toY);
7043         } else {
7044             ClearHighlights();
7045         }
7046         DragPieceEnd(xPix, yPix); dragging = 0;
7047         /* Don't animate move and drag both */
7048         appData.animate = FALSE;
7049     }
7050
7051     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7052     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7053         ChessSquare piece = boards[currentMove][fromY][fromX];
7054         if(gameMode == EditPosition && piece != EmptySquare &&
7055            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7056             int n;
7057
7058             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7059                 n = PieceToNumber(piece - (int)BlackPawn);
7060                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7063             } else
7064             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7065                 n = PieceToNumber(piece);
7066                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7067                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7068                 boards[currentMove][n][BOARD_WIDTH-2]++;
7069             }
7070             boards[currentMove][fromY][fromX] = EmptySquare;
7071         }
7072         ClearHighlights();
7073         fromX = fromY = -1;
7074         DrawPosition(TRUE, boards[currentMove]);
7075         return;
7076     }
7077
7078     // off-board moves should not be highlighted
7079     if(x < 0 || y < 0) ClearHighlights();
7080
7081     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7082
7083     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7084         SetHighlights(fromX, fromY, toX, toY);
7085         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7086             // [HGM] super: promotion to captured piece selected from holdings
7087             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7088             promotionChoice = TRUE;
7089             // kludge follows to temporarily execute move on display, without promoting yet
7090             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7091             boards[currentMove][toY][toX] = p;
7092             DrawPosition(FALSE, boards[currentMove]);
7093             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7094             boards[currentMove][toY][toX] = q;
7095             DisplayMessage("Click in holdings to choose piece", "");
7096             return;
7097         }
7098         PromotionPopUp();
7099     } else {
7100         int oldMove = currentMove;
7101         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7102         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7103         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7104         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7105            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7106             DrawPosition(TRUE, boards[currentMove]);
7107         fromX = fromY = -1;
7108     }
7109     appData.animate = saveAnimate;
7110     if (appData.animate || appData.animateDragging) {
7111         /* Undo animation damage if needed */
7112         DrawPosition(FALSE, NULL);
7113     }
7114 }
7115
7116 int
7117 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7118 {   // front-end-free part taken out of PieceMenuPopup
7119     int whichMenu; int xSqr, ySqr;
7120
7121     if(seekGraphUp) { // [HGM] seekgraph
7122         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7123         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7124         return -2;
7125     }
7126
7127     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7128          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7129         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7130         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7131         if(action == Press)   {
7132             originalFlip = flipView;
7133             flipView = !flipView; // temporarily flip board to see game from partners perspective
7134             DrawPosition(TRUE, partnerBoard);
7135             DisplayMessage(partnerStatus, "");
7136             partnerUp = TRUE;
7137         } else if(action == Release) {
7138             flipView = originalFlip;
7139             DrawPosition(TRUE, boards[currentMove]);
7140             partnerUp = FALSE;
7141         }
7142         return -2;
7143     }
7144
7145     xSqr = EventToSquare(x, BOARD_WIDTH);
7146     ySqr = EventToSquare(y, BOARD_HEIGHT);
7147     if (action == Release) {
7148         if(pieceSweep != EmptySquare) {
7149             EditPositionMenuEvent(pieceSweep, toX, toY);
7150             pieceSweep = EmptySquare;
7151         } else UnLoadPV(); // [HGM] pv
7152     }
7153     if (action != Press) return -2; // return code to be ignored
7154     switch (gameMode) {
7155       case IcsExamining:
7156         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7157       case EditPosition:
7158         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7159         if (xSqr < 0 || ySqr < 0) return -1;
7160         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7161         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7162         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7163         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7164         NextPiece(0);
7165         return 2; // grab
7166       case IcsObserving:
7167         if(!appData.icsEngineAnalyze) return -1;
7168       case IcsPlayingWhite:
7169       case IcsPlayingBlack:
7170         if(!appData.zippyPlay) goto noZip;
7171       case AnalyzeMode:
7172       case AnalyzeFile:
7173       case MachinePlaysWhite:
7174       case MachinePlaysBlack:
7175       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7176         if (!appData.dropMenu) {
7177           LoadPV(x, y);
7178           return 2; // flag front-end to grab mouse events
7179         }
7180         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7181            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7182       case EditGame:
7183       noZip:
7184         if (xSqr < 0 || ySqr < 0) return -1;
7185         if (!appData.dropMenu || appData.testLegality &&
7186             gameInfo.variant != VariantBughouse &&
7187             gameInfo.variant != VariantCrazyhouse) return -1;
7188         whichMenu = 1; // drop menu
7189         break;
7190       default:
7191         return -1;
7192     }
7193
7194     if (((*fromX = xSqr) < 0) ||
7195         ((*fromY = ySqr) < 0)) {
7196         *fromX = *fromY = -1;
7197         return -1;
7198     }
7199     if (flipView)
7200       *fromX = BOARD_WIDTH - 1 - *fromX;
7201     else
7202       *fromY = BOARD_HEIGHT - 1 - *fromY;
7203
7204     return whichMenu;
7205 }
7206
7207 void
7208 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7209 {
7210 //    char * hint = lastHint;
7211     FrontEndProgramStats stats;
7212
7213     stats.which = cps == &first ? 0 : 1;
7214     stats.depth = cpstats->depth;
7215     stats.nodes = cpstats->nodes;
7216     stats.score = cpstats->score;
7217     stats.time = cpstats->time;
7218     stats.pv = cpstats->movelist;
7219     stats.hint = lastHint;
7220     stats.an_move_index = 0;
7221     stats.an_move_count = 0;
7222
7223     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7224         stats.hint = cpstats->move_name;
7225         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7226         stats.an_move_count = cpstats->nr_moves;
7227     }
7228
7229     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
7230
7231     SetProgramStats( &stats );
7232 }
7233
7234 void
7235 ClearEngineOutputPane (int which)
7236 {
7237     static FrontEndProgramStats dummyStats;
7238     dummyStats.which = which;
7239     dummyStats.pv = "#";
7240     SetProgramStats( &dummyStats );
7241 }
7242
7243 #define MAXPLAYERS 500
7244
7245 char *
7246 TourneyStandings (int display)
7247 {
7248     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7249     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7250     char result, *p, *names[MAXPLAYERS];
7251
7252     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7253         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7254     names[0] = p = strdup(appData.participants);
7255     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7256
7257     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7258
7259     while(result = appData.results[nr]) {
7260         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7261         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7262         wScore = bScore = 0;
7263         switch(result) {
7264           case '+': wScore = 2; break;
7265           case '-': bScore = 2; break;
7266           case '=': wScore = bScore = 1; break;
7267           case ' ':
7268           case '*': return strdup("busy"); // tourney not finished
7269         }
7270         score[w] += wScore;
7271         score[b] += bScore;
7272         games[w]++;
7273         games[b]++;
7274         nr++;
7275     }
7276     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7277     for(w=0; w<nPlayers; w++) {
7278         bScore = -1;
7279         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7280         ranking[w] = b; points[w] = bScore; score[b] = -2;
7281     }
7282     p = malloc(nPlayers*34+1);
7283     for(w=0; w<nPlayers && w<display; w++)
7284         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7285     free(names[0]);
7286     return p;
7287 }
7288
7289 void
7290 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7291 {       // count all piece types
7292         int p, f, r;
7293         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7294         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7296                 p = board[r][f];
7297                 pCnt[p]++;
7298                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7299                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7300                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7301                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7302                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7303                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7304         }
7305 }
7306
7307 int
7308 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7309 {
7310         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7311         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7312
7313         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7314         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7315         if(myPawns == 2 && nMine == 3) // KPP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7317         if(myPawns == 1 && nMine == 2) // KP
7318             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7319         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7320             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7321         if(myPawns) return FALSE;
7322         if(pCnt[WhiteRook+side])
7323             return pCnt[BlackRook-side] ||
7324                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7325                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7326                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7327         if(pCnt[WhiteCannon+side]) {
7328             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7329             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7330         }
7331         if(pCnt[WhiteKnight+side])
7332             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7333         return FALSE;
7334 }
7335
7336 int
7337 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7338 {
7339         VariantClass v = gameInfo.variant;
7340
7341         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7342         if(v == VariantShatranj) return TRUE; // always winnable through baring
7343         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7344         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7345
7346         if(v == VariantXiangqi) {
7347                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7348
7349                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7350                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7351                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7352                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7353                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7354                 if(stale) // we have at least one last-rank P plus perhaps C
7355                     return majors // KPKX
7356                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7357                 else // KCA*E*
7358                     return pCnt[WhiteFerz+side] // KCAK
7359                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7360                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7361                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7362
7363         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7364                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7365
7366                 if(nMine == 1) return FALSE; // bare King
7367                 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
7368                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7369                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7370                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7371                 if(pCnt[WhiteKnight+side])
7372                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7373                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7374                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7375                 if(nBishops)
7376                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7377                 if(pCnt[WhiteAlfil+side])
7378                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7379                 if(pCnt[WhiteWazir+side])
7380                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7381         }
7382
7383         return TRUE;
7384 }
7385
7386 int
7387 CompareWithRights (Board b1, Board b2)
7388 {
7389     int rights = 0;
7390     if(!CompareBoards(b1, b2)) return FALSE;
7391     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7392     /* compare castling rights */
7393     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7394            rights++; /* King lost rights, while rook still had them */
7395     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7396         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7397            rights++; /* but at least one rook lost them */
7398     }
7399     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7400            rights++;
7401     if( b1[CASTLING][5] != NoRights ) {
7402         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7403            rights++;
7404     }
7405     return rights == 0;
7406 }
7407
7408 int
7409 Adjudicate (ChessProgramState *cps)
7410 {       // [HGM] some adjudications useful with buggy engines
7411         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7412         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7413         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7414         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7415         int k, count = 0; static int bare = 1;
7416         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7417         Boolean canAdjudicate = !appData.icsActive;
7418
7419         // most tests only when we understand the game, i.e. legality-checking on
7420             if( appData.testLegality )
7421             {   /* [HGM] Some more adjudications for obstinate engines */
7422                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7423                 static int moveCount = 6;
7424                 ChessMove result;
7425                 char *reason = NULL;
7426
7427                 /* Count what is on board. */
7428                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7429
7430                 /* Some material-based adjudications that have to be made before stalemate test */
7431                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7432                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7433                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7434                      if(canAdjudicate && appData.checkMates) {
7435                          if(engineOpponent)
7436                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7437                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7438                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7439                          return 1;
7440                      }
7441                 }
7442
7443                 /* Bare King in Shatranj (loses) or Losers (wins) */
7444                 if( nrW == 1 || nrB == 1) {
7445                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7446                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7447                      if(canAdjudicate && appData.checkMates) {
7448                          if(engineOpponent)
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7450                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7451                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7452                          return 1;
7453                      }
7454                   } else
7455                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7456                   {    /* bare King */
7457                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7458                         if(canAdjudicate && appData.checkMates) {
7459                             /* but only adjudicate if adjudication enabled */
7460                             if(engineOpponent)
7461                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7462                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7463                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7464                             return 1;
7465                         }
7466                   }
7467                 } else bare = 1;
7468
7469
7470             // don't wait for engine to announce game end if we can judge ourselves
7471             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7472               case MT_CHECK:
7473                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7474                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7475                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7476                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7477                             checkCnt++;
7478                         if(checkCnt >= 2) {
7479                             reason = "Xboard adjudication: 3rd check";
7480                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7481                             break;
7482                         }
7483                     }
7484                 }
7485               case MT_NONE:
7486               default:
7487                 break;
7488               case MT_STALEMATE:
7489               case MT_STAINMATE:
7490                 reason = "Xboard adjudication: Stalemate";
7491                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7492                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7493                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7494                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7495                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7496                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7497                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7498                                                                         EP_CHECKMATE : EP_WINS);
7499                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7500                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7501                 }
7502                 break;
7503               case MT_CHECKMATE:
7504                 reason = "Xboard adjudication: Checkmate";
7505                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7506                 break;
7507             }
7508
7509                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7510                     case EP_STALEMATE:
7511                         result = GameIsDrawn; break;
7512                     case EP_CHECKMATE:
7513                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7514                     case EP_WINS:
7515                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7516                     default:
7517                         result = EndOfFile;
7518                 }
7519                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7520                     if(engineOpponent)
7521                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                     GameEnds( result, reason, GE_XBOARD );
7523                     return 1;
7524                 }
7525
7526                 /* Next absolutely insufficient mating material. */
7527                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7528                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7529                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7530
7531                      /* always flag draws, for judging claims */
7532                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7533
7534                      if(canAdjudicate && appData.materialDraws) {
7535                          /* but only adjudicate them if adjudication enabled */
7536                          if(engineOpponent) {
7537                            SendToProgram("force\n", engineOpponent); // suppress reply
7538                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7539                          }
7540                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7541                          return 1;
7542                      }
7543                 }
7544
7545                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7546                 if(gameInfo.variant == VariantXiangqi ?
7547                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7548                  : nrW + nrB == 4 &&
7549                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7550                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7551                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7552                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7553                    ) ) {
7554                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7555                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7556                           if(engineOpponent) {
7557                             SendToProgram("force\n", engineOpponent); // suppress reply
7558                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7559                           }
7560                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7561                           return 1;
7562                      }
7563                 } else moveCount = 6;
7564             }
7565
7566         // Repetition draws and 50-move rule can be applied independently of legality testing
7567
7568                 /* Check for rep-draws */
7569                 count = 0;
7570                 for(k = forwardMostMove-2;
7571                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7572                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7573                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7574                     k-=2)
7575                 {   int rights=0;
7576                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7577                         /* compare castling rights */
7578                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7579                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7580                                 rights++; /* King lost rights, while rook still had them */
7581                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7582                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7583                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7584                                    rights++; /* but at least one rook lost them */
7585                         }
7586                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7587                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7588                                 rights++;
7589                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7590                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7591                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7592                                    rights++;
7593                         }
7594                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7595                             && appData.drawRepeats > 1) {
7596                              /* adjudicate after user-specified nr of repeats */
7597                              int result = GameIsDrawn;
7598                              char *details = "XBoard adjudication: repetition draw";
7599                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7600                                 // [HGM] xiangqi: check for forbidden perpetuals
7601                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7602                                 for(m=forwardMostMove; m>k; m-=2) {
7603                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7604                                         ourPerpetual = 0; // the current mover did not always check
7605                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7606                                         hisPerpetual = 0; // the opponent did not always check
7607                                 }
7608                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7609                                                                         ourPerpetual, hisPerpetual);
7610                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7611                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7612                                     details = "Xboard adjudication: perpetual checking";
7613                                 } else
7614                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7615                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7616                                 } else
7617                                 // Now check for perpetual chases
7618                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7619                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7620                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7621                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7622                                         static char resdet[MSG_SIZ];
7623                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7624                                         details = resdet;
7625                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7626                                     } else
7627                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7628                                         break; // Abort repetition-checking loop.
7629                                 }
7630                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7631                              }
7632                              if(engineOpponent) {
7633                                SendToProgram("force\n", engineOpponent); // suppress reply
7634                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7635                              }
7636                              GameEnds( result, details, GE_XBOARD );
7637                              return 1;
7638                         }
7639                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7640                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7641                     }
7642                 }
7643
7644                 /* Now we test for 50-move draws. Determine ply count */
7645                 count = forwardMostMove;
7646                 /* look for last irreversble move */
7647                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7648                     count--;
7649                 /* if we hit starting position, add initial plies */
7650                 if( count == backwardMostMove )
7651                     count -= initialRulePlies;
7652                 count = forwardMostMove - count;
7653                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7654                         // adjust reversible move counter for checks in Xiangqi
7655                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7656                         if(i < backwardMostMove) i = backwardMostMove;
7657                         while(i <= forwardMostMove) {
7658                                 lastCheck = inCheck; // check evasion does not count
7659                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7660                                 if(inCheck || lastCheck) count--; // check does not count
7661                                 i++;
7662                         }
7663                 }
7664                 if( count >= 100)
7665                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7666                          /* this is used to judge if draw claims are legal */
7667                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7668                          if(engineOpponent) {
7669                            SendToProgram("force\n", engineOpponent); // suppress reply
7670                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7671                          }
7672                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7673                          return 1;
7674                 }
7675
7676                 /* if draw offer is pending, treat it as a draw claim
7677                  * when draw condition present, to allow engines a way to
7678                  * claim draws before making their move to avoid a race
7679                  * condition occurring after their move
7680                  */
7681                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7682                          char *p = NULL;
7683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7684                              p = "Draw claim: 50-move rule";
7685                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7686                              p = "Draw claim: 3-fold repetition";
7687                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7688                              p = "Draw claim: insufficient mating material";
7689                          if( p != NULL && canAdjudicate) {
7690                              if(engineOpponent) {
7691                                SendToProgram("force\n", engineOpponent); // suppress reply
7692                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7693                              }
7694                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7695                              return 1;
7696                          }
7697                 }
7698
7699                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7700                     if(engineOpponent) {
7701                       SendToProgram("force\n", engineOpponent); // suppress reply
7702                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7703                     }
7704                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7705                     return 1;
7706                 }
7707         return 0;
7708 }
7709
7710 char *
7711 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7712 {   // [HGM] book: this routine intercepts moves to simulate book replies
7713     char *bookHit = NULL;
7714
7715     //first determine if the incoming move brings opponent into his book
7716     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7717         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7718     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7719     if(bookHit != NULL && !cps->bookSuspend) {
7720         // make sure opponent is not going to reply after receiving move to book position
7721         SendToProgram("force\n", cps);
7722         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7723     }
7724     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7725     // now arrange restart after book miss
7726     if(bookHit) {
7727         // after a book hit we never send 'go', and the code after the call to this routine
7728         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7729         char buf[MSG_SIZ], *move = bookHit;
7730         if(cps->useSAN) {
7731             int fromX, fromY, toX, toY;
7732             char promoChar;
7733             ChessMove moveType;
7734             move = buf + 30;
7735             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7736                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7737                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7738                                     PosFlags(forwardMostMove),
7739                                     fromY, fromX, toY, toX, promoChar, move);
7740             } else {
7741                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7742                 bookHit = NULL;
7743             }
7744         }
7745         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7746         SendToProgram(buf, cps);
7747         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7748     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7749         SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7751     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7752         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7753             SendToProgram("go\n", cps);
7754         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7755     }
7756     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7757 }
7758
7759 char *savedMessage;
7760 ChessProgramState *savedState;
7761 void
7762 DeferredBookMove (void)
7763 {
7764         if(savedState->lastPing != savedState->lastPong)
7765                     ScheduleDelayedEvent(DeferredBookMove, 10);
7766         else
7767         HandleMachineMove(savedMessage, savedState);
7768 }
7769
7770 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7771
7772 void
7773 HandleMachineMove (char *message, ChessProgramState *cps)
7774 {
7775     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7776     char realname[MSG_SIZ];
7777     int fromX, fromY, toX, toY;
7778     ChessMove moveType;
7779     char promoChar;
7780     char *p, *pv=buf1;
7781     int machineWhite;
7782     char *bookHit;
7783
7784     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7785         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7786         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7787             DisplayError(_("Invalid pairing from pairing engine"), 0);
7788             return;
7789         }
7790         pairingReceived = 1;
7791         NextMatchGame();
7792         return; // Skim the pairing messages here.
7793     }
7794
7795     cps->userError = 0;
7796
7797 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7798     /*
7799      * Kludge to ignore BEL characters
7800      */
7801     while (*message == '\007') message++;
7802
7803     /*
7804      * [HGM] engine debug message: ignore lines starting with '#' character
7805      */
7806     if(cps->debug && *message == '#') return;
7807
7808     /*
7809      * Look for book output
7810      */
7811     if (cps == &first && bookRequested) {
7812         if (message[0] == '\t' || message[0] == ' ') {
7813             /* Part of the book output is here; append it */
7814             strcat(bookOutput, message);
7815             strcat(bookOutput, "  \n");
7816             return;
7817         } else if (bookOutput[0] != NULLCHAR) {
7818             /* All of book output has arrived; display it */
7819             char *p = bookOutput;
7820             while (*p != NULLCHAR) {
7821                 if (*p == '\t') *p = ' ';
7822                 p++;
7823             }
7824             DisplayInformation(bookOutput);
7825             bookRequested = FALSE;
7826             /* Fall through to parse the current output */
7827         }
7828     }
7829
7830     /*
7831      * Look for machine move.
7832      */
7833     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7834         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7835     {
7836         /* This method is only useful on engines that support ping */
7837         if (cps->lastPing != cps->lastPong) {
7838           if (gameMode == BeginningOfGame) {
7839             /* Extra move from before last new; ignore */
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7842             }
7843           } else {
7844             if (appData.debugMode) {
7845                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7846                         cps->which, gameMode);
7847             }
7848
7849             SendToProgram("undo\n", cps);
7850           }
7851           return;
7852         }
7853
7854         switch (gameMode) {
7855           case BeginningOfGame:
7856             /* Extra move from before last reset; ignore */
7857             if (appData.debugMode) {
7858                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7859             }
7860             return;
7861
7862           case EndOfGame:
7863           case IcsIdle:
7864           default:
7865             /* Extra move after we tried to stop.  The mode test is
7866                not a reliable way of detecting this problem, but it's
7867                the best we can do on engines that don't support ping.
7868             */
7869             if (appData.debugMode) {
7870                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7871                         cps->which, gameMode);
7872             }
7873             SendToProgram("undo\n", cps);
7874             return;
7875
7876           case MachinePlaysWhite:
7877           case IcsPlayingWhite:
7878             machineWhite = TRUE;
7879             break;
7880
7881           case MachinePlaysBlack:
7882           case IcsPlayingBlack:
7883             machineWhite = FALSE;
7884             break;
7885
7886           case TwoMachinesPlay:
7887             machineWhite = (cps->twoMachinesColor[0] == 'w');
7888             break;
7889         }
7890         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7891             if (appData.debugMode) {
7892                 fprintf(debugFP,
7893                         "Ignoring move out of turn by %s, gameMode %d"
7894                         ", forwardMost %d\n",
7895                         cps->which, gameMode, forwardMostMove);
7896             }
7897             return;
7898         }
7899
7900         if(cps->alphaRank) AlphaRank(machineMove, 4);
7901         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7902                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7903             /* Machine move could not be parsed; ignore it. */
7904           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7905                     machineMove, _(cps->which));
7906             DisplayError(buf1, 0);
7907             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7908                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7909             if (gameMode == TwoMachinesPlay) {
7910               GameEnds(machineWhite ? BlackWins : WhiteWins,
7911                        buf1, GE_XBOARD);
7912             }
7913             return;
7914         }
7915
7916         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7917         /* So we have to redo legality test with true e.p. status here,  */
7918         /* to make sure an illegal e.p. capture does not slip through,   */
7919         /* to cause a forfeit on a justified illegal-move complaint      */
7920         /* of the opponent.                                              */
7921         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7922            ChessMove moveType;
7923            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7924                              fromY, fromX, toY, toX, promoChar);
7925             if(moveType == IllegalMove) {
7926               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7927                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7928                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7929                            buf1, GE_XBOARD);
7930                 return;
7931            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7932            /* [HGM] Kludge to handle engines that send FRC-style castling
7933               when they shouldn't (like TSCP-Gothic) */
7934            switch(moveType) {
7935              case WhiteASideCastleFR:
7936              case BlackASideCastleFR:
7937                toX+=2;
7938                currentMoveString[2]++;
7939                break;
7940              case WhiteHSideCastleFR:
7941              case BlackHSideCastleFR:
7942                toX--;
7943                currentMoveString[2]--;
7944                break;
7945              default: ; // nothing to do, but suppresses warning of pedantic compilers
7946            }
7947         }
7948         hintRequested = FALSE;
7949         lastHint[0] = NULLCHAR;
7950         bookRequested = FALSE;
7951         /* Program may be pondering now */
7952         cps->maybeThinking = TRUE;
7953         if (cps->sendTime == 2) cps->sendTime = 1;
7954         if (cps->offeredDraw) cps->offeredDraw--;
7955
7956         /* [AS] Save move info*/
7957         pvInfoList[ forwardMostMove ].score = programStats.score;
7958         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7959         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7960
7961         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7962
7963         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7964         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7965             int count = 0;
7966
7967             while( count < adjudicateLossPlies ) {
7968                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7969
7970                 if( count & 1 ) {
7971                     score = -score; /* Flip score for winning side */
7972                 }
7973
7974                 if( score > adjudicateLossThreshold ) {
7975                     break;
7976                 }
7977
7978                 count++;
7979             }
7980
7981             if( count >= adjudicateLossPlies ) {
7982                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7983
7984                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7985                     "Xboard adjudication",
7986                     GE_XBOARD );
7987
7988                 return;
7989             }
7990         }
7991
7992         if(Adjudicate(cps)) {
7993             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7994             return; // [HGM] adjudicate: for all automatic game ends
7995         }
7996
7997 #if ZIPPY
7998         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7999             first.initDone) {
8000           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8001                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8002                 SendToICS("draw ");
8003                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8004           }
8005           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8006           ics_user_moved = 1;
8007           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8008                 char buf[3*MSG_SIZ];
8009
8010                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8011                         programStats.score / 100.,
8012                         programStats.depth,
8013                         programStats.time / 100.,
8014                         (unsigned int)programStats.nodes,
8015                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8016                         programStats.movelist);
8017                 SendToICS(buf);
8018 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8019           }
8020         }
8021 #endif
8022
8023         /* [AS] Clear stats for next move */
8024         ClearProgramStats();
8025         thinkOutput[0] = NULLCHAR;
8026         hiddenThinkOutputState = 0;
8027
8028         bookHit = NULL;
8029         if (gameMode == TwoMachinesPlay) {
8030             /* [HGM] relaying draw offers moved to after reception of move */
8031             /* and interpreting offer as claim if it brings draw condition */
8032             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8033                 SendToProgram("draw\n", cps->other);
8034             }
8035             if (cps->other->sendTime) {
8036                 SendTimeRemaining(cps->other,
8037                                   cps->other->twoMachinesColor[0] == 'w');
8038             }
8039             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8040             if (firstMove && !bookHit) {
8041                 firstMove = FALSE;
8042                 if (cps->other->useColors) {
8043                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8044                 }
8045                 SendToProgram("go\n", cps->other);
8046             }
8047             cps->other->maybeThinking = TRUE;
8048         }
8049
8050         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8051
8052         if (!pausing && appData.ringBellAfterMoves) {
8053             RingBell();
8054         }
8055
8056         /*
8057          * Reenable menu items that were disabled while
8058          * machine was thinking
8059          */
8060         if (gameMode != TwoMachinesPlay)
8061             SetUserThinkingEnables();
8062
8063         // [HGM] book: after book hit opponent has received move and is now in force mode
8064         // force the book reply into it, and then fake that it outputted this move by jumping
8065         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8066         if(bookHit) {
8067                 static char bookMove[MSG_SIZ]; // a bit generous?
8068
8069                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8070                 strcat(bookMove, bookHit);
8071                 message = bookMove;
8072                 cps = cps->other;
8073                 programStats.nodes = programStats.depth = programStats.time =
8074                 programStats.score = programStats.got_only_move = 0;
8075                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8076
8077                 if(cps->lastPing != cps->lastPong) {
8078                     savedMessage = message; // args for deferred call
8079                     savedState = cps;
8080                     ScheduleDelayedEvent(DeferredBookMove, 10);
8081                     return;
8082                 }
8083                 goto FakeBookMove;
8084         }
8085
8086         return;
8087     }
8088
8089     /* Set special modes for chess engines.  Later something general
8090      *  could be added here; for now there is just one kludge feature,
8091      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8092      *  when "xboard" is given as an interactive command.
8093      */
8094     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8095         cps->useSigint = FALSE;
8096         cps->useSigterm = FALSE;
8097     }
8098     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8099       ParseFeatures(message+8, cps);
8100       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8101     }
8102
8103     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8104                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8105       int dummy, s=6; char buf[MSG_SIZ];
8106       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8107       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8108       if(startedFromSetupPosition) return;
8109       ParseFEN(boards[0], &dummy, message+s);
8110       DrawPosition(TRUE, boards[0]);
8111       startedFromSetupPosition = TRUE;
8112       return;
8113     }
8114     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8115      * want this, I was asked to put it in, and obliged.
8116      */
8117     if (!strncmp(message, "setboard ", 9)) {
8118         Board initial_position;
8119
8120         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8121
8122         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8123             DisplayError(_("Bad FEN received from engine"), 0);
8124             return ;
8125         } else {
8126            Reset(TRUE, FALSE);
8127            CopyBoard(boards[0], initial_position);
8128            initialRulePlies = FENrulePlies;
8129            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8130            else gameMode = MachinePlaysBlack;
8131            DrawPosition(FALSE, boards[currentMove]);
8132         }
8133         return;
8134     }
8135
8136     /*
8137      * Look for communication commands
8138      */
8139     if (!strncmp(message, "telluser ", 9)) {
8140         if(message[9] == '\\' && message[10] == '\\')
8141             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8142         PlayTellSound();
8143         DisplayNote(message + 9);
8144         return;
8145     }
8146     if (!strncmp(message, "tellusererror ", 14)) {
8147         cps->userError = 1;
8148         if(message[14] == '\\' && message[15] == '\\')
8149             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8150         PlayTellSound();
8151         DisplayError(message + 14, 0);
8152         return;
8153     }
8154     if (!strncmp(message, "tellopponent ", 13)) {
8155       if (appData.icsActive) {
8156         if (loggedOn) {
8157           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8158           SendToICS(buf1);
8159         }
8160       } else {
8161         DisplayNote(message + 13);
8162       }
8163       return;
8164     }
8165     if (!strncmp(message, "tellothers ", 11)) {
8166       if (appData.icsActive) {
8167         if (loggedOn) {
8168           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8169           SendToICS(buf1);
8170         }
8171       }
8172       return;
8173     }
8174     if (!strncmp(message, "tellall ", 8)) {
8175       if (appData.icsActive) {
8176         if (loggedOn) {
8177           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8178           SendToICS(buf1);
8179         }
8180       } else {
8181         DisplayNote(message + 8);
8182       }
8183       return;
8184     }
8185     if (strncmp(message, "warning", 7) == 0) {
8186         /* Undocumented feature, use tellusererror in new code */
8187         DisplayError(message, 0);
8188         return;
8189     }
8190     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8191         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8192         strcat(realname, " query");
8193         AskQuestion(realname, buf2, buf1, cps->pr);
8194         return;
8195     }
8196     /* Commands from the engine directly to ICS.  We don't allow these to be
8197      *  sent until we are logged on. Crafty kibitzes have been known to
8198      *  interfere with the login process.
8199      */
8200     if (loggedOn) {
8201         if (!strncmp(message, "tellics ", 8)) {
8202             SendToICS(message + 8);
8203             SendToICS("\n");
8204             return;
8205         }
8206         if (!strncmp(message, "tellicsnoalias ", 15)) {
8207             SendToICS(ics_prefix);
8208             SendToICS(message + 15);
8209             SendToICS("\n");
8210             return;
8211         }
8212         /* The following are for backward compatibility only */
8213         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8214             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8215             SendToICS(ics_prefix);
8216             SendToICS(message);
8217             SendToICS("\n");
8218             return;
8219         }
8220     }
8221     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8222         return;
8223     }
8224     /*
8225      * If the move is illegal, cancel it and redraw the board.
8226      * Also deal with other error cases.  Matching is rather loose
8227      * here to accommodate engines written before the spec.
8228      */
8229     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8230         strncmp(message, "Error", 5) == 0) {
8231         if (StrStr(message, "name") ||
8232             StrStr(message, "rating") || StrStr(message, "?") ||
8233             StrStr(message, "result") || StrStr(message, "board") ||
8234             StrStr(message, "bk") || StrStr(message, "computer") ||
8235             StrStr(message, "variant") || StrStr(message, "hint") ||
8236             StrStr(message, "random") || StrStr(message, "depth") ||
8237             StrStr(message, "accepted")) {
8238             return;
8239         }
8240         if (StrStr(message, "protover")) {
8241           /* Program is responding to input, so it's apparently done
8242              initializing, and this error message indicates it is
8243              protocol version 1.  So we don't need to wait any longer
8244              for it to initialize and send feature commands. */
8245           FeatureDone(cps, 1);
8246           cps->protocolVersion = 1;
8247           return;
8248         }
8249         cps->maybeThinking = FALSE;
8250
8251         if (StrStr(message, "draw")) {
8252             /* Program doesn't have "draw" command */
8253             cps->sendDrawOffers = 0;
8254             return;
8255         }
8256         if (cps->sendTime != 1 &&
8257             (StrStr(message, "time") || StrStr(message, "otim"))) {
8258           /* Program apparently doesn't have "time" or "otim" command */
8259           cps->sendTime = 0;
8260           return;
8261         }
8262         if (StrStr(message, "analyze")) {
8263             cps->analysisSupport = FALSE;
8264             cps->analyzing = FALSE;
8265 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8266             EditGameEvent(); // [HGM] try to preserve loaded game
8267             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8268             DisplayError(buf2, 0);
8269             return;
8270         }
8271         if (StrStr(message, "(no matching move)st")) {
8272           /* Special kludge for GNU Chess 4 only */
8273           cps->stKludge = TRUE;
8274           SendTimeControl(cps, movesPerSession, timeControl,
8275                           timeIncrement, appData.searchDepth,
8276                           searchTime);
8277           return;
8278         }
8279         if (StrStr(message, "(no matching move)sd")) {
8280           /* Special kludge for GNU Chess 4 only */
8281           cps->sdKludge = TRUE;
8282           SendTimeControl(cps, movesPerSession, timeControl,
8283                           timeIncrement, appData.searchDepth,
8284                           searchTime);
8285           return;
8286         }
8287         if (!StrStr(message, "llegal")) {
8288             return;
8289         }
8290         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8291             gameMode == IcsIdle) return;
8292         if (forwardMostMove <= backwardMostMove) return;
8293         if (pausing) PauseEvent();
8294       if(appData.forceIllegal) {
8295             // [HGM] illegal: machine refused move; force position after move into it
8296           SendToProgram("force\n", cps);
8297           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8298                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8299                 // when black is to move, while there might be nothing on a2 or black
8300                 // might already have the move. So send the board as if white has the move.
8301                 // But first we must change the stm of the engine, as it refused the last move
8302                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8303                 if(WhiteOnMove(forwardMostMove)) {
8304                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8305                     SendBoard(cps, forwardMostMove); // kludgeless board
8306                 } else {
8307                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8308                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8309                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8310                 }
8311           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8312             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8313                  gameMode == TwoMachinesPlay)
8314               SendToProgram("go\n", cps);
8315             return;
8316       } else
8317         if (gameMode == PlayFromGameFile) {
8318             /* Stop reading this game file */
8319             gameMode = EditGame;
8320             ModeHighlight();
8321         }
8322         /* [HGM] illegal-move claim should forfeit game when Xboard */
8323         /* only passes fully legal moves                            */
8324         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8325             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8326                                 "False illegal-move claim", GE_XBOARD );
8327             return; // do not take back move we tested as valid
8328         }
8329         currentMove = forwardMostMove-1;
8330         DisplayMove(currentMove-1); /* before DisplayMoveError */
8331         SwitchClocks(forwardMostMove-1); // [HGM] race
8332         DisplayBothClocks();
8333         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8334                 parseList[currentMove], _(cps->which));
8335         DisplayMoveError(buf1);
8336         DrawPosition(FALSE, boards[currentMove]);
8337
8338         SetUserThinkingEnables();
8339         return;
8340     }
8341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8342         /* Program has a broken "time" command that
8343            outputs a string not ending in newline.
8344            Don't use it. */
8345         cps->sendTime = 0;
8346     }
8347
8348     /*
8349      * If chess program startup fails, exit with an error message.
8350      * Attempts to recover here are futile.
8351      */
8352     if ((StrStr(message, "unknown host") != NULL)
8353         || (StrStr(message, "No remote directory") != NULL)
8354         || (StrStr(message, "not found") != NULL)
8355         || (StrStr(message, "No such file") != NULL)
8356         || (StrStr(message, "can't alloc") != NULL)
8357         || (StrStr(message, "Permission denied") != NULL)) {
8358
8359         cps->maybeThinking = FALSE;
8360         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8361                 _(cps->which), cps->program, cps->host, message);
8362         RemoveInputSource(cps->isr);
8363         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8364             if(cps == &first) appData.noChessProgram = TRUE;
8365             DisplayError(buf1, 0);
8366         }
8367         return;
8368     }
8369
8370     /*
8371      * Look for hint output
8372      */
8373     if (sscanf(message, "Hint: %s", buf1) == 1) {
8374         if (cps == &first && hintRequested) {
8375             hintRequested = FALSE;
8376             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8377                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8378                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8379                                     PosFlags(forwardMostMove),
8380                                     fromY, fromX, toY, toX, promoChar, buf1);
8381                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8382                 DisplayInformation(buf2);
8383             } else {
8384                 /* Hint move could not be parsed!? */
8385               snprintf(buf2, sizeof(buf2),
8386                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8387                         buf1, _(cps->which));
8388                 DisplayError(buf2, 0);
8389             }
8390         } else {
8391           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8392         }
8393         return;
8394     }
8395
8396     /*
8397      * Ignore other messages if game is not in progress
8398      */
8399     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8400         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8401
8402     /*
8403      * look for win, lose, draw, or draw offer
8404      */
8405     if (strncmp(message, "1-0", 3) == 0) {
8406         char *p, *q, *r = "";
8407         p = strchr(message, '{');
8408         if (p) {
8409             q = strchr(p, '}');
8410             if (q) {
8411                 *q = NULLCHAR;
8412                 r = p + 1;
8413             }
8414         }
8415         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8416         return;
8417     } else if (strncmp(message, "0-1", 3) == 0) {
8418         char *p, *q, *r = "";
8419         p = strchr(message, '{');
8420         if (p) {
8421             q = strchr(p, '}');
8422             if (q) {
8423                 *q = NULLCHAR;
8424                 r = p + 1;
8425             }
8426         }
8427         /* Kludge for Arasan 4.1 bug */
8428         if (strcmp(r, "Black resigns") == 0) {
8429             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8430             return;
8431         }
8432         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8433         return;
8434     } else if (strncmp(message, "1/2", 3) == 0) {
8435         char *p, *q, *r = "";
8436         p = strchr(message, '{');
8437         if (p) {
8438             q = strchr(p, '}');
8439             if (q) {
8440                 *q = NULLCHAR;
8441                 r = p + 1;
8442             }
8443         }
8444
8445         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8446         return;
8447
8448     } else if (strncmp(message, "White resign", 12) == 0) {
8449         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8450         return;
8451     } else if (strncmp(message, "Black resign", 12) == 0) {
8452         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "White matches", 13) == 0 ||
8455                strncmp(message, "Black matches", 13) == 0   ) {
8456         /* [HGM] ignore GNUShogi noises */
8457         return;
8458     } else if (strncmp(message, "White", 5) == 0 &&
8459                message[5] != '(' &&
8460                StrStr(message, "Black") == NULL) {
8461         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8462         return;
8463     } else if (strncmp(message, "Black", 5) == 0 &&
8464                message[5] != '(') {
8465         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strcmp(message, "resign") == 0 ||
8468                strcmp(message, "computer resigns") == 0) {
8469         switch (gameMode) {
8470           case MachinePlaysBlack:
8471           case IcsPlayingBlack:
8472             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8473             break;
8474           case MachinePlaysWhite:
8475           case IcsPlayingWhite:
8476             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8477             break;
8478           case TwoMachinesPlay:
8479             if (cps->twoMachinesColor[0] == 'w')
8480               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8481             else
8482               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8483             break;
8484           default:
8485             /* can't happen */
8486             break;
8487         }
8488         return;
8489     } else if (strncmp(message, "opponent mates", 14) == 0) {
8490         switch (gameMode) {
8491           case MachinePlaysBlack:
8492           case IcsPlayingBlack:
8493             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8494             break;
8495           case MachinePlaysWhite:
8496           case IcsPlayingWhite:
8497             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8498             break;
8499           case TwoMachinesPlay:
8500             if (cps->twoMachinesColor[0] == 'w')
8501               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8502             else
8503               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8504             break;
8505           default:
8506             /* can't happen */
8507             break;
8508         }
8509         return;
8510     } else if (strncmp(message, "computer mates", 14) == 0) {
8511         switch (gameMode) {
8512           case MachinePlaysBlack:
8513           case IcsPlayingBlack:
8514             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8515             break;
8516           case MachinePlaysWhite:
8517           case IcsPlayingWhite:
8518             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8519             break;
8520           case TwoMachinesPlay:
8521             if (cps->twoMachinesColor[0] == 'w')
8522               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8523             else
8524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525             break;
8526           default:
8527             /* can't happen */
8528             break;
8529         }
8530         return;
8531     } else if (strncmp(message, "checkmate", 9) == 0) {
8532         if (WhiteOnMove(forwardMostMove)) {
8533             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8534         } else {
8535             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8536         }
8537         return;
8538     } else if (strstr(message, "Draw") != NULL ||
8539                strstr(message, "game is a draw") != NULL) {
8540         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8541         return;
8542     } else if (strstr(message, "offer") != NULL &&
8543                strstr(message, "draw") != NULL) {
8544 #if ZIPPY
8545         if (appData.zippyPlay && first.initDone) {
8546             /* Relay offer to ICS */
8547             SendToICS(ics_prefix);
8548             SendToICS("draw\n");
8549         }
8550 #endif
8551         cps->offeredDraw = 2; /* valid until this engine moves twice */
8552         if (gameMode == TwoMachinesPlay) {
8553             if (cps->other->offeredDraw) {
8554                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8555             /* [HGM] in two-machine mode we delay relaying draw offer      */
8556             /* until after we also have move, to see if it is really claim */
8557             }
8558         } else if (gameMode == MachinePlaysWhite ||
8559                    gameMode == MachinePlaysBlack) {
8560           if (userOfferedDraw) {
8561             DisplayInformation(_("Machine accepts your draw offer"));
8562             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8563           } else {
8564             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8565           }
8566         }
8567     }
8568
8569
8570     /*
8571      * Look for thinking output
8572      */
8573     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8574           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8575                                 ) {
8576         int plylev, mvleft, mvtot, curscore, time;
8577         char mvname[MOVE_LEN];
8578         u64 nodes; // [DM]
8579         char plyext;
8580         int ignore = FALSE;
8581         int prefixHint = FALSE;
8582         mvname[0] = NULLCHAR;
8583
8584         switch (gameMode) {
8585           case MachinePlaysBlack:
8586           case IcsPlayingBlack:
8587             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588             break;
8589           case MachinePlaysWhite:
8590           case IcsPlayingWhite:
8591             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8592             break;
8593           case AnalyzeMode:
8594           case AnalyzeFile:
8595             break;
8596           case IcsObserving: /* [DM] icsEngineAnalyze */
8597             if (!appData.icsEngineAnalyze) ignore = TRUE;
8598             break;
8599           case TwoMachinesPlay:
8600             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8601                 ignore = TRUE;
8602             }
8603             break;
8604           default:
8605             ignore = TRUE;
8606             break;
8607         }
8608
8609         if (!ignore) {
8610             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8611             buf1[0] = NULLCHAR;
8612             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8613                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8614
8615                 if (plyext != ' ' && plyext != '\t') {
8616                     time *= 100;
8617                 }
8618
8619                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8620                 if( cps->scoreIsAbsolute &&
8621                     ( gameMode == MachinePlaysBlack ||
8622                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8623                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8624                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8625                      !WhiteOnMove(currentMove)
8626                     ) )
8627                 {
8628                     curscore = -curscore;
8629                 }
8630
8631                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8632
8633                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8634                         char buf[MSG_SIZ];
8635                         FILE *f;
8636                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8637                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8638                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8639                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8640                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8641                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8642                                 fclose(f);
8643                         } else DisplayError(_("failed writing PV"), 0);
8644                 }
8645
8646                 tempStats.depth = plylev;
8647                 tempStats.nodes = nodes;
8648                 tempStats.time = time;
8649                 tempStats.score = curscore;
8650                 tempStats.got_only_move = 0;
8651
8652                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8653                         int ticklen;
8654
8655                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8656                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8657                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8658                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8659                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8660                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8661                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8662                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8663                 }
8664
8665                 /* Buffer overflow protection */
8666                 if (pv[0] != NULLCHAR) {
8667                     if (strlen(pv) >= sizeof(tempStats.movelist)
8668                         && appData.debugMode) {
8669                         fprintf(debugFP,
8670                                 "PV is too long; using the first %u bytes.\n",
8671                                 (unsigned) sizeof(tempStats.movelist) - 1);
8672                     }
8673
8674                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8675                 } else {
8676                     sprintf(tempStats.movelist, " no PV\n");
8677                 }
8678
8679                 if (tempStats.seen_stat) {
8680                     tempStats.ok_to_send = 1;
8681                 }
8682
8683                 if (strchr(tempStats.movelist, '(') != NULL) {
8684                     tempStats.line_is_book = 1;
8685                     tempStats.nr_moves = 0;
8686                     tempStats.moves_left = 0;
8687                 } else {
8688                     tempStats.line_is_book = 0;
8689                 }
8690
8691                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8692                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8693
8694                 SendProgramStatsToFrontend( cps, &tempStats );
8695
8696                 /*
8697                     [AS] Protect the thinkOutput buffer from overflow... this
8698                     is only useful if buf1 hasn't overflowed first!
8699                 */
8700                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8701                          plylev,
8702                          (gameMode == TwoMachinesPlay ?
8703                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8704                          ((double) curscore) / 100.0,
8705                          prefixHint ? lastHint : "",
8706                          prefixHint ? " " : "" );
8707
8708                 if( buf1[0] != NULLCHAR ) {
8709                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8710
8711                     if( strlen(pv) > max_len ) {
8712                         if( appData.debugMode) {
8713                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8714                         }
8715                         pv[max_len+1] = '\0';
8716                     }
8717
8718                     strcat( thinkOutput, pv);
8719                 }
8720
8721                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8722                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8723                     DisplayMove(currentMove - 1);
8724                 }
8725                 return;
8726
8727             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8728                 /* crafty (9.25+) says "(only move) <move>"
8729                  * if there is only 1 legal move
8730                  */
8731                 sscanf(p, "(only move) %s", buf1);
8732                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8733                 sprintf(programStats.movelist, "%s (only move)", buf1);
8734                 programStats.depth = 1;
8735                 programStats.nr_moves = 1;
8736                 programStats.moves_left = 1;
8737                 programStats.nodes = 1;
8738                 programStats.time = 1;
8739                 programStats.got_only_move = 1;
8740
8741                 /* Not really, but we also use this member to
8742                    mean "line isn't going to change" (Crafty
8743                    isn't searching, so stats won't change) */
8744                 programStats.line_is_book = 1;
8745
8746                 SendProgramStatsToFrontend( cps, &programStats );
8747
8748                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8749                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8750                     DisplayMove(currentMove - 1);
8751                 }
8752                 return;
8753             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8754                               &time, &nodes, &plylev, &mvleft,
8755                               &mvtot, mvname) >= 5) {
8756                 /* The stat01: line is from Crafty (9.29+) in response
8757                    to the "." command */
8758                 programStats.seen_stat = 1;
8759                 cps->maybeThinking = TRUE;
8760
8761                 if (programStats.got_only_move || !appData.periodicUpdates)
8762                   return;
8763
8764                 programStats.depth = plylev;
8765                 programStats.time = time;
8766                 programStats.nodes = nodes;
8767                 programStats.moves_left = mvleft;
8768                 programStats.nr_moves = mvtot;
8769                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8770                 programStats.ok_to_send = 1;
8771                 programStats.movelist[0] = '\0';
8772
8773                 SendProgramStatsToFrontend( cps, &programStats );
8774
8775                 return;
8776
8777             } else if (strncmp(message,"++",2) == 0) {
8778                 /* Crafty 9.29+ outputs this */
8779                 programStats.got_fail = 2;
8780                 return;
8781
8782             } else if (strncmp(message,"--",2) == 0) {
8783                 /* Crafty 9.29+ outputs this */
8784                 programStats.got_fail = 1;
8785                 return;
8786
8787             } else if (thinkOutput[0] != NULLCHAR &&
8788                        strncmp(message, "    ", 4) == 0) {
8789                 unsigned message_len;
8790
8791                 p = message;
8792                 while (*p && *p == ' ') p++;
8793
8794                 message_len = strlen( p );
8795
8796                 /* [AS] Avoid buffer overflow */
8797                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8798                     strcat(thinkOutput, " ");
8799                     strcat(thinkOutput, p);
8800                 }
8801
8802                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8803                     strcat(programStats.movelist, " ");
8804                     strcat(programStats.movelist, p);
8805                 }
8806
8807                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8808                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8809                     DisplayMove(currentMove - 1);
8810                 }
8811                 return;
8812             }
8813         }
8814         else {
8815             buf1[0] = NULLCHAR;
8816
8817             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8818                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8819             {
8820                 ChessProgramStats cpstats;
8821
8822                 if (plyext != ' ' && plyext != '\t') {
8823                     time *= 100;
8824                 }
8825
8826                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8827                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8828                     curscore = -curscore;
8829                 }
8830
8831                 cpstats.depth = plylev;
8832                 cpstats.nodes = nodes;
8833                 cpstats.time = time;
8834                 cpstats.score = curscore;
8835                 cpstats.got_only_move = 0;
8836                 cpstats.movelist[0] = '\0';
8837
8838                 if (buf1[0] != NULLCHAR) {
8839                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8840                 }
8841
8842                 cpstats.ok_to_send = 0;
8843                 cpstats.line_is_book = 0;
8844                 cpstats.nr_moves = 0;
8845                 cpstats.moves_left = 0;
8846
8847                 SendProgramStatsToFrontend( cps, &cpstats );
8848             }
8849         }
8850     }
8851 }
8852
8853
8854 /* Parse a game score from the character string "game", and
8855    record it as the history of the current game.  The game
8856    score is NOT assumed to start from the standard position.
8857    The display is not updated in any way.
8858    */
8859 void
8860 ParseGameHistory (char *game)
8861 {
8862     ChessMove moveType;
8863     int fromX, fromY, toX, toY, boardIndex;
8864     char promoChar;
8865     char *p, *q;
8866     char buf[MSG_SIZ];
8867
8868     if (appData.debugMode)
8869       fprintf(debugFP, "Parsing game history: %s\n", game);
8870
8871     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8872     gameInfo.site = StrSave(appData.icsHost);
8873     gameInfo.date = PGNDate();
8874     gameInfo.round = StrSave("-");
8875
8876     /* Parse out names of players */
8877     while (*game == ' ') game++;
8878     p = buf;
8879     while (*game != ' ') *p++ = *game++;
8880     *p = NULLCHAR;
8881     gameInfo.white = StrSave(buf);
8882     while (*game == ' ') game++;
8883     p = buf;
8884     while (*game != ' ' && *game != '\n') *p++ = *game++;
8885     *p = NULLCHAR;
8886     gameInfo.black = StrSave(buf);
8887
8888     /* Parse moves */
8889     boardIndex = blackPlaysFirst ? 1 : 0;
8890     yynewstr(game);
8891     for (;;) {
8892         yyboardindex = boardIndex;
8893         moveType = (ChessMove) Myylex();
8894         switch (moveType) {
8895           case IllegalMove:             /* maybe suicide chess, etc. */
8896   if (appData.debugMode) {
8897     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8898     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8899     setbuf(debugFP, NULL);
8900   }
8901           case WhitePromotion:
8902           case BlackPromotion:
8903           case WhiteNonPromotion:
8904           case BlackNonPromotion:
8905           case NormalMove:
8906           case WhiteCapturesEnPassant:
8907           case BlackCapturesEnPassant:
8908           case WhiteKingSideCastle:
8909           case WhiteQueenSideCastle:
8910           case BlackKingSideCastle:
8911           case BlackQueenSideCastle:
8912           case WhiteKingSideCastleWild:
8913           case WhiteQueenSideCastleWild:
8914           case BlackKingSideCastleWild:
8915           case BlackQueenSideCastleWild:
8916           /* PUSH Fabien */
8917           case WhiteHSideCastleFR:
8918           case WhiteASideCastleFR:
8919           case BlackHSideCastleFR:
8920           case BlackASideCastleFR:
8921           /* POP Fabien */
8922             fromX = currentMoveString[0] - AAA;
8923             fromY = currentMoveString[1] - ONE;
8924             toX = currentMoveString[2] - AAA;
8925             toY = currentMoveString[3] - ONE;
8926             promoChar = currentMoveString[4];
8927             break;
8928           case WhiteDrop:
8929           case BlackDrop:
8930             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8931             fromX = moveType == WhiteDrop ?
8932               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8933             (int) CharToPiece(ToLower(currentMoveString[0]));
8934             fromY = DROP_RANK;
8935             toX = currentMoveString[2] - AAA;
8936             toY = currentMoveString[3] - ONE;
8937             promoChar = NULLCHAR;
8938             break;
8939           case AmbiguousMove:
8940             /* bug? */
8941             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8942   if (appData.debugMode) {
8943     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8945     setbuf(debugFP, NULL);
8946   }
8947             DisplayError(buf, 0);
8948             return;
8949           case ImpossibleMove:
8950             /* bug? */
8951             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8952   if (appData.debugMode) {
8953     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8954     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8955     setbuf(debugFP, NULL);
8956   }
8957             DisplayError(buf, 0);
8958             return;
8959           case EndOfFile:
8960             if (boardIndex < backwardMostMove) {
8961                 /* Oops, gap.  How did that happen? */
8962                 DisplayError(_("Gap in move list"), 0);
8963                 return;
8964             }
8965             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8966             if (boardIndex > forwardMostMove) {
8967                 forwardMostMove = boardIndex;
8968             }
8969             return;
8970           case ElapsedTime:
8971             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8972                 strcat(parseList[boardIndex-1], " ");
8973                 strcat(parseList[boardIndex-1], yy_text);
8974             }
8975             continue;
8976           case Comment:
8977           case PGNTag:
8978           case NAG:
8979           default:
8980             /* ignore */
8981             continue;
8982           case WhiteWins:
8983           case BlackWins:
8984           case GameIsDrawn:
8985           case GameUnfinished:
8986             if (gameMode == IcsExamining) {
8987                 if (boardIndex < backwardMostMove) {
8988                     /* Oops, gap.  How did that happen? */
8989                     return;
8990                 }
8991                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8992                 return;
8993             }
8994             gameInfo.result = moveType;
8995             p = strchr(yy_text, '{');
8996             if (p == NULL) p = strchr(yy_text, '(');
8997             if (p == NULL) {
8998                 p = yy_text;
8999                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9000             } else {
9001                 q = strchr(p, *p == '{' ? '}' : ')');
9002                 if (q != NULL) *q = NULLCHAR;
9003                 p++;
9004             }
9005             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9006             gameInfo.resultDetails = StrSave(p);
9007             continue;
9008         }
9009         if (boardIndex >= forwardMostMove &&
9010             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9011             backwardMostMove = blackPlaysFirst ? 1 : 0;
9012             return;
9013         }
9014         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9015                                  fromY, fromX, toY, toX, promoChar,
9016                                  parseList[boardIndex]);
9017         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9018         /* currentMoveString is set as a side-effect of yylex */
9019         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9020         strcat(moveList[boardIndex], "\n");
9021         boardIndex++;
9022         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9023         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9024           case MT_NONE:
9025           case MT_STALEMATE:
9026           default:
9027             break;
9028           case MT_CHECK:
9029             if(gameInfo.variant != VariantShogi)
9030                 strcat(parseList[boardIndex - 1], "+");
9031             break;
9032           case MT_CHECKMATE:
9033           case MT_STAINMATE:
9034             strcat(parseList[boardIndex - 1], "#");
9035             break;
9036         }
9037     }
9038 }
9039
9040
9041 /* Apply a move to the given board  */
9042 void
9043 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9044 {
9045   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9046   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9047
9048     /* [HGM] compute & store e.p. status and castling rights for new position */
9049     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9050
9051       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9052       oldEP = (signed char)board[EP_STATUS];
9053       board[EP_STATUS] = EP_NONE;
9054
9055   if (fromY == DROP_RANK) {
9056         /* must be first */
9057         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9058             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9059             return;
9060         }
9061         piece = board[toY][toX] = (ChessSquare) fromX;
9062   } else {
9063       int i;
9064
9065       if( board[toY][toX] != EmptySquare )
9066            board[EP_STATUS] = EP_CAPTURE;
9067
9068       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9069            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9070                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9071       } else
9072       if( board[fromY][fromX] == WhitePawn ) {
9073            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9074                board[EP_STATUS] = EP_PAWN_MOVE;
9075            if( toY-fromY==2) {
9076                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9077                         gameInfo.variant != VariantBerolina || toX < fromX)
9078                       board[EP_STATUS] = toX | berolina;
9079                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9080                         gameInfo.variant != VariantBerolina || toX > fromX)
9081                       board[EP_STATUS] = toX;
9082            }
9083       } else
9084       if( board[fromY][fromX] == BlackPawn ) {
9085            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9086                board[EP_STATUS] = EP_PAWN_MOVE;
9087            if( toY-fromY== -2) {
9088                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9089                         gameInfo.variant != VariantBerolina || toX < fromX)
9090                       board[EP_STATUS] = toX | berolina;
9091                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9092                         gameInfo.variant != VariantBerolina || toX > fromX)
9093                       board[EP_STATUS] = toX;
9094            }
9095        }
9096
9097        for(i=0; i<nrCastlingRights; i++) {
9098            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9099               board[CASTLING][i] == toX   && castlingRank[i] == toY
9100              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9101        }
9102
9103      if (fromX == toX && fromY == toY) return;
9104
9105      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9106      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9107      if(gameInfo.variant == VariantKnightmate)
9108          king += (int) WhiteUnicorn - (int) WhiteKing;
9109
9110     /* Code added by Tord: */
9111     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9112     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9113         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9114       board[fromY][fromX] = EmptySquare;
9115       board[toY][toX] = EmptySquare;
9116       if((toX > fromX) != (piece == WhiteRook)) {
9117         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9118       } else {
9119         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9120       }
9121     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9122                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9123       board[fromY][fromX] = EmptySquare;
9124       board[toY][toX] = EmptySquare;
9125       if((toX > fromX) != (piece == BlackRook)) {
9126         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9127       } else {
9128         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9129       }
9130     /* End of code added by Tord */
9131
9132     } else if (board[fromY][fromX] == king
9133         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9134         && toY == fromY && toX > fromX+1) {
9135         board[fromY][fromX] = EmptySquare;
9136         board[toY][toX] = king;
9137         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9138         board[fromY][BOARD_RGHT-1] = EmptySquare;
9139     } else if (board[fromY][fromX] == king
9140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9141                && toY == fromY && toX < fromX-1) {
9142         board[fromY][fromX] = EmptySquare;
9143         board[toY][toX] = king;
9144         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9145         board[fromY][BOARD_LEFT] = EmptySquare;
9146     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9147                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9148                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9149                ) {
9150         /* white pawn promotion */
9151         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9152         if(gameInfo.variant==VariantBughouse ||
9153            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9155         board[fromY][fromX] = EmptySquare;
9156     } else if ((fromY >= BOARD_HEIGHT>>1)
9157                && (toX != fromX)
9158                && gameInfo.variant != VariantXiangqi
9159                && gameInfo.variant != VariantBerolina
9160                && (board[fromY][fromX] == WhitePawn)
9161                && (board[toY][toX] == EmptySquare)) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = WhitePawn;
9164         captured = board[toY - 1][toX];
9165         board[toY - 1][toX] = EmptySquare;
9166     } else if ((fromY == BOARD_HEIGHT-4)
9167                && (toX == fromX)
9168                && gameInfo.variant == VariantBerolina
9169                && (board[fromY][fromX] == WhitePawn)
9170                && (board[toY][toX] == EmptySquare)) {
9171         board[fromY][fromX] = EmptySquare;
9172         board[toY][toX] = WhitePawn;
9173         if(oldEP & EP_BEROLIN_A) {
9174                 captured = board[fromY][fromX-1];
9175                 board[fromY][fromX-1] = EmptySquare;
9176         }else{  captured = board[fromY][fromX+1];
9177                 board[fromY][fromX+1] = EmptySquare;
9178         }
9179     } else if (board[fromY][fromX] == king
9180         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9181                && toY == fromY && toX > fromX+1) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = king;
9184         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9185         board[fromY][BOARD_RGHT-1] = EmptySquare;
9186     } else if (board[fromY][fromX] == king
9187         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9188                && toY == fromY && toX < fromX-1) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = king;
9191         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9192         board[fromY][BOARD_LEFT] = EmptySquare;
9193     } else if (fromY == 7 && fromX == 3
9194                && board[fromY][fromX] == BlackKing
9195                && toY == 7 && toX == 5) {
9196         board[fromY][fromX] = EmptySquare;
9197         board[toY][toX] = BlackKing;
9198         board[fromY][7] = EmptySquare;
9199         board[toY][4] = BlackRook;
9200     } else if (fromY == 7 && fromX == 3
9201                && board[fromY][fromX] == BlackKing
9202                && toY == 7 && toX == 1) {
9203         board[fromY][fromX] = EmptySquare;
9204         board[toY][toX] = BlackKing;
9205         board[fromY][0] = EmptySquare;
9206         board[toY][2] = BlackRook;
9207     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9208                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9209                && toY < promoRank && promoChar
9210                ) {
9211         /* black pawn promotion */
9212         board[toY][toX] = CharToPiece(ToLower(promoChar));
9213         if(gameInfo.variant==VariantBughouse ||
9214            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9215             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9216         board[fromY][fromX] = EmptySquare;
9217     } else if ((fromY < BOARD_HEIGHT>>1)
9218                && (toX != fromX)
9219                && gameInfo.variant != VariantXiangqi
9220                && gameInfo.variant != VariantBerolina
9221                && (board[fromY][fromX] == BlackPawn)
9222                && (board[toY][toX] == EmptySquare)) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackPawn;
9225         captured = board[toY + 1][toX];
9226         board[toY + 1][toX] = EmptySquare;
9227     } else if ((fromY == 3)
9228                && (toX == fromX)
9229                && gameInfo.variant == VariantBerolina
9230                && (board[fromY][fromX] == BlackPawn)
9231                && (board[toY][toX] == EmptySquare)) {
9232         board[fromY][fromX] = EmptySquare;
9233         board[toY][toX] = BlackPawn;
9234         if(oldEP & EP_BEROLIN_A) {
9235                 captured = board[fromY][fromX-1];
9236                 board[fromY][fromX-1] = EmptySquare;
9237         }else{  captured = board[fromY][fromX+1];
9238                 board[fromY][fromX+1] = EmptySquare;
9239         }
9240     } else {
9241         board[toY][toX] = board[fromY][fromX];
9242         board[fromY][fromX] = EmptySquare;
9243     }
9244   }
9245
9246     if (gameInfo.holdingsWidth != 0) {
9247
9248       /* !!A lot more code needs to be written to support holdings  */
9249       /* [HGM] OK, so I have written it. Holdings are stored in the */
9250       /* penultimate board files, so they are automaticlly stored   */
9251       /* in the game history.                                       */
9252       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9253                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9254         /* Delete from holdings, by decreasing count */
9255         /* and erasing image if necessary            */
9256         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9257         if(p < (int) BlackPawn) { /* white drop */
9258              p -= (int)WhitePawn;
9259                  p = PieceToNumber((ChessSquare)p);
9260              if(p >= gameInfo.holdingsSize) p = 0;
9261              if(--board[p][BOARD_WIDTH-2] <= 0)
9262                   board[p][BOARD_WIDTH-1] = EmptySquare;
9263              if((int)board[p][BOARD_WIDTH-2] < 0)
9264                         board[p][BOARD_WIDTH-2] = 0;
9265         } else {                  /* black drop */
9266              p -= (int)BlackPawn;
9267                  p = PieceToNumber((ChessSquare)p);
9268              if(p >= gameInfo.holdingsSize) p = 0;
9269              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9270                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9271              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9272                         board[BOARD_HEIGHT-1-p][1] = 0;
9273         }
9274       }
9275       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9276           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9277         /* [HGM] holdings: Add to holdings, if holdings exist */
9278         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9279                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9280                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9281         }
9282         p = (int) captured;
9283         if (p >= (int) BlackPawn) {
9284           p -= (int)BlackPawn;
9285           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9286                   /* in Shogi restore piece to its original  first */
9287                   captured = (ChessSquare) (DEMOTED captured);
9288                   p = DEMOTED p;
9289           }
9290           p = PieceToNumber((ChessSquare)p);
9291           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9292           board[p][BOARD_WIDTH-2]++;
9293           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9294         } else {
9295           p -= (int)WhitePawn;
9296           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9297                   captured = (ChessSquare) (DEMOTED captured);
9298                   p = DEMOTED p;
9299           }
9300           p = PieceToNumber((ChessSquare)p);
9301           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9302           board[BOARD_HEIGHT-1-p][1]++;
9303           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9304         }
9305       }
9306     } else if (gameInfo.variant == VariantAtomic) {
9307       if (captured != EmptySquare) {
9308         int y, x;
9309         for (y = toY-1; y <= toY+1; y++) {
9310           for (x = toX-1; x <= toX+1; x++) {
9311             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9312                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9313               board[y][x] = EmptySquare;
9314             }
9315           }
9316         }
9317         board[toY][toX] = EmptySquare;
9318       }
9319     }
9320     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9321         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9322     } else
9323     if(promoChar == '+') {
9324         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9325         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9326     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9327         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9328     }
9329     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9330                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9331         // [HGM] superchess: take promotion piece out of holdings
9332         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9333         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9334             if(!--board[k][BOARD_WIDTH-2])
9335                 board[k][BOARD_WIDTH-1] = EmptySquare;
9336         } else {
9337             if(!--board[BOARD_HEIGHT-1-k][1])
9338                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9339         }
9340     }
9341
9342 }
9343
9344 /* Updates forwardMostMove */
9345 void
9346 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9347 {
9348 //    forwardMostMove++; // [HGM] bare: moved downstream
9349
9350     (void) CoordsToAlgebraic(boards[forwardMostMove],
9351                              PosFlags(forwardMostMove),
9352                              fromY, fromX, toY, toX, promoChar,
9353                              parseList[forwardMostMove]);
9354
9355     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9356         int timeLeft; static int lastLoadFlag=0; int king, piece;
9357         piece = boards[forwardMostMove][fromY][fromX];
9358         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9359         if(gameInfo.variant == VariantKnightmate)
9360             king += (int) WhiteUnicorn - (int) WhiteKing;
9361         if(forwardMostMove == 0) {
9362             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9363                 fprintf(serverMoves, "%s;", UserName());
9364             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9365                 fprintf(serverMoves, "%s;", second.tidy);
9366             fprintf(serverMoves, "%s;", first.tidy);
9367             if(gameMode == MachinePlaysWhite)
9368                 fprintf(serverMoves, "%s;", UserName());
9369             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9370                 fprintf(serverMoves, "%s;", second.tidy);
9371         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9372         lastLoadFlag = loadFlag;
9373         // print base move
9374         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9375         // print castling suffix
9376         if( toY == fromY && piece == king ) {
9377             if(toX-fromX > 1)
9378                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9379             if(fromX-toX >1)
9380                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9381         }
9382         // e.p. suffix
9383         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9384              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9385              boards[forwardMostMove][toY][toX] == EmptySquare
9386              && fromX != toX && fromY != toY)
9387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9388         // promotion suffix
9389         if(promoChar != NULLCHAR)
9390                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9391         if(!loadFlag) {
9392                 char buf[MOVE_LEN*2], *p; int len;
9393             fprintf(serverMoves, "/%d/%d",
9394                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9395             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9396             else                      timeLeft = blackTimeRemaining/1000;
9397             fprintf(serverMoves, "/%d", timeLeft);
9398                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9399                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9400                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9401             fprintf(serverMoves, "/%s", buf);
9402         }
9403         fflush(serverMoves);
9404     }
9405
9406     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9407         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9408       return;
9409     }
9410     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9411     if (commentList[forwardMostMove+1] != NULL) {
9412         free(commentList[forwardMostMove+1]);
9413         commentList[forwardMostMove+1] = NULL;
9414     }
9415     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9416     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9417     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9418     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9419     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9420     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9421     adjustedClock = FALSE;
9422     gameInfo.result = GameUnfinished;
9423     if (gameInfo.resultDetails != NULL) {
9424         free(gameInfo.resultDetails);
9425         gameInfo.resultDetails = NULL;
9426     }
9427     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9428                               moveList[forwardMostMove - 1]);
9429     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9430       case MT_NONE:
9431       case MT_STALEMATE:
9432       default:
9433         break;
9434       case MT_CHECK:
9435         if(gameInfo.variant != VariantShogi)
9436             strcat(parseList[forwardMostMove - 1], "+");
9437         break;
9438       case MT_CHECKMATE:
9439       case MT_STAINMATE:
9440         strcat(parseList[forwardMostMove - 1], "#");
9441         break;
9442     }
9443
9444 }
9445
9446 /* Updates currentMove if not pausing */
9447 void
9448 ShowMove (int fromX, int fromY, int toX, int toY)
9449 {
9450     int instant = (gameMode == PlayFromGameFile) ?
9451         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9452     if(appData.noGUI) return;
9453     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9454         if (!instant) {
9455             if (forwardMostMove == currentMove + 1) {
9456                 AnimateMove(boards[forwardMostMove - 1],
9457                             fromX, fromY, toX, toY);
9458             }
9459             if (appData.highlightLastMove) {
9460                 SetHighlights(fromX, fromY, toX, toY);
9461             }
9462         }
9463         currentMove = forwardMostMove;
9464     }
9465
9466     if (instant) return;
9467
9468     DisplayMove(currentMove - 1);
9469     DrawPosition(FALSE, boards[currentMove]);
9470     DisplayBothClocks();
9471     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9472 }
9473
9474 void
9475 SendEgtPath (ChessProgramState *cps)
9476 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9477         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9478
9479         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9480
9481         while(*p) {
9482             char c, *q = name+1, *r, *s;
9483
9484             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9485             while(*p && *p != ',') *q++ = *p++;
9486             *q++ = ':'; *q = 0;
9487             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9488                 strcmp(name, ",nalimov:") == 0 ) {
9489                 // take nalimov path from the menu-changeable option first, if it is defined
9490               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9491                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9492             } else
9493             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9494                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9495                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9496                 s = r = StrStr(s, ":") + 1; // beginning of path info
9497                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9498                 c = *r; *r = 0;             // temporarily null-terminate path info
9499                     *--q = 0;               // strip of trailig ':' from name
9500                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9501                 *r = c;
9502                 SendToProgram(buf,cps);     // send egtbpath command for this format
9503             }
9504             if(*p == ',') p++; // read away comma to position for next format name
9505         }
9506 }
9507
9508 void
9509 InitChessProgram (ChessProgramState *cps, int setup)
9510 /* setup needed to setup FRC opening position */
9511 {
9512     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9513     if (appData.noChessProgram) return;
9514     hintRequested = FALSE;
9515     bookRequested = FALSE;
9516
9517     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9518     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9519     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9520     if(cps->memSize) { /* [HGM] memory */
9521       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9522         SendToProgram(buf, cps);
9523     }
9524     SendEgtPath(cps); /* [HGM] EGT */
9525     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9526       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9527         SendToProgram(buf, cps);
9528     }
9529
9530     SendToProgram(cps->initString, cps);
9531     if (gameInfo.variant != VariantNormal &&
9532         gameInfo.variant != VariantLoadable
9533         /* [HGM] also send variant if board size non-standard */
9534         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9535                                             ) {
9536       char *v = VariantName(gameInfo.variant);
9537       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9538         /* [HGM] in protocol 1 we have to assume all variants valid */
9539         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9540         DisplayFatalError(buf, 0, 1);
9541         return;
9542       }
9543
9544       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9545       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9546       if( gameInfo.variant == VariantXiangqi )
9547            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9548       if( gameInfo.variant == VariantShogi )
9549            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9550       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9551            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9552       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9553           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9554            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9555       if( gameInfo.variant == VariantCourier )
9556            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9557       if( gameInfo.variant == VariantSuper )
9558            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9559       if( gameInfo.variant == VariantGreat )
9560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9561       if( gameInfo.variant == VariantSChess )
9562            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9563       if( gameInfo.variant == VariantGrand )
9564            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9565
9566       if(overruled) {
9567         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9568                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9569            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9570            if(StrStr(cps->variants, b) == NULL) {
9571                // specific sized variant not known, check if general sizing allowed
9572                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9573                    if(StrStr(cps->variants, "boardsize") == NULL) {
9574                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9575                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9576                        DisplayFatalError(buf, 0, 1);
9577                        return;
9578                    }
9579                    /* [HGM] here we really should compare with the maximum supported board size */
9580                }
9581            }
9582       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9583       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9584       SendToProgram(buf, cps);
9585     }
9586     currentlyInitializedVariant = gameInfo.variant;
9587
9588     /* [HGM] send opening position in FRC to first engine */
9589     if(setup) {
9590           SendToProgram("force\n", cps);
9591           SendBoard(cps, 0);
9592           /* engine is now in force mode! Set flag to wake it up after first move. */
9593           setboardSpoiledMachineBlack = 1;
9594     }
9595
9596     if (cps->sendICS) {
9597       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9598       SendToProgram(buf, cps);
9599     }
9600     cps->maybeThinking = FALSE;
9601     cps->offeredDraw = 0;
9602     if (!appData.icsActive) {
9603         SendTimeControl(cps, movesPerSession, timeControl,
9604                         timeIncrement, appData.searchDepth,
9605                         searchTime);
9606     }
9607     if (appData.showThinking
9608         // [HGM] thinking: four options require thinking output to be sent
9609         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9610                                 ) {
9611         SendToProgram("post\n", cps);
9612     }
9613     SendToProgram("hard\n", cps);
9614     if (!appData.ponderNextMove) {
9615         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9616            it without being sure what state we are in first.  "hard"
9617            is not a toggle, so that one is OK.
9618          */
9619         SendToProgram("easy\n", cps);
9620     }
9621     if (cps->usePing) {
9622       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9623       SendToProgram(buf, cps);
9624     }
9625     cps->initDone = TRUE;
9626     ClearEngineOutputPane(cps == &second);
9627 }
9628
9629
9630 void
9631 StartChessProgram (ChessProgramState *cps)
9632 {
9633     char buf[MSG_SIZ];
9634     int err;
9635
9636     if (appData.noChessProgram) return;
9637     cps->initDone = FALSE;
9638
9639     if (strcmp(cps->host, "localhost") == 0) {
9640         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9641     } else if (*appData.remoteShell == NULLCHAR) {
9642         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9643     } else {
9644         if (*appData.remoteUser == NULLCHAR) {
9645           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9646                     cps->program);
9647         } else {
9648           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9649                     cps->host, appData.remoteUser, cps->program);
9650         }
9651         err = StartChildProcess(buf, "", &cps->pr);
9652     }
9653
9654     if (err != 0) {
9655       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9656         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9657         if(cps != &first) return;
9658         appData.noChessProgram = TRUE;
9659         ThawUI();
9660         SetNCPMode();
9661 //      DisplayFatalError(buf, err, 1);
9662 //      cps->pr = NoProc;
9663 //      cps->isr = NULL;
9664         return;
9665     }
9666
9667     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9668     if (cps->protocolVersion > 1) {
9669       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9670       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9671       cps->comboCnt = 0;  //                and values of combo boxes
9672       SendToProgram(buf, cps);
9673     } else {
9674       SendToProgram("xboard\n", cps);
9675     }
9676 }
9677
9678 void
9679 TwoMachinesEventIfReady P((void))
9680 {
9681   static int curMess = 0;
9682   if (first.lastPing != first.lastPong) {
9683     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9684     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9685     return;
9686   }
9687   if (second.lastPing != second.lastPong) {
9688     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9689     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9690     return;
9691   }
9692   DisplayMessage("", ""); curMess = 0;
9693   ThawUI();
9694   TwoMachinesEvent();
9695 }
9696
9697 char *
9698 MakeName (char *template)
9699 {
9700     time_t clock;
9701     struct tm *tm;
9702     static char buf[MSG_SIZ];
9703     char *p = buf;
9704     int i;
9705
9706     clock = time((time_t *)NULL);
9707     tm = localtime(&clock);
9708
9709     while(*p++ = *template++) if(p[-1] == '%') {
9710         switch(*template++) {
9711           case 0:   *p = 0; return buf;
9712           case 'Y': i = tm->tm_year+1900; break;
9713           case 'y': i = tm->tm_year-100; break;
9714           case 'M': i = tm->tm_mon+1; break;
9715           case 'd': i = tm->tm_mday; break;
9716           case 'h': i = tm->tm_hour; break;
9717           case 'm': i = tm->tm_min; break;
9718           case 's': i = tm->tm_sec; break;
9719           default:  i = 0;
9720         }
9721         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9722     }
9723     return buf;
9724 }
9725
9726 int
9727 CountPlayers (char *p)
9728 {
9729     int n = 0;
9730     while(p = strchr(p, '\n')) p++, n++; // count participants
9731     return n;
9732 }
9733
9734 FILE *
9735 WriteTourneyFile (char *results, FILE *f)
9736 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9737     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9738     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9739         // create a file with tournament description
9740         fprintf(f, "-participants {%s}\n", appData.participants);
9741         fprintf(f, "-seedBase %d\n", appData.seedBase);
9742         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9743         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9744         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9745         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9746         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9747         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9748         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9749         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9750         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9751         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9752         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9753         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9754         if(searchTime > 0)
9755                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9756         else {
9757                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9758                 fprintf(f, "-tc %s\n", appData.timeControl);
9759                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9760         }
9761         fprintf(f, "-results \"%s\"\n", results);
9762     }
9763     return f;
9764 }
9765
9766 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9767
9768 void
9769 Substitute (char *participants, int expunge)
9770 {
9771     int i, changed, changes=0, nPlayers=0;
9772     char *p, *q, *r, buf[MSG_SIZ];
9773     if(participants == NULL) return;
9774     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9775     r = p = participants; q = appData.participants;
9776     while(*p && *p == *q) {
9777         if(*p == '\n') r = p+1, nPlayers++;
9778         p++; q++;
9779     }
9780     if(*p) { // difference
9781         while(*p && *p++ != '\n');
9782         while(*q && *q++ != '\n');
9783       changed = nPlayers;
9784         changes = 1 + (strcmp(p, q) != 0);
9785     }
9786     if(changes == 1) { // a single engine mnemonic was changed
9787         q = r; while(*q) nPlayers += (*q++ == '\n');
9788         p = buf; while(*r && (*p = *r++) != '\n') p++;
9789         *p = NULLCHAR;
9790         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9791         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9792         if(mnemonic[i]) { // The substitute is valid
9793             FILE *f;
9794             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9795                 flock(fileno(f), LOCK_EX);
9796                 ParseArgsFromFile(f);
9797                 fseek(f, 0, SEEK_SET);
9798                 FREE(appData.participants); appData.participants = participants;
9799                 if(expunge) { // erase results of replaced engine
9800                     int len = strlen(appData.results), w, b, dummy;
9801                     for(i=0; i<len; i++) {
9802                         Pairing(i, nPlayers, &w, &b, &dummy);
9803                         if((w == changed || b == changed) && appData.results[i] == '*') {
9804                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9805                             fclose(f);
9806                             return;
9807                         }
9808                     }
9809                     for(i=0; i<len; i++) {
9810                         Pairing(i, nPlayers, &w, &b, &dummy);
9811                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9812                     }
9813                 }
9814                 WriteTourneyFile(appData.results, f);
9815                 fclose(f); // release lock
9816                 return;
9817             }
9818         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9819     }
9820     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9821     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9822     free(participants);
9823     return;
9824 }
9825
9826 int
9827 CreateTourney (char *name)
9828 {
9829         FILE *f;
9830         if(matchMode && strcmp(name, appData.tourneyFile)) {
9831              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9832         }
9833         if(name[0] == NULLCHAR) {
9834             if(appData.participants[0])
9835                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9836             return 0;
9837         }
9838         f = fopen(name, "r");
9839         if(f) { // file exists
9840             ASSIGN(appData.tourneyFile, name);
9841             ParseArgsFromFile(f); // parse it
9842         } else {
9843             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9844             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9845                 DisplayError(_("Not enough participants"), 0);
9846                 return 0;
9847             }
9848             ASSIGN(appData.tourneyFile, name);
9849             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9850             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9851         }
9852         fclose(f);
9853         appData.noChessProgram = FALSE;
9854         appData.clockMode = TRUE;
9855         SetGNUMode();
9856         return 1;
9857 }
9858
9859 int
9860 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9861 {
9862     char buf[MSG_SIZ], *p, *q;
9863     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9864     skip = !all && group[0]; // if group requested, we start in skip mode
9865     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9866         p = names; q = buf; header = 0;
9867         while(*p && *p != '\n') *q++ = *p++;
9868         *q = 0;
9869         if(*p == '\n') p++;
9870         if(buf[0] == '#') {
9871             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9872             depth++; // we must be entering a new group
9873             if(all) continue; // suppress printing group headers when complete list requested
9874             header = 1;
9875             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9876         }
9877         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9878         if(engineList[i]) free(engineList[i]);
9879         engineList[i] = strdup(buf);
9880         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9881         if(engineMnemonic[i]) free(engineMnemonic[i]);
9882         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9883             strcat(buf, " (");
9884             sscanf(q + 8, "%s", buf + strlen(buf));
9885             strcat(buf, ")");
9886         }
9887         engineMnemonic[i] = strdup(buf);
9888         i++;
9889     }
9890     engineList[i] = engineMnemonic[i] = NULL;
9891     return i;
9892 }
9893
9894 // following implemented as macro to avoid type limitations
9895 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9896
9897 void
9898 SwapEngines (int n)
9899 {   // swap settings for first engine and other engine (so far only some selected options)
9900     int h;
9901     char *p;
9902     if(n == 0) return;
9903     SWAP(directory, p)
9904     SWAP(chessProgram, p)
9905     SWAP(isUCI, h)
9906     SWAP(hasOwnBookUCI, h)
9907     SWAP(protocolVersion, h)
9908     SWAP(reuse, h)
9909     SWAP(scoreIsAbsolute, h)
9910     SWAP(timeOdds, h)
9911     SWAP(logo, p)
9912     SWAP(pgnName, p)
9913     SWAP(pvSAN, h)
9914     SWAP(engOptions, p)
9915 }
9916
9917 int
9918 SetPlayer (int player, char *p)
9919 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9920     int i;
9921     char buf[MSG_SIZ], *engineName;
9922     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9923     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9924     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9925     if(mnemonic[i]) {
9926         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9927         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9928         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9929         ParseArgsFromString(buf);
9930     }
9931     free(engineName);
9932     return i;
9933 }
9934
9935 char *recentEngines;
9936
9937 void
9938 RecentEngineEvent (int nr)
9939 {
9940     int n;
9941 //    SwapEngines(1); // bump first to second
9942 //    ReplaceEngine(&second, 1); // and load it there
9943     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9944     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9945     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9946         ReplaceEngine(&first, 0);
9947         FloatToFront(&appData.recentEngineList, command[n]);
9948     }
9949 }
9950
9951 int
9952 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9953 {   // determine players from game number
9954     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9955
9956     if(appData.tourneyType == 0) {
9957         roundsPerCycle = (nPlayers - 1) | 1;
9958         pairingsPerRound = nPlayers / 2;
9959     } else if(appData.tourneyType > 0) {
9960         roundsPerCycle = nPlayers - appData.tourneyType;
9961         pairingsPerRound = appData.tourneyType;
9962     }
9963     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9964     gamesPerCycle = gamesPerRound * roundsPerCycle;
9965     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9966     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9967     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9968     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9969     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9970     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9971
9972     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9973     if(appData.roundSync) *syncInterval = gamesPerRound;
9974
9975     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9976
9977     if(appData.tourneyType == 0) {
9978         if(curPairing == (nPlayers-1)/2 ) {
9979             *whitePlayer = curRound;
9980             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9981         } else {
9982             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9983             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9984             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9985             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9986         }
9987     } else if(appData.tourneyType > 1) {
9988         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9989         *whitePlayer = curRound + appData.tourneyType;
9990     } else if(appData.tourneyType > 0) {
9991         *whitePlayer = curPairing;
9992         *blackPlayer = curRound + appData.tourneyType;
9993     }
9994
9995     // take care of white/black alternation per round. 
9996     // For cycles and games this is already taken care of by default, derived from matchGame!
9997     return curRound & 1;
9998 }
9999
10000 int
10001 NextTourneyGame (int nr, int *swapColors)
10002 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10003     char *p, *q;
10004     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10005     FILE *tf;
10006     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10007     tf = fopen(appData.tourneyFile, "r");
10008     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10009     ParseArgsFromFile(tf); fclose(tf);
10010     InitTimeControls(); // TC might be altered from tourney file
10011
10012     nPlayers = CountPlayers(appData.participants); // count participants
10013     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10014     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10015
10016     if(syncInterval) {
10017         p = q = appData.results;
10018         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10019         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10020             DisplayMessage(_("Waiting for other game(s)"),"");
10021             waitingForGame = TRUE;
10022             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10023             return 0;
10024         }
10025         waitingForGame = FALSE;
10026     }
10027
10028     if(appData.tourneyType < 0) {
10029         if(nr>=0 && !pairingReceived) {
10030             char buf[1<<16];
10031             if(pairing.pr == NoProc) {
10032                 if(!appData.pairingEngine[0]) {
10033                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10034                     return 0;
10035                 }
10036                 StartChessProgram(&pairing); // starts the pairing engine
10037             }
10038             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10039             SendToProgram(buf, &pairing);
10040             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10041             SendToProgram(buf, &pairing);
10042             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10043         }
10044         pairingReceived = 0;                              // ... so we continue here 
10045         *swapColors = 0;
10046         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10047         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10048         matchGame = 1; roundNr = nr / syncInterval + 1;
10049     }
10050
10051     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10052
10053     // redefine engines, engine dir, etc.
10054     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10055     if(first.pr == NoProc) {
10056       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10057       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10058     }
10059     if(second.pr == NoProc) {
10060       SwapEngines(1);
10061       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10062       SwapEngines(1);         // and make that valid for second engine by swapping
10063       InitEngine(&second, 1);
10064     }
10065     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10066     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10067     return 1;
10068 }
10069
10070 void
10071 NextMatchGame ()
10072 {   // performs game initialization that does not invoke engines, and then tries to start the game
10073     int res, firstWhite, swapColors = 0;
10074     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10075     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
10076         char buf[MSG_SIZ];
10077         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10078         if(strcmp(buf, currentDebugFile)) { // name has changed
10079             FILE *f = fopen(buf, "w");
10080             if(f) { // if opening the new file failed, just keep using the old one
10081                 ASSIGN(currentDebugFile, buf);
10082                 fclose(debugFP);
10083                 debugFP = f;
10084             }
10085         }
10086     }
10087     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10088     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10089     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10090     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10091     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10092     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10093     Reset(FALSE, first.pr != NoProc);
10094     res = LoadGameOrPosition(matchGame); // setup game
10095     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10096     if(!res) return; // abort when bad game/pos file
10097     TwoMachinesEvent();
10098 }
10099
10100 void
10101 UserAdjudicationEvent (int result)
10102 {
10103     ChessMove gameResult = GameIsDrawn;
10104
10105     if( result > 0 ) {
10106         gameResult = WhiteWins;
10107     }
10108     else if( result < 0 ) {
10109         gameResult = BlackWins;
10110     }
10111
10112     if( gameMode == TwoMachinesPlay ) {
10113         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10114     }
10115 }
10116
10117
10118 // [HGM] save: calculate checksum of game to make games easily identifiable
10119 int
10120 StringCheckSum (char *s)
10121 {
10122         int i = 0;
10123         if(s==NULL) return 0;
10124         while(*s) i = i*259 + *s++;
10125         return i;
10126 }
10127
10128 int
10129 GameCheckSum ()
10130 {
10131         int i, sum=0;
10132         for(i=backwardMostMove; i<forwardMostMove; i++) {
10133                 sum += pvInfoList[i].depth;
10134                 sum += StringCheckSum(parseList[i]);
10135                 sum += StringCheckSum(commentList[i]);
10136                 sum *= 261;
10137         }
10138         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10139         return sum + StringCheckSum(commentList[i]);
10140 } // end of save patch
10141
10142 void
10143 GameEnds (ChessMove result, char *resultDetails, int whosays)
10144 {
10145     GameMode nextGameMode;
10146     int isIcsGame;
10147     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10148
10149     if(endingGame) return; /* [HGM] crash: forbid recursion */
10150     endingGame = 1;
10151     if(twoBoards) { // [HGM] dual: switch back to one board
10152         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10153         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10154     }
10155     if (appData.debugMode) {
10156       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10157               result, resultDetails ? resultDetails : "(null)", whosays);
10158     }
10159
10160     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10161
10162     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10163         /* If we are playing on ICS, the server decides when the
10164            game is over, but the engine can offer to draw, claim
10165            a draw, or resign.
10166          */
10167 #if ZIPPY
10168         if (appData.zippyPlay && first.initDone) {
10169             if (result == GameIsDrawn) {
10170                 /* In case draw still needs to be claimed */
10171                 SendToICS(ics_prefix);
10172                 SendToICS("draw\n");
10173             } else if (StrCaseStr(resultDetails, "resign")) {
10174                 SendToICS(ics_prefix);
10175                 SendToICS("resign\n");
10176             }
10177         }
10178 #endif
10179         endingGame = 0; /* [HGM] crash */
10180         return;
10181     }
10182
10183     /* If we're loading the game from a file, stop */
10184     if (whosays == GE_FILE) {
10185       (void) StopLoadGameTimer();
10186       gameFileFP = NULL;
10187     }
10188
10189     /* Cancel draw offers */
10190     first.offeredDraw = second.offeredDraw = 0;
10191
10192     /* If this is an ICS game, only ICS can really say it's done;
10193        if not, anyone can. */
10194     isIcsGame = (gameMode == IcsPlayingWhite ||
10195                  gameMode == IcsPlayingBlack ||
10196                  gameMode == IcsObserving    ||
10197                  gameMode == IcsExamining);
10198
10199     if (!isIcsGame || whosays == GE_ICS) {
10200         /* OK -- not an ICS game, or ICS said it was done */
10201         StopClocks();
10202         if (!isIcsGame && !appData.noChessProgram)
10203           SetUserThinkingEnables();
10204
10205         /* [HGM] if a machine claims the game end we verify this claim */
10206         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10207             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10208                 char claimer;
10209                 ChessMove trueResult = (ChessMove) -1;
10210
10211                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10212                                             first.twoMachinesColor[0] :
10213                                             second.twoMachinesColor[0] ;
10214
10215                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10216                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10217                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10218                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10219                 } else
10220                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10221                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10222                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10223                 } else
10224                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10225                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10226                 }
10227
10228                 // now verify win claims, but not in drop games, as we don't understand those yet
10229                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10230                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10231                     (result == WhiteWins && claimer == 'w' ||
10232                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10233                       if (appData.debugMode) {
10234                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10235                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10236                       }
10237                       if(result != trueResult) {
10238                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10239                               result = claimer == 'w' ? BlackWins : WhiteWins;
10240                               resultDetails = buf;
10241                       }
10242                 } else
10243                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10244                     && (forwardMostMove <= backwardMostMove ||
10245                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10246                         (claimer=='b')==(forwardMostMove&1))
10247                                                                                   ) {
10248                       /* [HGM] verify: draws that were not flagged are false claims */
10249                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10250                       result = claimer == 'w' ? BlackWins : WhiteWins;
10251                       resultDetails = buf;
10252                 }
10253                 /* (Claiming a loss is accepted no questions asked!) */
10254             }
10255             /* [HGM] bare: don't allow bare King to win */
10256             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10257                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10258                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10259                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10260                && result != GameIsDrawn)
10261             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10262                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10263                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10264                         if(p >= 0 && p <= (int)WhiteKing) k++;
10265                 }
10266                 if (appData.debugMode) {
10267                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10268                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10269                 }
10270                 if(k <= 1) {
10271                         result = GameIsDrawn;
10272                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10273                         resultDetails = buf;
10274                 }
10275             }
10276         }
10277
10278
10279         if(serverMoves != NULL && !loadFlag) { char c = '=';
10280             if(result==WhiteWins) c = '+';
10281             if(result==BlackWins) c = '-';
10282             if(resultDetails != NULL)
10283                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10284         }
10285         if (resultDetails != NULL) {
10286             gameInfo.result = result;
10287             gameInfo.resultDetails = StrSave(resultDetails);
10288
10289             /* display last move only if game was not loaded from file */
10290             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10291                 DisplayMove(currentMove - 1);
10292
10293             if (forwardMostMove != 0) {
10294                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10295                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10296                                                                 ) {
10297                     if (*appData.saveGameFile != NULLCHAR) {
10298                         SaveGameToFile(appData.saveGameFile, TRUE);
10299                     } else if (appData.autoSaveGames) {
10300                         AutoSaveGame();
10301                     }
10302                     if (*appData.savePositionFile != NULLCHAR) {
10303                         SavePositionToFile(appData.savePositionFile);
10304                     }
10305                 }
10306             }
10307
10308             /* Tell program how game ended in case it is learning */
10309             /* [HGM] Moved this to after saving the PGN, just in case */
10310             /* engine died and we got here through time loss. In that */
10311             /* case we will get a fatal error writing the pipe, which */
10312             /* would otherwise lose us the PGN.                       */
10313             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10314             /* output during GameEnds should never be fatal anymore   */
10315             if (gameMode == MachinePlaysWhite ||
10316                 gameMode == MachinePlaysBlack ||
10317                 gameMode == TwoMachinesPlay ||
10318                 gameMode == IcsPlayingWhite ||
10319                 gameMode == IcsPlayingBlack ||
10320                 gameMode == BeginningOfGame) {
10321                 char buf[MSG_SIZ];
10322                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10323                         resultDetails);
10324                 if (first.pr != NoProc) {
10325                     SendToProgram(buf, &first);
10326                 }
10327                 if (second.pr != NoProc &&
10328                     gameMode == TwoMachinesPlay) {
10329                     SendToProgram(buf, &second);
10330                 }
10331             }
10332         }
10333
10334         if (appData.icsActive) {
10335             if (appData.quietPlay &&
10336                 (gameMode == IcsPlayingWhite ||
10337                  gameMode == IcsPlayingBlack)) {
10338                 SendToICS(ics_prefix);
10339                 SendToICS("set shout 1\n");
10340             }
10341             nextGameMode = IcsIdle;
10342             ics_user_moved = FALSE;
10343             /* clean up premove.  It's ugly when the game has ended and the
10344              * premove highlights are still on the board.
10345              */
10346             if (gotPremove) {
10347               gotPremove = FALSE;
10348               ClearPremoveHighlights();
10349               DrawPosition(FALSE, boards[currentMove]);
10350             }
10351             if (whosays == GE_ICS) {
10352                 switch (result) {
10353                 case WhiteWins:
10354                     if (gameMode == IcsPlayingWhite)
10355                         PlayIcsWinSound();
10356                     else if(gameMode == IcsPlayingBlack)
10357                         PlayIcsLossSound();
10358                     break;
10359                 case BlackWins:
10360                     if (gameMode == IcsPlayingBlack)
10361                         PlayIcsWinSound();
10362                     else if(gameMode == IcsPlayingWhite)
10363                         PlayIcsLossSound();
10364                     break;
10365                 case GameIsDrawn:
10366                     PlayIcsDrawSound();
10367                     break;
10368                 default:
10369                     PlayIcsUnfinishedSound();
10370                 }
10371             }
10372         } else if (gameMode == EditGame ||
10373                    gameMode == PlayFromGameFile ||
10374                    gameMode == AnalyzeMode ||
10375                    gameMode == AnalyzeFile) {
10376             nextGameMode = gameMode;
10377         } else {
10378             nextGameMode = EndOfGame;
10379         }
10380         pausing = FALSE;
10381         ModeHighlight();
10382     } else {
10383         nextGameMode = gameMode;
10384     }
10385
10386     if (appData.noChessProgram) {
10387         gameMode = nextGameMode;
10388         ModeHighlight();
10389         endingGame = 0; /* [HGM] crash */
10390         return;
10391     }
10392
10393     if (first.reuse) {
10394         /* Put first chess program into idle state */
10395         if (first.pr != NoProc &&
10396             (gameMode == MachinePlaysWhite ||
10397              gameMode == MachinePlaysBlack ||
10398              gameMode == TwoMachinesPlay ||
10399              gameMode == IcsPlayingWhite ||
10400              gameMode == IcsPlayingBlack ||
10401              gameMode == BeginningOfGame)) {
10402             SendToProgram("force\n", &first);
10403             if (first.usePing) {
10404               char buf[MSG_SIZ];
10405               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10406               SendToProgram(buf, &first);
10407             }
10408         }
10409     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10410         /* Kill off first chess program */
10411         if (first.isr != NULL)
10412           RemoveInputSource(first.isr);
10413         first.isr = NULL;
10414
10415         if (first.pr != NoProc) {
10416             ExitAnalyzeMode();
10417             DoSleep( appData.delayBeforeQuit );
10418             SendToProgram("quit\n", &first);
10419             DoSleep( appData.delayAfterQuit );
10420             DestroyChildProcess(first.pr, first.useSigterm);
10421         }
10422         first.pr = NoProc;
10423     }
10424     if (second.reuse) {
10425         /* Put second chess program into idle state */
10426         if (second.pr != NoProc &&
10427             gameMode == TwoMachinesPlay) {
10428             SendToProgram("force\n", &second);
10429             if (second.usePing) {
10430               char buf[MSG_SIZ];
10431               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10432               SendToProgram(buf, &second);
10433             }
10434         }
10435     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10436         /* Kill off second chess program */
10437         if (second.isr != NULL)
10438           RemoveInputSource(second.isr);
10439         second.isr = NULL;
10440
10441         if (second.pr != NoProc) {
10442             DoSleep( appData.delayBeforeQuit );
10443             SendToProgram("quit\n", &second);
10444             DoSleep( appData.delayAfterQuit );
10445             DestroyChildProcess(second.pr, second.useSigterm);
10446         }
10447         second.pr = NoProc;
10448     }
10449
10450     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10451         char resChar = '=';
10452         switch (result) {
10453         case WhiteWins:
10454           resChar = '+';
10455           if (first.twoMachinesColor[0] == 'w') {
10456             first.matchWins++;
10457           } else {
10458             second.matchWins++;
10459           }
10460           break;
10461         case BlackWins:
10462           resChar = '-';
10463           if (first.twoMachinesColor[0] == 'b') {
10464             first.matchWins++;
10465           } else {
10466             second.matchWins++;
10467           }
10468           break;
10469         case GameUnfinished:
10470           resChar = ' ';
10471         default:
10472           break;
10473         }
10474
10475         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10476         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10477             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10478             ReserveGame(nextGame, resChar); // sets nextGame
10479             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10480             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10481         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10482
10483         if (nextGame <= appData.matchGames && !abortMatch) {
10484             gameMode = nextGameMode;
10485             matchGame = nextGame; // this will be overruled in tourney mode!
10486             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10487             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10488             endingGame = 0; /* [HGM] crash */
10489             return;
10490         } else {
10491             gameMode = nextGameMode;
10492             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10493                      first.tidy, second.tidy,
10494                      first.matchWins, second.matchWins,
10495                      appData.matchGames - (first.matchWins + second.matchWins));
10496             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10497             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10498             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10499             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10500                 first.twoMachinesColor = "black\n";
10501                 second.twoMachinesColor = "white\n";
10502             } else {
10503                 first.twoMachinesColor = "white\n";
10504                 second.twoMachinesColor = "black\n";
10505             }
10506         }
10507     }
10508     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10509         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10510       ExitAnalyzeMode();
10511     gameMode = nextGameMode;
10512     ModeHighlight();
10513     endingGame = 0;  /* [HGM] crash */
10514     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10515         if(matchMode == TRUE) { // match through command line: exit with or without popup
10516             if(ranking) {
10517                 ToNrEvent(forwardMostMove);
10518                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10519                 else ExitEvent(0);
10520             } else DisplayFatalError(buf, 0, 0);
10521         } else { // match through menu; just stop, with or without popup
10522             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10523             ModeHighlight();
10524             if(ranking){
10525                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10526             } else DisplayNote(buf);
10527       }
10528       if(ranking) free(ranking);
10529     }
10530 }
10531
10532 /* Assumes program was just initialized (initString sent).
10533    Leaves program in force mode. */
10534 void
10535 FeedMovesToProgram (ChessProgramState *cps, int upto)
10536 {
10537     int i;
10538
10539     if (appData.debugMode)
10540       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10541               startedFromSetupPosition ? "position and " : "",
10542               backwardMostMove, upto, cps->which);
10543     if(currentlyInitializedVariant != gameInfo.variant) {
10544       char buf[MSG_SIZ];
10545         // [HGM] variantswitch: make engine aware of new variant
10546         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10547                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10548         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10549         SendToProgram(buf, cps);
10550         currentlyInitializedVariant = gameInfo.variant;
10551     }
10552     SendToProgram("force\n", cps);
10553     if (startedFromSetupPosition) {
10554         SendBoard(cps, backwardMostMove);
10555     if (appData.debugMode) {
10556         fprintf(debugFP, "feedMoves\n");
10557     }
10558     }
10559     for (i = backwardMostMove; i < upto; i++) {
10560         SendMoveToProgram(i, cps);
10561     }
10562 }
10563
10564
10565 int
10566 ResurrectChessProgram ()
10567 {
10568      /* The chess program may have exited.
10569         If so, restart it and feed it all the moves made so far. */
10570     static int doInit = 0;
10571
10572     if (appData.noChessProgram) return 1;
10573
10574     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10575         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10576         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10577         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10578     } else {
10579         if (first.pr != NoProc) return 1;
10580         StartChessProgram(&first);
10581     }
10582     InitChessProgram(&first, FALSE);
10583     FeedMovesToProgram(&first, currentMove);
10584
10585     if (!first.sendTime) {
10586         /* can't tell gnuchess what its clock should read,
10587            so we bow to its notion. */
10588         ResetClocks();
10589         timeRemaining[0][currentMove] = whiteTimeRemaining;
10590         timeRemaining[1][currentMove] = blackTimeRemaining;
10591     }
10592
10593     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10594                 appData.icsEngineAnalyze) && first.analysisSupport) {
10595       SendToProgram("analyze\n", &first);
10596       first.analyzing = TRUE;
10597     }
10598     return 1;
10599 }
10600
10601 /*
10602  * Button procedures
10603  */
10604 void
10605 Reset (int redraw, int init)
10606 {
10607     int i;
10608
10609     if (appData.debugMode) {
10610         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10611                 redraw, init, gameMode);
10612     }
10613     CleanupTail(); // [HGM] vari: delete any stored variations
10614     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10615     pausing = pauseExamInvalid = FALSE;
10616     startedFromSetupPosition = blackPlaysFirst = FALSE;
10617     firstMove = TRUE;
10618     whiteFlag = blackFlag = FALSE;
10619     userOfferedDraw = FALSE;
10620     hintRequested = bookRequested = FALSE;
10621     first.maybeThinking = FALSE;
10622     second.maybeThinking = FALSE;
10623     first.bookSuspend = FALSE; // [HGM] book
10624     second.bookSuspend = FALSE;
10625     thinkOutput[0] = NULLCHAR;
10626     lastHint[0] = NULLCHAR;
10627     ClearGameInfo(&gameInfo);
10628     gameInfo.variant = StringToVariant(appData.variant);
10629     ics_user_moved = ics_clock_paused = FALSE;
10630     ics_getting_history = H_FALSE;
10631     ics_gamenum = -1;
10632     white_holding[0] = black_holding[0] = NULLCHAR;
10633     ClearProgramStats();
10634     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10635
10636     ResetFrontEnd();
10637     ClearHighlights();
10638     flipView = appData.flipView;
10639     ClearPremoveHighlights();
10640     gotPremove = FALSE;
10641     alarmSounded = FALSE;
10642
10643     GameEnds(EndOfFile, NULL, GE_PLAYER);
10644     if(appData.serverMovesName != NULL) {
10645         /* [HGM] prepare to make moves file for broadcasting */
10646         clock_t t = clock();
10647         if(serverMoves != NULL) fclose(serverMoves);
10648         serverMoves = fopen(appData.serverMovesName, "r");
10649         if(serverMoves != NULL) {
10650             fclose(serverMoves);
10651             /* delay 15 sec before overwriting, so all clients can see end */
10652             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10653         }
10654         serverMoves = fopen(appData.serverMovesName, "w");
10655     }
10656
10657     ExitAnalyzeMode();
10658     gameMode = BeginningOfGame;
10659     ModeHighlight();
10660     if(appData.icsActive) gameInfo.variant = VariantNormal;
10661     currentMove = forwardMostMove = backwardMostMove = 0;
10662     MarkTargetSquares(1);
10663     InitPosition(redraw);
10664     for (i = 0; i < MAX_MOVES; i++) {
10665         if (commentList[i] != NULL) {
10666             free(commentList[i]);
10667             commentList[i] = NULL;
10668         }
10669     }
10670     ResetClocks();
10671     timeRemaining[0][0] = whiteTimeRemaining;
10672     timeRemaining[1][0] = blackTimeRemaining;
10673
10674     if (first.pr == NoProc) {
10675         StartChessProgram(&first);
10676     }
10677     if (init) {
10678             InitChessProgram(&first, startedFromSetupPosition);
10679     }
10680     DisplayTitle("");
10681     DisplayMessage("", "");
10682     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10683     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10684 }
10685
10686 void
10687 AutoPlayGameLoop ()
10688 {
10689     for (;;) {
10690         if (!AutoPlayOneMove())
10691           return;
10692         if (matchMode || appData.timeDelay == 0)
10693           continue;
10694         if (appData.timeDelay < 0)
10695           return;
10696         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10697         break;
10698     }
10699 }
10700
10701
10702 int
10703 AutoPlayOneMove ()
10704 {
10705     int fromX, fromY, toX, toY;
10706
10707     if (appData.debugMode) {
10708       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10709     }
10710
10711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10712       return FALSE;
10713
10714     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10715       pvInfoList[currentMove].depth = programStats.depth;
10716       pvInfoList[currentMove].score = programStats.score;
10717       pvInfoList[currentMove].time  = 0;
10718       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10719     }
10720
10721     if (currentMove >= forwardMostMove) {
10722       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10723 //      gameMode = EndOfGame;
10724 //      ModeHighlight();
10725
10726       /* [AS] Clear current move marker at the end of a game */
10727       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10728
10729       return FALSE;
10730     }
10731
10732     toX = moveList[currentMove][2] - AAA;
10733     toY = moveList[currentMove][3] - ONE;
10734
10735     if (moveList[currentMove][1] == '@') {
10736         if (appData.highlightLastMove) {
10737             SetHighlights(-1, -1, toX, toY);
10738         }
10739     } else {
10740         fromX = moveList[currentMove][0] - AAA;
10741         fromY = moveList[currentMove][1] - ONE;
10742
10743         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10744
10745         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10746
10747         if (appData.highlightLastMove) {
10748             SetHighlights(fromX, fromY, toX, toY);
10749         }
10750     }
10751     DisplayMove(currentMove);
10752     SendMoveToProgram(currentMove++, &first);
10753     DisplayBothClocks();
10754     DrawPosition(FALSE, boards[currentMove]);
10755     // [HGM] PV info: always display, routine tests if empty
10756     DisplayComment(currentMove - 1, commentList[currentMove]);
10757     return TRUE;
10758 }
10759
10760
10761 int
10762 LoadGameOneMove (ChessMove readAhead)
10763 {
10764     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10765     char promoChar = NULLCHAR;
10766     ChessMove moveType;
10767     char move[MSG_SIZ];
10768     char *p, *q;
10769
10770     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10771         gameMode != AnalyzeMode && gameMode != Training) {
10772         gameFileFP = NULL;
10773         return FALSE;
10774     }
10775
10776     yyboardindex = forwardMostMove;
10777     if (readAhead != EndOfFile) {
10778       moveType = readAhead;
10779     } else {
10780       if (gameFileFP == NULL)
10781           return FALSE;
10782       moveType = (ChessMove) Myylex();
10783     }
10784
10785     done = FALSE;
10786     switch (moveType) {
10787       case Comment:
10788         if (appData.debugMode)
10789           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10790         p = yy_text;
10791
10792         /* append the comment but don't display it */
10793         AppendComment(currentMove, p, FALSE);
10794         return TRUE;
10795
10796       case WhiteCapturesEnPassant:
10797       case BlackCapturesEnPassant:
10798       case WhitePromotion:
10799       case BlackPromotion:
10800       case WhiteNonPromotion:
10801       case BlackNonPromotion:
10802       case NormalMove:
10803       case WhiteKingSideCastle:
10804       case WhiteQueenSideCastle:
10805       case BlackKingSideCastle:
10806       case BlackQueenSideCastle:
10807       case WhiteKingSideCastleWild:
10808       case WhiteQueenSideCastleWild:
10809       case BlackKingSideCastleWild:
10810       case BlackQueenSideCastleWild:
10811       /* PUSH Fabien */
10812       case WhiteHSideCastleFR:
10813       case WhiteASideCastleFR:
10814       case BlackHSideCastleFR:
10815       case BlackASideCastleFR:
10816       /* POP Fabien */
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10819         fromX = currentMoveString[0] - AAA;
10820         fromY = currentMoveString[1] - ONE;
10821         toX = currentMoveString[2] - AAA;
10822         toY = currentMoveString[3] - ONE;
10823         promoChar = currentMoveString[4];
10824         break;
10825
10826       case WhiteDrop:
10827       case BlackDrop:
10828         if (appData.debugMode)
10829           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10830         fromX = moveType == WhiteDrop ?
10831           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10832         (int) CharToPiece(ToLower(currentMoveString[0]));
10833         fromY = DROP_RANK;
10834         toX = currentMoveString[2] - AAA;
10835         toY = currentMoveString[3] - ONE;
10836         break;
10837
10838       case WhiteWins:
10839       case BlackWins:
10840       case GameIsDrawn:
10841       case GameUnfinished:
10842         if (appData.debugMode)
10843           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10844         p = strchr(yy_text, '{');
10845         if (p == NULL) p = strchr(yy_text, '(');
10846         if (p == NULL) {
10847             p = yy_text;
10848             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10849         } else {
10850             q = strchr(p, *p == '{' ? '}' : ')');
10851             if (q != NULL) *q = NULLCHAR;
10852             p++;
10853         }
10854         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10855         GameEnds(moveType, p, GE_FILE);
10856         done = TRUE;
10857         if (cmailMsgLoaded) {
10858             ClearHighlights();
10859             flipView = WhiteOnMove(currentMove);
10860             if (moveType == GameUnfinished) flipView = !flipView;
10861             if (appData.debugMode)
10862               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10863         }
10864         break;
10865
10866       case EndOfFile:
10867         if (appData.debugMode)
10868           fprintf(debugFP, "Parser hit end of file\n");
10869         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10870           case MT_NONE:
10871           case MT_CHECK:
10872             break;
10873           case MT_CHECKMATE:
10874           case MT_STAINMATE:
10875             if (WhiteOnMove(currentMove)) {
10876                 GameEnds(BlackWins, "Black mates", GE_FILE);
10877             } else {
10878                 GameEnds(WhiteWins, "White mates", GE_FILE);
10879             }
10880             break;
10881           case MT_STALEMATE:
10882             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10883             break;
10884         }
10885         done = TRUE;
10886         break;
10887
10888       case MoveNumberOne:
10889         if (lastLoadGameStart == GNUChessGame) {
10890             /* GNUChessGames have numbers, but they aren't move numbers */
10891             if (appData.debugMode)
10892               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10893                       yy_text, (int) moveType);
10894             return LoadGameOneMove(EndOfFile); /* tail recursion */
10895         }
10896         /* else fall thru */
10897
10898       case XBoardGame:
10899       case GNUChessGame:
10900       case PGNTag:
10901         /* Reached start of next game in file */
10902         if (appData.debugMode)
10903           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10904         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10905           case MT_NONE:
10906           case MT_CHECK:
10907             break;
10908           case MT_CHECKMATE:
10909           case MT_STAINMATE:
10910             if (WhiteOnMove(currentMove)) {
10911                 GameEnds(BlackWins, "Black mates", GE_FILE);
10912             } else {
10913                 GameEnds(WhiteWins, "White mates", GE_FILE);
10914             }
10915             break;
10916           case MT_STALEMATE:
10917             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10918             break;
10919         }
10920         done = TRUE;
10921         break;
10922
10923       case PositionDiagram:     /* should not happen; ignore */
10924       case ElapsedTime:         /* ignore */
10925       case NAG:                 /* ignore */
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10928                   yy_text, (int) moveType);
10929         return LoadGameOneMove(EndOfFile); /* tail recursion */
10930
10931       case IllegalMove:
10932         if (appData.testLegality) {
10933             if (appData.debugMode)
10934               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10935             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10936                     (forwardMostMove / 2) + 1,
10937                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10938             DisplayError(move, 0);
10939             done = TRUE;
10940         } else {
10941             if (appData.debugMode)
10942               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10943                       yy_text, currentMoveString);
10944             fromX = currentMoveString[0] - AAA;
10945             fromY = currentMoveString[1] - ONE;
10946             toX = currentMoveString[2] - AAA;
10947             toY = currentMoveString[3] - ONE;
10948             promoChar = currentMoveString[4];
10949         }
10950         break;
10951
10952       case AmbiguousMove:
10953         if (appData.debugMode)
10954           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10955         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10956                 (forwardMostMove / 2) + 1,
10957                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10958         DisplayError(move, 0);
10959         done = TRUE;
10960         break;
10961
10962       default:
10963       case ImpossibleMove:
10964         if (appData.debugMode)
10965           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10966         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10967                 (forwardMostMove / 2) + 1,
10968                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10969         DisplayError(move, 0);
10970         done = TRUE;
10971         break;
10972     }
10973
10974     if (done) {
10975         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10976             DrawPosition(FALSE, boards[currentMove]);
10977             DisplayBothClocks();
10978             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10979               DisplayComment(currentMove - 1, commentList[currentMove]);
10980         }
10981         (void) StopLoadGameTimer();
10982         gameFileFP = NULL;
10983         cmailOldMove = forwardMostMove;
10984         return FALSE;
10985     } else {
10986         /* currentMoveString is set as a side-effect of yylex */
10987
10988         thinkOutput[0] = NULLCHAR;
10989         MakeMove(fromX, fromY, toX, toY, promoChar);
10990         currentMove = forwardMostMove;
10991         return TRUE;
10992     }
10993 }
10994
10995 /* Load the nth game from the given file */
10996 int
10997 LoadGameFromFile (char *filename, int n, char *title, int useList)
10998 {
10999     FILE *f;
11000     char buf[MSG_SIZ];
11001
11002     if (strcmp(filename, "-") == 0) {
11003         f = stdin;
11004         title = "stdin";
11005     } else {
11006         f = fopen(filename, "rb");
11007         if (f == NULL) {
11008           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11009             DisplayError(buf, errno);
11010             return FALSE;
11011         }
11012     }
11013     if (fseek(f, 0, 0) == -1) {
11014         /* f is not seekable; probably a pipe */
11015         useList = FALSE;
11016     }
11017     if (useList && n == 0) {
11018         int error = GameListBuild(f);
11019         if (error) {
11020             DisplayError(_("Cannot build game list"), error);
11021         } else if (!ListEmpty(&gameList) &&
11022                    ((ListGame *) gameList.tailPred)->number > 1) {
11023             GameListPopUp(f, title);
11024             return TRUE;
11025         }
11026         GameListDestroy();
11027         n = 1;
11028     }
11029     if (n == 0) n = 1;
11030     return LoadGame(f, n, title, FALSE);
11031 }
11032
11033
11034 void
11035 MakeRegisteredMove ()
11036 {
11037     int fromX, fromY, toX, toY;
11038     char promoChar;
11039     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11040         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11041           case CMAIL_MOVE:
11042           case CMAIL_DRAW:
11043             if (appData.debugMode)
11044               fprintf(debugFP, "Restoring %s for game %d\n",
11045                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11046
11047             thinkOutput[0] = NULLCHAR;
11048             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11049             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11050             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11051             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11052             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11053             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11054             MakeMove(fromX, fromY, toX, toY, promoChar);
11055             ShowMove(fromX, fromY, toX, toY);
11056
11057             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11058               case MT_NONE:
11059               case MT_CHECK:
11060                 break;
11061
11062               case MT_CHECKMATE:
11063               case MT_STAINMATE:
11064                 if (WhiteOnMove(currentMove)) {
11065                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11066                 } else {
11067                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11068                 }
11069                 break;
11070
11071               case MT_STALEMATE:
11072                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11073                 break;
11074             }
11075
11076             break;
11077
11078           case CMAIL_RESIGN:
11079             if (WhiteOnMove(currentMove)) {
11080                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11081             } else {
11082                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11083             }
11084             break;
11085
11086           case CMAIL_ACCEPT:
11087             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11088             break;
11089
11090           default:
11091             break;
11092         }
11093     }
11094
11095     return;
11096 }
11097
11098 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11099 int
11100 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11101 {
11102     int retVal;
11103
11104     if (gameNumber > nCmailGames) {
11105         DisplayError(_("No more games in this message"), 0);
11106         return FALSE;
11107     }
11108     if (f == lastLoadGameFP) {
11109         int offset = gameNumber - lastLoadGameNumber;
11110         if (offset == 0) {
11111             cmailMsg[0] = NULLCHAR;
11112             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11113                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11114                 nCmailMovesRegistered--;
11115             }
11116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11117             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11118                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11119             }
11120         } else {
11121             if (! RegisterMove()) return FALSE;
11122         }
11123     }
11124
11125     retVal = LoadGame(f, gameNumber, title, useList);
11126
11127     /* Make move registered during previous look at this game, if any */
11128     MakeRegisteredMove();
11129
11130     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11131         commentList[currentMove]
11132           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11133         DisplayComment(currentMove - 1, commentList[currentMove]);
11134     }
11135
11136     return retVal;
11137 }
11138
11139 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11140 int
11141 ReloadGame (int offset)
11142 {
11143     int gameNumber = lastLoadGameNumber + offset;
11144     if (lastLoadGameFP == NULL) {
11145         DisplayError(_("No game has been loaded yet"), 0);
11146         return FALSE;
11147     }
11148     if (gameNumber <= 0) {
11149         DisplayError(_("Can't back up any further"), 0);
11150         return FALSE;
11151     }
11152     if (cmailMsgLoaded) {
11153         return CmailLoadGame(lastLoadGameFP, gameNumber,
11154                              lastLoadGameTitle, lastLoadGameUseList);
11155     } else {
11156         return LoadGame(lastLoadGameFP, gameNumber,
11157                         lastLoadGameTitle, lastLoadGameUseList);
11158     }
11159 }
11160
11161 int keys[EmptySquare+1];
11162
11163 int
11164 PositionMatches (Board b1, Board b2)
11165 {
11166     int r, f, sum=0;
11167     switch(appData.searchMode) {
11168         case 1: return CompareWithRights(b1, b2);
11169         case 2:
11170             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11171                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11172             }
11173             return TRUE;
11174         case 3:
11175             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11176               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11177                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11178             }
11179             return sum==0;
11180         case 4:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11183             }
11184             return sum==0;
11185     }
11186     return TRUE;
11187 }
11188
11189 #define Q_PROMO  4
11190 #define Q_EP     3
11191 #define Q_BCASTL 2
11192 #define Q_WCASTL 1
11193
11194 int pieceList[256], quickBoard[256];
11195 ChessSquare pieceType[256] = { EmptySquare };
11196 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11197 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11198 int soughtTotal, turn;
11199 Boolean epOK, flipSearch;
11200
11201 typedef struct {
11202     unsigned char piece, to;
11203 } Move;
11204
11205 #define DSIZE (250000)
11206
11207 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11208 Move *moveDatabase = initialSpace;
11209 unsigned int movePtr, dataSize = DSIZE;
11210
11211 int
11212 MakePieceList (Board board, int *counts)
11213 {
11214     int r, f, n=Q_PROMO, total=0;
11215     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11216     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11217         int sq = f + (r<<4);
11218         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11219             quickBoard[sq] = ++n;
11220             pieceList[n] = sq;
11221             pieceType[n] = board[r][f];
11222             counts[board[r][f]]++;
11223             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11224             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11225             total++;
11226         }
11227     }
11228     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11229     return total;
11230 }
11231
11232 void
11233 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11234 {
11235     int sq = fromX + (fromY<<4);
11236     int piece = quickBoard[sq];
11237     quickBoard[sq] = 0;
11238     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11239     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11240         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11241         moveDatabase[movePtr++].piece = Q_WCASTL;
11242         quickBoard[sq] = piece;
11243         piece = quickBoard[from]; quickBoard[from] = 0;
11244         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11245     } else
11246     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11247         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11248         moveDatabase[movePtr++].piece = Q_BCASTL;
11249         quickBoard[sq] = piece;
11250         piece = quickBoard[from]; quickBoard[from] = 0;
11251         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11252     } else
11253     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11254         quickBoard[(fromY<<4)+toX] = 0;
11255         moveDatabase[movePtr].piece = Q_EP;
11256         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11257         moveDatabase[movePtr].to = sq;
11258     } else
11259     if(promoPiece != pieceType[piece]) {
11260         moveDatabase[movePtr++].piece = Q_PROMO;
11261         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11262     }
11263     moveDatabase[movePtr].piece = piece;
11264     quickBoard[sq] = piece;
11265     movePtr++;
11266 }
11267
11268 int
11269 PackGame (Board board)
11270 {
11271     Move *newSpace = NULL;
11272     moveDatabase[movePtr].piece = 0; // terminate previous game
11273     if(movePtr > dataSize) {
11274         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11275         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11276         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11277         if(newSpace) {
11278             int i;
11279             Move *p = moveDatabase, *q = newSpace;
11280             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11281             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11282             moveDatabase = newSpace;
11283         } else { // calloc failed, we must be out of memory. Too bad...
11284             dataSize = 0; // prevent calloc events for all subsequent games
11285             return 0;     // and signal this one isn't cached
11286         }
11287     }
11288     movePtr++;
11289     MakePieceList(board, counts);
11290     return movePtr;
11291 }
11292
11293 int
11294 QuickCompare (Board board, int *minCounts, int *maxCounts)
11295 {   // compare according to search mode
11296     int r, f;
11297     switch(appData.searchMode)
11298     {
11299       case 1: // exact position match
11300         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         }
11304         break;
11305       case 2: // can have extra material on empty squares
11306         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11307             if(board[r][f] == EmptySquare) continue;
11308             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11309         }
11310         break;
11311       case 3: // material with exact Pawn structure
11312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11313             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11314             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11315         } // fall through to material comparison
11316       case 4: // exact material
11317         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11318         break;
11319       case 6: // material range with given imbalance
11320         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11321         // fall through to range comparison
11322       case 5: // material range
11323         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11324     }
11325     return TRUE;
11326 }
11327
11328 int
11329 QuickScan (Board board, Move *move)
11330 {   // reconstruct game,and compare all positions in it
11331     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11332     do {
11333         int piece = move->piece;
11334         int to = move->to, from = pieceList[piece];
11335         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11336           if(!piece) return -1;
11337           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11338             piece = (++move)->piece;
11339             from = pieceList[piece];
11340             counts[pieceType[piece]]--;
11341             pieceType[piece] = (ChessSquare) move->to;
11342             counts[move->to]++;
11343           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11344             counts[pieceType[quickBoard[to]]]--;
11345             quickBoard[to] = 0; total--;
11346             move++;
11347             continue;
11348           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11349             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11350             from  = pieceList[piece]; // so this must be King
11351             quickBoard[from] = 0;
11352             quickBoard[to] = piece;
11353             pieceList[piece] = to;
11354             move++;
11355             continue;
11356           }
11357         }
11358         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11359         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11360         quickBoard[from] = 0;
11361         quickBoard[to] = piece;
11362         pieceList[piece] = to;
11363         cnt++; turn ^= 3;
11364         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11365            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11366            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11367                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11368           ) {
11369             static int lastCounts[EmptySquare+1];
11370             int i;
11371             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11372             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11373         } else stretch = 0;
11374         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11375         move++;
11376     } while(1);
11377 }
11378
11379 void
11380 InitSearch ()
11381 {
11382     int r, f;
11383     flipSearch = FALSE;
11384     CopyBoard(soughtBoard, boards[currentMove]);
11385     soughtTotal = MakePieceList(soughtBoard, maxSought);
11386     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11387     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11388     CopyBoard(reverseBoard, boards[currentMove]);
11389     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11391         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11392         reverseBoard[r][f] = piece;
11393     }
11394     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11395     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11396     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11397                  || (boards[currentMove][CASTLING][2] == NoRights || 
11398                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11399                  && (boards[currentMove][CASTLING][5] == NoRights || 
11400                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11401       ) {
11402         flipSearch = TRUE;
11403         CopyBoard(flipBoard, soughtBoard);
11404         CopyBoard(rotateBoard, reverseBoard);
11405         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11406             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11407             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11408         }
11409     }
11410     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11411     if(appData.searchMode >= 5) {
11412         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11413         MakePieceList(soughtBoard, minSought);
11414         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11415     }
11416     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11417         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11418 }
11419
11420 GameInfo dummyInfo;
11421
11422 int
11423 GameContainsPosition (FILE *f, ListGame *lg)
11424 {
11425     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11426     int fromX, fromY, toX, toY;
11427     char promoChar;
11428     static int initDone=FALSE;
11429
11430     // weed out games based on numerical tag comparison
11431     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11432     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11433     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11434     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11435     if(!initDone) {
11436         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11437         initDone = TRUE;
11438     }
11439     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11440     else CopyBoard(boards[scratch], initialPosition); // default start position
11441     if(lg->moves) {
11442         turn = btm + 1;
11443         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11444         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11445     }
11446     if(btm) plyNr++;
11447     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11448     fseek(f, lg->offset, 0);
11449     yynewfile(f);
11450     while(1) {
11451         yyboardindex = scratch;
11452         quickFlag = plyNr+1;
11453         next = Myylex();
11454         quickFlag = 0;
11455         switch(next) {
11456             case PGNTag:
11457                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11458             default:
11459                 continue;
11460
11461             case XBoardGame:
11462             case GNUChessGame:
11463                 if(plyNr) return -1; // after we have seen moves, this is for new game
11464               continue;
11465
11466             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11467             case ImpossibleMove:
11468             case WhiteWins: // game ends here with these four
11469             case BlackWins:
11470             case GameIsDrawn:
11471             case GameUnfinished:
11472                 return -1;
11473
11474             case IllegalMove:
11475                 if(appData.testLegality) return -1;
11476             case WhiteCapturesEnPassant:
11477             case BlackCapturesEnPassant:
11478             case WhitePromotion:
11479             case BlackPromotion:
11480             case WhiteNonPromotion:
11481             case BlackNonPromotion:
11482             case NormalMove:
11483             case WhiteKingSideCastle:
11484             case WhiteQueenSideCastle:
11485             case BlackKingSideCastle:
11486             case BlackQueenSideCastle:
11487             case WhiteKingSideCastleWild:
11488             case WhiteQueenSideCastleWild:
11489             case BlackKingSideCastleWild:
11490             case BlackQueenSideCastleWild:
11491             case WhiteHSideCastleFR:
11492             case WhiteASideCastleFR:
11493             case BlackHSideCastleFR:
11494             case BlackASideCastleFR:
11495                 fromX = currentMoveString[0] - AAA;
11496                 fromY = currentMoveString[1] - ONE;
11497                 toX = currentMoveString[2] - AAA;
11498                 toY = currentMoveString[3] - ONE;
11499                 promoChar = currentMoveString[4];
11500                 break;
11501             case WhiteDrop:
11502             case BlackDrop:
11503                 fromX = next == WhiteDrop ?
11504                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11505                   (int) CharToPiece(ToLower(currentMoveString[0]));
11506                 fromY = DROP_RANK;
11507                 toX = currentMoveString[2] - AAA;
11508                 toY = currentMoveString[3] - ONE;
11509                 promoChar = 0;
11510                 break;
11511         }
11512         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11513         plyNr++;
11514         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11515         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11516         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11517         if(appData.findMirror) {
11518             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11519             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11520         }
11521     }
11522 }
11523
11524 /* Load the nth game from open file f */
11525 int
11526 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11527 {
11528     ChessMove cm;
11529     char buf[MSG_SIZ];
11530     int gn = gameNumber;
11531     ListGame *lg = NULL;
11532     int numPGNTags = 0;
11533     int err, pos = -1;
11534     GameMode oldGameMode;
11535     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11536
11537     if (appData.debugMode)
11538         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11539
11540     if (gameMode == Training )
11541         SetTrainingModeOff();
11542
11543     oldGameMode = gameMode;
11544     if (gameMode != BeginningOfGame) {
11545       Reset(FALSE, TRUE);
11546     }
11547
11548     gameFileFP = f;
11549     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11550         fclose(lastLoadGameFP);
11551     }
11552
11553     if (useList) {
11554         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11555
11556         if (lg) {
11557             fseek(f, lg->offset, 0);
11558             GameListHighlight(gameNumber);
11559             pos = lg->position;
11560             gn = 1;
11561         }
11562         else {
11563             DisplayError(_("Game number out of range"), 0);
11564             return FALSE;
11565         }
11566     } else {
11567         GameListDestroy();
11568         if (fseek(f, 0, 0) == -1) {
11569             if (f == lastLoadGameFP ?
11570                 gameNumber == lastLoadGameNumber + 1 :
11571                 gameNumber == 1) {
11572                 gn = 1;
11573             } else {
11574                 DisplayError(_("Can't seek on game file"), 0);
11575                 return FALSE;
11576             }
11577         }
11578     }
11579     lastLoadGameFP = f;
11580     lastLoadGameNumber = gameNumber;
11581     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11582     lastLoadGameUseList = useList;
11583
11584     yynewfile(f);
11585
11586     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11587       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11588                 lg->gameInfo.black);
11589             DisplayTitle(buf);
11590     } else if (*title != NULLCHAR) {
11591         if (gameNumber > 1) {
11592           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11593             DisplayTitle(buf);
11594         } else {
11595             DisplayTitle(title);
11596         }
11597     }
11598
11599     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11600         gameMode = PlayFromGameFile;
11601         ModeHighlight();
11602     }
11603
11604     currentMove = forwardMostMove = backwardMostMove = 0;
11605     CopyBoard(boards[0], initialPosition);
11606     StopClocks();
11607
11608     /*
11609      * Skip the first gn-1 games in the file.
11610      * Also skip over anything that precedes an identifiable
11611      * start of game marker, to avoid being confused by
11612      * garbage at the start of the file.  Currently
11613      * recognized start of game markers are the move number "1",
11614      * the pattern "gnuchess .* game", the pattern
11615      * "^[#;%] [^ ]* game file", and a PGN tag block.
11616      * A game that starts with one of the latter two patterns
11617      * will also have a move number 1, possibly
11618      * following a position diagram.
11619      * 5-4-02: Let's try being more lenient and allowing a game to
11620      * start with an unnumbered move.  Does that break anything?
11621      */
11622     cm = lastLoadGameStart = EndOfFile;
11623     while (gn > 0) {
11624         yyboardindex = forwardMostMove;
11625         cm = (ChessMove) Myylex();
11626         switch (cm) {
11627           case EndOfFile:
11628             if (cmailMsgLoaded) {
11629                 nCmailGames = CMAIL_MAX_GAMES - gn;
11630             } else {
11631                 Reset(TRUE, TRUE);
11632                 DisplayError(_("Game not found in file"), 0);
11633             }
11634             return FALSE;
11635
11636           case GNUChessGame:
11637           case XBoardGame:
11638             gn--;
11639             lastLoadGameStart = cm;
11640             break;
11641
11642           case MoveNumberOne:
11643             switch (lastLoadGameStart) {
11644               case GNUChessGame:
11645               case XBoardGame:
11646               case PGNTag:
11647                 break;
11648               case MoveNumberOne:
11649               case EndOfFile:
11650                 gn--;           /* count this game */
11651                 lastLoadGameStart = cm;
11652                 break;
11653               default:
11654                 /* impossible */
11655                 break;
11656             }
11657             break;
11658
11659           case PGNTag:
11660             switch (lastLoadGameStart) {
11661               case GNUChessGame:
11662               case PGNTag:
11663               case MoveNumberOne:
11664               case EndOfFile:
11665                 gn--;           /* count this game */
11666                 lastLoadGameStart = cm;
11667                 break;
11668               case XBoardGame:
11669                 lastLoadGameStart = cm; /* game counted already */
11670                 break;
11671               default:
11672                 /* impossible */
11673                 break;
11674             }
11675             if (gn > 0) {
11676                 do {
11677                     yyboardindex = forwardMostMove;
11678                     cm = (ChessMove) Myylex();
11679                 } while (cm == PGNTag || cm == Comment);
11680             }
11681             break;
11682
11683           case WhiteWins:
11684           case BlackWins:
11685           case GameIsDrawn:
11686             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11687                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11688                     != CMAIL_OLD_RESULT) {
11689                     nCmailResults ++ ;
11690                     cmailResult[  CMAIL_MAX_GAMES
11691                                 - gn - 1] = CMAIL_OLD_RESULT;
11692                 }
11693             }
11694             break;
11695
11696           case NormalMove:
11697             /* Only a NormalMove can be at the start of a game
11698              * without a position diagram. */
11699             if (lastLoadGameStart == EndOfFile ) {
11700               gn--;
11701               lastLoadGameStart = MoveNumberOne;
11702             }
11703             break;
11704
11705           default:
11706             break;
11707         }
11708     }
11709
11710     if (appData.debugMode)
11711       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11712
11713     if (cm == XBoardGame) {
11714         /* Skip any header junk before position diagram and/or move 1 */
11715         for (;;) {
11716             yyboardindex = forwardMostMove;
11717             cm = (ChessMove) Myylex();
11718
11719             if (cm == EndOfFile ||
11720                 cm == GNUChessGame || cm == XBoardGame) {
11721                 /* Empty game; pretend end-of-file and handle later */
11722                 cm = EndOfFile;
11723                 break;
11724             }
11725
11726             if (cm == MoveNumberOne || cm == PositionDiagram ||
11727                 cm == PGNTag || cm == Comment)
11728               break;
11729         }
11730     } else if (cm == GNUChessGame) {
11731         if (gameInfo.event != NULL) {
11732             free(gameInfo.event);
11733         }
11734         gameInfo.event = StrSave(yy_text);
11735     }
11736
11737     startedFromSetupPosition = FALSE;
11738     while (cm == PGNTag) {
11739         if (appData.debugMode)
11740           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11741         err = ParsePGNTag(yy_text, &gameInfo);
11742         if (!err) numPGNTags++;
11743
11744         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11745         if(gameInfo.variant != oldVariant) {
11746             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11747             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11748             InitPosition(TRUE);
11749             oldVariant = gameInfo.variant;
11750             if (appData.debugMode)
11751               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11752         }
11753
11754
11755         if (gameInfo.fen != NULL) {
11756           Board initial_position;
11757           startedFromSetupPosition = TRUE;
11758           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11759             Reset(TRUE, TRUE);
11760             DisplayError(_("Bad FEN position in file"), 0);
11761             return FALSE;
11762           }
11763           CopyBoard(boards[0], initial_position);
11764           if (blackPlaysFirst) {
11765             currentMove = forwardMostMove = backwardMostMove = 1;
11766             CopyBoard(boards[1], initial_position);
11767             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11768             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11769             timeRemaining[0][1] = whiteTimeRemaining;
11770             timeRemaining[1][1] = blackTimeRemaining;
11771             if (commentList[0] != NULL) {
11772               commentList[1] = commentList[0];
11773               commentList[0] = NULL;
11774             }
11775           } else {
11776             currentMove = forwardMostMove = backwardMostMove = 0;
11777           }
11778           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11779           {   int i;
11780               initialRulePlies = FENrulePlies;
11781               for( i=0; i< nrCastlingRights; i++ )
11782                   initialRights[i] = initial_position[CASTLING][i];
11783           }
11784           yyboardindex = forwardMostMove;
11785           free(gameInfo.fen);
11786           gameInfo.fen = NULL;
11787         }
11788
11789         yyboardindex = forwardMostMove;
11790         cm = (ChessMove) Myylex();
11791
11792         /* Handle comments interspersed among the tags */
11793         while (cm == Comment) {
11794             char *p;
11795             if (appData.debugMode)
11796               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11797             p = yy_text;
11798             AppendComment(currentMove, p, FALSE);
11799             yyboardindex = forwardMostMove;
11800             cm = (ChessMove) Myylex();
11801         }
11802     }
11803
11804     /* don't rely on existence of Event tag since if game was
11805      * pasted from clipboard the Event tag may not exist
11806      */
11807     if (numPGNTags > 0){
11808         char *tags;
11809         if (gameInfo.variant == VariantNormal) {
11810           VariantClass v = StringToVariant(gameInfo.event);
11811           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11812           if(v < VariantShogi) gameInfo.variant = v;
11813         }
11814         if (!matchMode) {
11815           if( appData.autoDisplayTags ) {
11816             tags = PGNTags(&gameInfo);
11817             TagsPopUp(tags, CmailMsg());
11818             free(tags);
11819           }
11820         }
11821     } else {
11822         /* Make something up, but don't display it now */
11823         SetGameInfo();
11824         TagsPopDown();
11825     }
11826
11827     if (cm == PositionDiagram) {
11828         int i, j;
11829         char *p;
11830         Board initial_position;
11831
11832         if (appData.debugMode)
11833           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11834
11835         if (!startedFromSetupPosition) {
11836             p = yy_text;
11837             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11838               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11839                 switch (*p) {
11840                   case '{':
11841                   case '[':
11842                   case '-':
11843                   case ' ':
11844                   case '\t':
11845                   case '\n':
11846                   case '\r':
11847                     break;
11848                   default:
11849                     initial_position[i][j++] = CharToPiece(*p);
11850                     break;
11851                 }
11852             while (*p == ' ' || *p == '\t' ||
11853                    *p == '\n' || *p == '\r') p++;
11854
11855             if (strncmp(p, "black", strlen("black"))==0)
11856               blackPlaysFirst = TRUE;
11857             else
11858               blackPlaysFirst = FALSE;
11859             startedFromSetupPosition = TRUE;
11860
11861             CopyBoard(boards[0], initial_position);
11862             if (blackPlaysFirst) {
11863                 currentMove = forwardMostMove = backwardMostMove = 1;
11864                 CopyBoard(boards[1], initial_position);
11865                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11866                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11867                 timeRemaining[0][1] = whiteTimeRemaining;
11868                 timeRemaining[1][1] = blackTimeRemaining;
11869                 if (commentList[0] != NULL) {
11870                     commentList[1] = commentList[0];
11871                     commentList[0] = NULL;
11872                 }
11873             } else {
11874                 currentMove = forwardMostMove = backwardMostMove = 0;
11875             }
11876         }
11877         yyboardindex = forwardMostMove;
11878         cm = (ChessMove) Myylex();
11879     }
11880
11881     if (first.pr == NoProc) {
11882         StartChessProgram(&first);
11883     }
11884     InitChessProgram(&first, FALSE);
11885     SendToProgram("force\n", &first);
11886     if (startedFromSetupPosition) {
11887         SendBoard(&first, forwardMostMove);
11888     if (appData.debugMode) {
11889         fprintf(debugFP, "Load Game\n");
11890     }
11891         DisplayBothClocks();
11892     }
11893
11894     /* [HGM] server: flag to write setup moves in broadcast file as one */
11895     loadFlag = appData.suppressLoadMoves;
11896
11897     while (cm == Comment) {
11898         char *p;
11899         if (appData.debugMode)
11900           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11901         p = yy_text;
11902         AppendComment(currentMove, p, FALSE);
11903         yyboardindex = forwardMostMove;
11904         cm = (ChessMove) Myylex();
11905     }
11906
11907     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11908         cm == WhiteWins || cm == BlackWins ||
11909         cm == GameIsDrawn || cm == GameUnfinished) {
11910         DisplayMessage("", _("No moves in game"));
11911         if (cmailMsgLoaded) {
11912             if (appData.debugMode)
11913               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11914             ClearHighlights();
11915             flipView = FALSE;
11916         }
11917         DrawPosition(FALSE, boards[currentMove]);
11918         DisplayBothClocks();
11919         gameMode = EditGame;
11920         ModeHighlight();
11921         gameFileFP = NULL;
11922         cmailOldMove = 0;
11923         return TRUE;
11924     }
11925
11926     // [HGM] PV info: routine tests if comment empty
11927     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11928         DisplayComment(currentMove - 1, commentList[currentMove]);
11929     }
11930     if (!matchMode && appData.timeDelay != 0)
11931       DrawPosition(FALSE, boards[currentMove]);
11932
11933     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11934       programStats.ok_to_send = 1;
11935     }
11936
11937     /* if the first token after the PGN tags is a move
11938      * and not move number 1, retrieve it from the parser
11939      */
11940     if (cm != MoveNumberOne)
11941         LoadGameOneMove(cm);
11942
11943     /* load the remaining moves from the file */
11944     while (LoadGameOneMove(EndOfFile)) {
11945       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11946       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11947     }
11948
11949     /* rewind to the start of the game */
11950     currentMove = backwardMostMove;
11951
11952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11953
11954     if (oldGameMode == AnalyzeFile ||
11955         oldGameMode == AnalyzeMode) {
11956       AnalyzeFileEvent();
11957     }
11958
11959     if (!matchMode && pos >= 0) {
11960         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11961     } else
11962     if (matchMode || appData.timeDelay == 0) {
11963       ToEndEvent();
11964     } else if (appData.timeDelay > 0) {
11965       AutoPlayGameLoop();
11966     }
11967
11968     if (appData.debugMode)
11969         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11970
11971     loadFlag = 0; /* [HGM] true game starts */
11972     return TRUE;
11973 }
11974
11975 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11976 int
11977 ReloadPosition (int offset)
11978 {
11979     int positionNumber = lastLoadPositionNumber + offset;
11980     if (lastLoadPositionFP == NULL) {
11981         DisplayError(_("No position has been loaded yet"), 0);
11982         return FALSE;
11983     }
11984     if (positionNumber <= 0) {
11985         DisplayError(_("Can't back up any further"), 0);
11986         return FALSE;
11987     }
11988     return LoadPosition(lastLoadPositionFP, positionNumber,
11989                         lastLoadPositionTitle);
11990 }
11991
11992 /* Load the nth position from the given file */
11993 int
11994 LoadPositionFromFile (char *filename, int n, char *title)
11995 {
11996     FILE *f;
11997     char buf[MSG_SIZ];
11998
11999     if (strcmp(filename, "-") == 0) {
12000         return LoadPosition(stdin, n, "stdin");
12001     } else {
12002         f = fopen(filename, "rb");
12003         if (f == NULL) {
12004             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12005             DisplayError(buf, errno);
12006             return FALSE;
12007         } else {
12008             return LoadPosition(f, n, title);
12009         }
12010     }
12011 }
12012
12013 /* Load the nth position from the given open file, and close it */
12014 int
12015 LoadPosition (FILE *f, int positionNumber, char *title)
12016 {
12017     char *p, line[MSG_SIZ];
12018     Board initial_position;
12019     int i, j, fenMode, pn;
12020
12021     if (gameMode == Training )
12022         SetTrainingModeOff();
12023
12024     if (gameMode != BeginningOfGame) {
12025         Reset(FALSE, TRUE);
12026     }
12027     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12028         fclose(lastLoadPositionFP);
12029     }
12030     if (positionNumber == 0) positionNumber = 1;
12031     lastLoadPositionFP = f;
12032     lastLoadPositionNumber = positionNumber;
12033     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12034     if (first.pr == NoProc && !appData.noChessProgram) {
12035       StartChessProgram(&first);
12036       InitChessProgram(&first, FALSE);
12037     }
12038     pn = positionNumber;
12039     if (positionNumber < 0) {
12040         /* Negative position number means to seek to that byte offset */
12041         if (fseek(f, -positionNumber, 0) == -1) {
12042             DisplayError(_("Can't seek on position file"), 0);
12043             return FALSE;
12044         };
12045         pn = 1;
12046     } else {
12047         if (fseek(f, 0, 0) == -1) {
12048             if (f == lastLoadPositionFP ?
12049                 positionNumber == lastLoadPositionNumber + 1 :
12050                 positionNumber == 1) {
12051                 pn = 1;
12052             } else {
12053                 DisplayError(_("Can't seek on position file"), 0);
12054                 return FALSE;
12055             }
12056         }
12057     }
12058     /* See if this file is FEN or old-style xboard */
12059     if (fgets(line, MSG_SIZ, f) == NULL) {
12060         DisplayError(_("Position not found in file"), 0);
12061         return FALSE;
12062     }
12063     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12064     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12065
12066     if (pn >= 2) {
12067         if (fenMode || line[0] == '#') pn--;
12068         while (pn > 0) {
12069             /* skip positions before number pn */
12070             if (fgets(line, MSG_SIZ, f) == NULL) {
12071                 Reset(TRUE, TRUE);
12072                 DisplayError(_("Position not found in file"), 0);
12073                 return FALSE;
12074             }
12075             if (fenMode || line[0] == '#') pn--;
12076         }
12077     }
12078
12079     if (fenMode) {
12080         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12081             DisplayError(_("Bad FEN position in file"), 0);
12082             return FALSE;
12083         }
12084     } else {
12085         (void) fgets(line, MSG_SIZ, f);
12086         (void) fgets(line, MSG_SIZ, f);
12087
12088         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12089             (void) fgets(line, MSG_SIZ, f);
12090             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12091                 if (*p == ' ')
12092                   continue;
12093                 initial_position[i][j++] = CharToPiece(*p);
12094             }
12095         }
12096
12097         blackPlaysFirst = FALSE;
12098         if (!feof(f)) {
12099             (void) fgets(line, MSG_SIZ, f);
12100             if (strncmp(line, "black", strlen("black"))==0)
12101               blackPlaysFirst = TRUE;
12102         }
12103     }
12104     startedFromSetupPosition = TRUE;
12105
12106     CopyBoard(boards[0], initial_position);
12107     if (blackPlaysFirst) {
12108         currentMove = forwardMostMove = backwardMostMove = 1;
12109         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12110         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12111         CopyBoard(boards[1], initial_position);
12112         DisplayMessage("", _("Black to play"));
12113     } else {
12114         currentMove = forwardMostMove = backwardMostMove = 0;
12115         DisplayMessage("", _("White to play"));
12116     }
12117     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12118     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12119         SendToProgram("force\n", &first);
12120         SendBoard(&first, forwardMostMove);
12121     }
12122     if (appData.debugMode) {
12123 int i, j;
12124   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12125   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12126         fprintf(debugFP, "Load Position\n");
12127     }
12128
12129     if (positionNumber > 1) {
12130       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12131         DisplayTitle(line);
12132     } else {
12133         DisplayTitle(title);
12134     }
12135     gameMode = EditGame;
12136     ModeHighlight();
12137     ResetClocks();
12138     timeRemaining[0][1] = whiteTimeRemaining;
12139     timeRemaining[1][1] = blackTimeRemaining;
12140     DrawPosition(FALSE, boards[currentMove]);
12141
12142     return TRUE;
12143 }
12144
12145
12146 void
12147 CopyPlayerNameIntoFileName (char **dest, char *src)
12148 {
12149     while (*src != NULLCHAR && *src != ',') {
12150         if (*src == ' ') {
12151             *(*dest)++ = '_';
12152             src++;
12153         } else {
12154             *(*dest)++ = *src++;
12155         }
12156     }
12157 }
12158
12159 char *
12160 DefaultFileName (char *ext)
12161 {
12162     static char def[MSG_SIZ];
12163     char *p;
12164
12165     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12166         p = def;
12167         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12168         *p++ = '-';
12169         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12170         *p++ = '.';
12171         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12172     } else {
12173         def[0] = NULLCHAR;
12174     }
12175     return def;
12176 }
12177
12178 /* Save the current game to the given file */
12179 int
12180 SaveGameToFile (char *filename, int append)
12181 {
12182     FILE *f;
12183     char buf[MSG_SIZ];
12184     int result, i, t,tot=0;
12185
12186     if (strcmp(filename, "-") == 0) {
12187         return SaveGame(stdout, 0, NULL);
12188     } else {
12189         for(i=0; i<10; i++) { // upto 10 tries
12190              f = fopen(filename, append ? "a" : "w");
12191              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12192              if(f || errno != 13) break;
12193              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12194              tot += t;
12195         }
12196         if (f == NULL) {
12197             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12198             DisplayError(buf, errno);
12199             return FALSE;
12200         } else {
12201             safeStrCpy(buf, lastMsg, MSG_SIZ);
12202             DisplayMessage(_("Waiting for access to save file"), "");
12203             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12204             DisplayMessage(_("Saving game"), "");
12205             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12206             result = SaveGame(f, 0, NULL);
12207             DisplayMessage(buf, "");
12208             return result;
12209         }
12210     }
12211 }
12212
12213 char *
12214 SavePart (char *str)
12215 {
12216     static char buf[MSG_SIZ];
12217     char *p;
12218
12219     p = strchr(str, ' ');
12220     if (p == NULL) return str;
12221     strncpy(buf, str, p - str);
12222     buf[p - str] = NULLCHAR;
12223     return buf;
12224 }
12225
12226 #define PGN_MAX_LINE 75
12227
12228 #define PGN_SIDE_WHITE  0
12229 #define PGN_SIDE_BLACK  1
12230
12231 static int
12232 FindFirstMoveOutOfBook (int side)
12233 {
12234     int result = -1;
12235
12236     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12237         int index = backwardMostMove;
12238         int has_book_hit = 0;
12239
12240         if( (index % 2) != side ) {
12241             index++;
12242         }
12243
12244         while( index < forwardMostMove ) {
12245             /* Check to see if engine is in book */
12246             int depth = pvInfoList[index].depth;
12247             int score = pvInfoList[index].score;
12248             int in_book = 0;
12249
12250             if( depth <= 2 ) {
12251                 in_book = 1;
12252             }
12253             else if( score == 0 && depth == 63 ) {
12254                 in_book = 1; /* Zappa */
12255             }
12256             else if( score == 2 && depth == 99 ) {
12257                 in_book = 1; /* Abrok */
12258             }
12259
12260             has_book_hit += in_book;
12261
12262             if( ! in_book ) {
12263                 result = index;
12264
12265                 break;
12266             }
12267
12268             index += 2;
12269         }
12270     }
12271
12272     return result;
12273 }
12274
12275 void
12276 GetOutOfBookInfo (char * buf)
12277 {
12278     int oob[2];
12279     int i;
12280     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12281
12282     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12283     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12284
12285     *buf = '\0';
12286
12287     if( oob[0] >= 0 || oob[1] >= 0 ) {
12288         for( i=0; i<2; i++ ) {
12289             int idx = oob[i];
12290
12291             if( idx >= 0 ) {
12292                 if( i > 0 && oob[0] >= 0 ) {
12293                     strcat( buf, "   " );
12294                 }
12295
12296                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12297                 sprintf( buf+strlen(buf), "%s%.2f",
12298                     pvInfoList[idx].score >= 0 ? "+" : "",
12299                     pvInfoList[idx].score / 100.0 );
12300             }
12301         }
12302     }
12303 }
12304
12305 /* Save game in PGN style and close the file */
12306 int
12307 SaveGamePGN (FILE *f)
12308 {
12309     int i, offset, linelen, newblock;
12310     time_t tm;
12311 //    char *movetext;
12312     char numtext[32];
12313     int movelen, numlen, blank;
12314     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12315
12316     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12317
12318     tm = time((time_t *) NULL);
12319
12320     PrintPGNTags(f, &gameInfo);
12321
12322     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12323
12324     if (backwardMostMove > 0 || startedFromSetupPosition) {
12325         char *fen = PositionToFEN(backwardMostMove, NULL);
12326         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12327         fprintf(f, "\n{--------------\n");
12328         PrintPosition(f, backwardMostMove);
12329         fprintf(f, "--------------}\n");
12330         free(fen);
12331     }
12332     else {
12333         /* [AS] Out of book annotation */
12334         if( appData.saveOutOfBookInfo ) {
12335             char buf[64];
12336
12337             GetOutOfBookInfo( buf );
12338
12339             if( buf[0] != '\0' ) {
12340                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12341             }
12342         }
12343
12344         fprintf(f, "\n");
12345     }
12346
12347     i = backwardMostMove;
12348     linelen = 0;
12349     newblock = TRUE;
12350
12351     while (i < forwardMostMove) {
12352         /* Print comments preceding this move */
12353         if (commentList[i] != NULL) {
12354             if (linelen > 0) fprintf(f, "\n");
12355             fprintf(f, "%s", commentList[i]);
12356             linelen = 0;
12357             newblock = TRUE;
12358         }
12359
12360         /* Format move number */
12361         if ((i % 2) == 0)
12362           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12363         else
12364           if (newblock)
12365             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12366           else
12367             numtext[0] = NULLCHAR;
12368
12369         numlen = strlen(numtext);
12370         newblock = FALSE;
12371
12372         /* Print move number */
12373         blank = linelen > 0 && numlen > 0;
12374         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12375             fprintf(f, "\n");
12376             linelen = 0;
12377             blank = 0;
12378         }
12379         if (blank) {
12380             fprintf(f, " ");
12381             linelen++;
12382         }
12383         fprintf(f, "%s", numtext);
12384         linelen += numlen;
12385
12386         /* Get move */
12387         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12388         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12389
12390         /* Print move */
12391         blank = linelen > 0 && movelen > 0;
12392         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12393             fprintf(f, "\n");
12394             linelen = 0;
12395             blank = 0;
12396         }
12397         if (blank) {
12398             fprintf(f, " ");
12399             linelen++;
12400         }
12401         fprintf(f, "%s", move_buffer);
12402         linelen += movelen;
12403
12404         /* [AS] Add PV info if present */
12405         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12406             /* [HGM] add time */
12407             char buf[MSG_SIZ]; int seconds;
12408
12409             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12410
12411             if( seconds <= 0)
12412               buf[0] = 0;
12413             else
12414               if( seconds < 30 )
12415                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12416               else
12417                 {
12418                   seconds = (seconds + 4)/10; // round to full seconds
12419                   if( seconds < 60 )
12420                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12421                   else
12422                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12423                 }
12424
12425             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12426                       pvInfoList[i].score >= 0 ? "+" : "",
12427                       pvInfoList[i].score / 100.0,
12428                       pvInfoList[i].depth,
12429                       buf );
12430
12431             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12432
12433             /* Print score/depth */
12434             blank = linelen > 0 && movelen > 0;
12435             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12436                 fprintf(f, "\n");
12437                 linelen = 0;
12438                 blank = 0;
12439             }
12440             if (blank) {
12441                 fprintf(f, " ");
12442                 linelen++;
12443             }
12444             fprintf(f, "%s", move_buffer);
12445             linelen += movelen;
12446         }
12447
12448         i++;
12449     }
12450
12451     /* Start a new line */
12452     if (linelen > 0) fprintf(f, "\n");
12453
12454     /* Print comments after last move */
12455     if (commentList[i] != NULL) {
12456         fprintf(f, "%s\n", commentList[i]);
12457     }
12458
12459     /* Print result */
12460     if (gameInfo.resultDetails != NULL &&
12461         gameInfo.resultDetails[0] != NULLCHAR) {
12462         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12463                 PGNResult(gameInfo.result));
12464     } else {
12465         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12466     }
12467
12468     fclose(f);
12469     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12470     return TRUE;
12471 }
12472
12473 /* Save game in old style and close the file */
12474 int
12475 SaveGameOldStyle (FILE *f)
12476 {
12477     int i, offset;
12478     time_t tm;
12479
12480     tm = time((time_t *) NULL);
12481
12482     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12483     PrintOpponents(f);
12484
12485     if (backwardMostMove > 0 || startedFromSetupPosition) {
12486         fprintf(f, "\n[--------------\n");
12487         PrintPosition(f, backwardMostMove);
12488         fprintf(f, "--------------]\n");
12489     } else {
12490         fprintf(f, "\n");
12491     }
12492
12493     i = backwardMostMove;
12494     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12495
12496     while (i < forwardMostMove) {
12497         if (commentList[i] != NULL) {
12498             fprintf(f, "[%s]\n", commentList[i]);
12499         }
12500
12501         if ((i % 2) == 1) {
12502             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12503             i++;
12504         } else {
12505             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12506             i++;
12507             if (commentList[i] != NULL) {
12508                 fprintf(f, "\n");
12509                 continue;
12510             }
12511             if (i >= forwardMostMove) {
12512                 fprintf(f, "\n");
12513                 break;
12514             }
12515             fprintf(f, "%s\n", parseList[i]);
12516             i++;
12517         }
12518     }
12519
12520     if (commentList[i] != NULL) {
12521         fprintf(f, "[%s]\n", commentList[i]);
12522     }
12523
12524     /* This isn't really the old style, but it's close enough */
12525     if (gameInfo.resultDetails != NULL &&
12526         gameInfo.resultDetails[0] != NULLCHAR) {
12527         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12528                 gameInfo.resultDetails);
12529     } else {
12530         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12531     }
12532
12533     fclose(f);
12534     return TRUE;
12535 }
12536
12537 /* Save the current game to open file f and close the file */
12538 int
12539 SaveGame (FILE *f, int dummy, char *dummy2)
12540 {
12541     if (gameMode == EditPosition) EditPositionDone(TRUE);
12542     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12543     if (appData.oldSaveStyle)
12544       return SaveGameOldStyle(f);
12545     else
12546       return SaveGamePGN(f);
12547 }
12548
12549 /* Save the current position to the given file */
12550 int
12551 SavePositionToFile (char *filename)
12552 {
12553     FILE *f;
12554     char buf[MSG_SIZ];
12555
12556     if (strcmp(filename, "-") == 0) {
12557         return SavePosition(stdout, 0, NULL);
12558     } else {
12559         f = fopen(filename, "a");
12560         if (f == NULL) {
12561             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12562             DisplayError(buf, errno);
12563             return FALSE;
12564         } else {
12565             safeStrCpy(buf, lastMsg, MSG_SIZ);
12566             DisplayMessage(_("Waiting for access to save file"), "");
12567             flock(fileno(f), LOCK_EX); // [HGM] lock
12568             DisplayMessage(_("Saving position"), "");
12569             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12570             SavePosition(f, 0, NULL);
12571             DisplayMessage(buf, "");
12572             return TRUE;
12573         }
12574     }
12575 }
12576
12577 /* Save the current position to the given open file and close the file */
12578 int
12579 SavePosition (FILE *f, int dummy, char *dummy2)
12580 {
12581     time_t tm;
12582     char *fen;
12583
12584     if (gameMode == EditPosition) EditPositionDone(TRUE);
12585     if (appData.oldSaveStyle) {
12586         tm = time((time_t *) NULL);
12587
12588         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12589         PrintOpponents(f);
12590         fprintf(f, "[--------------\n");
12591         PrintPosition(f, currentMove);
12592         fprintf(f, "--------------]\n");
12593     } else {
12594         fen = PositionToFEN(currentMove, NULL);
12595         fprintf(f, "%s\n", fen);
12596         free(fen);
12597     }
12598     fclose(f);
12599     return TRUE;
12600 }
12601
12602 void
12603 ReloadCmailMsgEvent (int unregister)
12604 {
12605 #if !WIN32
12606     static char *inFilename = NULL;
12607     static char *outFilename;
12608     int i;
12609     struct stat inbuf, outbuf;
12610     int status;
12611
12612     /* Any registered moves are unregistered if unregister is set, */
12613     /* i.e. invoked by the signal handler */
12614     if (unregister) {
12615         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12616             cmailMoveRegistered[i] = FALSE;
12617             if (cmailCommentList[i] != NULL) {
12618                 free(cmailCommentList[i]);
12619                 cmailCommentList[i] = NULL;
12620             }
12621         }
12622         nCmailMovesRegistered = 0;
12623     }
12624
12625     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12626         cmailResult[i] = CMAIL_NOT_RESULT;
12627     }
12628     nCmailResults = 0;
12629
12630     if (inFilename == NULL) {
12631         /* Because the filenames are static they only get malloced once  */
12632         /* and they never get freed                                      */
12633         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12634         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12635
12636         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12637         sprintf(outFilename, "%s.out", appData.cmailGameName);
12638     }
12639
12640     status = stat(outFilename, &outbuf);
12641     if (status < 0) {
12642         cmailMailedMove = FALSE;
12643     } else {
12644         status = stat(inFilename, &inbuf);
12645         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12646     }
12647
12648     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12649        counts the games, notes how each one terminated, etc.
12650
12651        It would be nice to remove this kludge and instead gather all
12652        the information while building the game list.  (And to keep it
12653        in the game list nodes instead of having a bunch of fixed-size
12654        parallel arrays.)  Note this will require getting each game's
12655        termination from the PGN tags, as the game list builder does
12656        not process the game moves.  --mann
12657        */
12658     cmailMsgLoaded = TRUE;
12659     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12660
12661     /* Load first game in the file or popup game menu */
12662     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12663
12664 #endif /* !WIN32 */
12665     return;
12666 }
12667
12668 int
12669 RegisterMove ()
12670 {
12671     FILE *f;
12672     char string[MSG_SIZ];
12673
12674     if (   cmailMailedMove
12675         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12676         return TRUE;            /* Allow free viewing  */
12677     }
12678
12679     /* Unregister move to ensure that we don't leave RegisterMove        */
12680     /* with the move registered when the conditions for registering no   */
12681     /* longer hold                                                       */
12682     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12683         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12684         nCmailMovesRegistered --;
12685
12686         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12687           {
12688               free(cmailCommentList[lastLoadGameNumber - 1]);
12689               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12690           }
12691     }
12692
12693     if (cmailOldMove == -1) {
12694         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12695         return FALSE;
12696     }
12697
12698     if (currentMove > cmailOldMove + 1) {
12699         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12700         return FALSE;
12701     }
12702
12703     if (currentMove < cmailOldMove) {
12704         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12705         return FALSE;
12706     }
12707
12708     if (forwardMostMove > currentMove) {
12709         /* Silently truncate extra moves */
12710         TruncateGame();
12711     }
12712
12713     if (   (currentMove == cmailOldMove + 1)
12714         || (   (currentMove == cmailOldMove)
12715             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12716                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12717         if (gameInfo.result != GameUnfinished) {
12718             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12719         }
12720
12721         if (commentList[currentMove] != NULL) {
12722             cmailCommentList[lastLoadGameNumber - 1]
12723               = StrSave(commentList[currentMove]);
12724         }
12725         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12726
12727         if (appData.debugMode)
12728           fprintf(debugFP, "Saving %s for game %d\n",
12729                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12730
12731         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12732
12733         f = fopen(string, "w");
12734         if (appData.oldSaveStyle) {
12735             SaveGameOldStyle(f); /* also closes the file */
12736
12737             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12738             f = fopen(string, "w");
12739             SavePosition(f, 0, NULL); /* also closes the file */
12740         } else {
12741             fprintf(f, "{--------------\n");
12742             PrintPosition(f, currentMove);
12743             fprintf(f, "--------------}\n\n");
12744
12745             SaveGame(f, 0, NULL); /* also closes the file*/
12746         }
12747
12748         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12749         nCmailMovesRegistered ++;
12750     } else if (nCmailGames == 1) {
12751         DisplayError(_("You have not made a move yet"), 0);
12752         return FALSE;
12753     }
12754
12755     return TRUE;
12756 }
12757
12758 void
12759 MailMoveEvent ()
12760 {
12761 #if !WIN32
12762     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12763     FILE *commandOutput;
12764     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12765     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12766     int nBuffers;
12767     int i;
12768     int archived;
12769     char *arcDir;
12770
12771     if (! cmailMsgLoaded) {
12772         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12773         return;
12774     }
12775
12776     if (nCmailGames == nCmailResults) {
12777         DisplayError(_("No unfinished games"), 0);
12778         return;
12779     }
12780
12781 #if CMAIL_PROHIBIT_REMAIL
12782     if (cmailMailedMove) {
12783       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);
12784         DisplayError(msg, 0);
12785         return;
12786     }
12787 #endif
12788
12789     if (! (cmailMailedMove || RegisterMove())) return;
12790
12791     if (   cmailMailedMove
12792         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12793       snprintf(string, MSG_SIZ, partCommandString,
12794                appData.debugMode ? " -v" : "", appData.cmailGameName);
12795         commandOutput = popen(string, "r");
12796
12797         if (commandOutput == NULL) {
12798             DisplayError(_("Failed to invoke cmail"), 0);
12799         } else {
12800             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12801                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12802             }
12803             if (nBuffers > 1) {
12804                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12805                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12806                 nBytes = MSG_SIZ - 1;
12807             } else {
12808                 (void) memcpy(msg, buffer, nBytes);
12809             }
12810             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12811
12812             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12813                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12814
12815                 archived = TRUE;
12816                 for (i = 0; i < nCmailGames; i ++) {
12817                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12818                         archived = FALSE;
12819                     }
12820                 }
12821                 if (   archived
12822                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12823                         != NULL)) {
12824                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12825                            arcDir,
12826                            appData.cmailGameName,
12827                            gameInfo.date);
12828                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12829                     cmailMsgLoaded = FALSE;
12830                 }
12831             }
12832
12833             DisplayInformation(msg);
12834             pclose(commandOutput);
12835         }
12836     } else {
12837         if ((*cmailMsg) != '\0') {
12838             DisplayInformation(cmailMsg);
12839         }
12840     }
12841
12842     return;
12843 #endif /* !WIN32 */
12844 }
12845
12846 char *
12847 CmailMsg ()
12848 {
12849 #if WIN32
12850     return NULL;
12851 #else
12852     int  prependComma = 0;
12853     char number[5];
12854     char string[MSG_SIZ];       /* Space for game-list */
12855     int  i;
12856
12857     if (!cmailMsgLoaded) return "";
12858
12859     if (cmailMailedMove) {
12860       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12861     } else {
12862         /* Create a list of games left */
12863       snprintf(string, MSG_SIZ, "[");
12864         for (i = 0; i < nCmailGames; i ++) {
12865             if (! (   cmailMoveRegistered[i]
12866                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12867                 if (prependComma) {
12868                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12869                 } else {
12870                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12871                     prependComma = 1;
12872                 }
12873
12874                 strcat(string, number);
12875             }
12876         }
12877         strcat(string, "]");
12878
12879         if (nCmailMovesRegistered + nCmailResults == 0) {
12880             switch (nCmailGames) {
12881               case 1:
12882                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12883                 break;
12884
12885               case 2:
12886                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12887                 break;
12888
12889               default:
12890                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12891                          nCmailGames);
12892                 break;
12893             }
12894         } else {
12895             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12896               case 1:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12898                          string);
12899                 break;
12900
12901               case 0:
12902                 if (nCmailResults == nCmailGames) {
12903                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12904                 } else {
12905                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12906                 }
12907                 break;
12908
12909               default:
12910                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12911                          string);
12912             }
12913         }
12914     }
12915     return cmailMsg;
12916 #endif /* WIN32 */
12917 }
12918
12919 void
12920 ResetGameEvent ()
12921 {
12922     if (gameMode == Training)
12923       SetTrainingModeOff();
12924
12925     Reset(TRUE, TRUE);
12926     cmailMsgLoaded = FALSE;
12927     if (appData.icsActive) {
12928       SendToICS(ics_prefix);
12929       SendToICS("refresh\n");
12930     }
12931 }
12932
12933 void
12934 ExitEvent (int status)
12935 {
12936     exiting++;
12937     if (exiting > 2) {
12938       /* Give up on clean exit */
12939       exit(status);
12940     }
12941     if (exiting > 1) {
12942       /* Keep trying for clean exit */
12943       return;
12944     }
12945
12946     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12947
12948     if (telnetISR != NULL) {
12949       RemoveInputSource(telnetISR);
12950     }
12951     if (icsPR != NoProc) {
12952       DestroyChildProcess(icsPR, TRUE);
12953     }
12954
12955     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12956     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12957
12958     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12959     /* make sure this other one finishes before killing it!                  */
12960     if(endingGame) { int count = 0;
12961         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12962         while(endingGame && count++ < 10) DoSleep(1);
12963         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12964     }
12965
12966     /* Kill off chess programs */
12967     if (first.pr != NoProc) {
12968         ExitAnalyzeMode();
12969
12970         DoSleep( appData.delayBeforeQuit );
12971         SendToProgram("quit\n", &first);
12972         DoSleep( appData.delayAfterQuit );
12973         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12974     }
12975     if (second.pr != NoProc) {
12976         DoSleep( appData.delayBeforeQuit );
12977         SendToProgram("quit\n", &second);
12978         DoSleep( appData.delayAfterQuit );
12979         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12980     }
12981     if (first.isr != NULL) {
12982         RemoveInputSource(first.isr);
12983     }
12984     if (second.isr != NULL) {
12985         RemoveInputSource(second.isr);
12986     }
12987
12988     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12989     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12990
12991     ShutDownFrontEnd();
12992     exit(status);
12993 }
12994
12995 void
12996 PauseEvent ()
12997 {
12998     if (appData.debugMode)
12999         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13000     if (pausing) {
13001         pausing = FALSE;
13002         ModeHighlight();
13003         if (gameMode == MachinePlaysWhite ||
13004             gameMode == MachinePlaysBlack) {
13005             StartClocks();
13006         } else {
13007             DisplayBothClocks();
13008         }
13009         if (gameMode == PlayFromGameFile) {
13010             if (appData.timeDelay >= 0)
13011                 AutoPlayGameLoop();
13012         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13013             Reset(FALSE, TRUE);
13014             SendToICS(ics_prefix);
13015             SendToICS("refresh\n");
13016         } else if (currentMove < forwardMostMove) {
13017             ForwardInner(forwardMostMove);
13018         }
13019         pauseExamInvalid = FALSE;
13020     } else {
13021         switch (gameMode) {
13022           default:
13023             return;
13024           case IcsExamining:
13025             pauseExamForwardMostMove = forwardMostMove;
13026             pauseExamInvalid = FALSE;
13027             /* fall through */
13028           case IcsObserving:
13029           case IcsPlayingWhite:
13030           case IcsPlayingBlack:
13031             pausing = TRUE;
13032             ModeHighlight();
13033             return;
13034           case PlayFromGameFile:
13035             (void) StopLoadGameTimer();
13036             pausing = TRUE;
13037             ModeHighlight();
13038             break;
13039           case BeginningOfGame:
13040             if (appData.icsActive) return;
13041             /* else fall through */
13042           case MachinePlaysWhite:
13043           case MachinePlaysBlack:
13044           case TwoMachinesPlay:
13045             if (forwardMostMove == 0)
13046               return;           /* don't pause if no one has moved */
13047             if ((gameMode == MachinePlaysWhite &&
13048                  !WhiteOnMove(forwardMostMove)) ||
13049                 (gameMode == MachinePlaysBlack &&
13050                  WhiteOnMove(forwardMostMove))) {
13051                 StopClocks();
13052             }
13053             pausing = TRUE;
13054             ModeHighlight();
13055             break;
13056         }
13057     }
13058 }
13059
13060 void
13061 EditCommentEvent ()
13062 {
13063     char title[MSG_SIZ];
13064
13065     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13066       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13067     } else {
13068       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13069                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13070                parseList[currentMove - 1]);
13071     }
13072
13073     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13074 }
13075
13076
13077 void
13078 EditTagsEvent ()
13079 {
13080     char *tags = PGNTags(&gameInfo);
13081     bookUp = FALSE;
13082     EditTagsPopUp(tags, NULL);
13083     free(tags);
13084 }
13085
13086 void
13087 AnalyzeModeEvent ()
13088 {
13089     if (appData.noChessProgram || gameMode == AnalyzeMode)
13090       return;
13091
13092     if (gameMode != AnalyzeFile) {
13093         if (!appData.icsEngineAnalyze) {
13094                EditGameEvent();
13095                if (gameMode != EditGame) return;
13096         }
13097         ResurrectChessProgram();
13098         SendToProgram("analyze\n", &first);
13099         first.analyzing = TRUE;
13100         /*first.maybeThinking = TRUE;*/
13101         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13102         EngineOutputPopUp();
13103     }
13104     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13105     pausing = FALSE;
13106     ModeHighlight();
13107     SetGameInfo();
13108
13109     StartAnalysisClock();
13110     GetTimeMark(&lastNodeCountTime);
13111     lastNodeCount = 0;
13112 }
13113
13114 void
13115 AnalyzeFileEvent ()
13116 {
13117     if (appData.noChessProgram || gameMode == AnalyzeFile)
13118       return;
13119
13120     if (gameMode != AnalyzeMode) {
13121         EditGameEvent();
13122         if (gameMode != EditGame) return;
13123         ResurrectChessProgram();
13124         SendToProgram("analyze\n", &first);
13125         first.analyzing = TRUE;
13126         /*first.maybeThinking = TRUE;*/
13127         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13128         EngineOutputPopUp();
13129     }
13130     gameMode = AnalyzeFile;
13131     pausing = FALSE;
13132     ModeHighlight();
13133     SetGameInfo();
13134
13135     StartAnalysisClock();
13136     GetTimeMark(&lastNodeCountTime);
13137     lastNodeCount = 0;
13138     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13139 }
13140
13141 void
13142 MachineWhiteEvent ()
13143 {
13144     char buf[MSG_SIZ];
13145     char *bookHit = NULL;
13146
13147     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13148       return;
13149
13150
13151     if (gameMode == PlayFromGameFile ||
13152         gameMode == TwoMachinesPlay  ||
13153         gameMode == Training         ||
13154         gameMode == AnalyzeMode      ||
13155         gameMode == EndOfGame)
13156         EditGameEvent();
13157
13158     if (gameMode == EditPosition)
13159         EditPositionDone(TRUE);
13160
13161     if (!WhiteOnMove(currentMove)) {
13162         DisplayError(_("It is not White's turn"), 0);
13163         return;
13164     }
13165
13166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13167       ExitAnalyzeMode();
13168
13169     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13170         gameMode == AnalyzeFile)
13171         TruncateGame();
13172
13173     ResurrectChessProgram();    /* in case it isn't running */
13174     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13175         gameMode = MachinePlaysWhite;
13176         ResetClocks();
13177     } else
13178     gameMode = MachinePlaysWhite;
13179     pausing = FALSE;
13180     ModeHighlight();
13181     SetGameInfo();
13182     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13183     DisplayTitle(buf);
13184     if (first.sendName) {
13185       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13186       SendToProgram(buf, &first);
13187     }
13188     if (first.sendTime) {
13189       if (first.useColors) {
13190         SendToProgram("black\n", &first); /*gnu kludge*/
13191       }
13192       SendTimeRemaining(&first, TRUE);
13193     }
13194     if (first.useColors) {
13195       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13196     }
13197     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13198     SetMachineThinkingEnables();
13199     first.maybeThinking = TRUE;
13200     StartClocks();
13201     firstMove = FALSE;
13202
13203     if (appData.autoFlipView && !flipView) {
13204       flipView = !flipView;
13205       DrawPosition(FALSE, NULL);
13206       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13207     }
13208
13209     if(bookHit) { // [HGM] book: simulate book reply
13210         static char bookMove[MSG_SIZ]; // a bit generous?
13211
13212         programStats.nodes = programStats.depth = programStats.time =
13213         programStats.score = programStats.got_only_move = 0;
13214         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13215
13216         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13217         strcat(bookMove, bookHit);
13218         HandleMachineMove(bookMove, &first);
13219     }
13220 }
13221
13222 void
13223 MachineBlackEvent ()
13224 {
13225   char buf[MSG_SIZ];
13226   char *bookHit = NULL;
13227
13228     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13229         return;
13230
13231
13232     if (gameMode == PlayFromGameFile ||
13233         gameMode == TwoMachinesPlay  ||
13234         gameMode == Training         ||
13235         gameMode == AnalyzeMode      ||
13236         gameMode == EndOfGame)
13237         EditGameEvent();
13238
13239     if (gameMode == EditPosition)
13240         EditPositionDone(TRUE);
13241
13242     if (WhiteOnMove(currentMove)) {
13243         DisplayError(_("It is not Black's turn"), 0);
13244         return;
13245     }
13246
13247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13248       ExitAnalyzeMode();
13249
13250     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13251         gameMode == AnalyzeFile)
13252         TruncateGame();
13253
13254     ResurrectChessProgram();    /* in case it isn't running */
13255     gameMode = MachinePlaysBlack;
13256     pausing = FALSE;
13257     ModeHighlight();
13258     SetGameInfo();
13259     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13260     DisplayTitle(buf);
13261     if (first.sendName) {
13262       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13263       SendToProgram(buf, &first);
13264     }
13265     if (first.sendTime) {
13266       if (first.useColors) {
13267         SendToProgram("white\n", &first); /*gnu kludge*/
13268       }
13269       SendTimeRemaining(&first, FALSE);
13270     }
13271     if (first.useColors) {
13272       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13273     }
13274     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13275     SetMachineThinkingEnables();
13276     first.maybeThinking = TRUE;
13277     StartClocks();
13278
13279     if (appData.autoFlipView && flipView) {
13280       flipView = !flipView;
13281       DrawPosition(FALSE, NULL);
13282       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13283     }
13284     if(bookHit) { // [HGM] book: simulate book reply
13285         static char bookMove[MSG_SIZ]; // a bit generous?
13286
13287         programStats.nodes = programStats.depth = programStats.time =
13288         programStats.score = programStats.got_only_move = 0;
13289         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13290
13291         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13292         strcat(bookMove, bookHit);
13293         HandleMachineMove(bookMove, &first);
13294     }
13295 }
13296
13297
13298 void
13299 DisplayTwoMachinesTitle ()
13300 {
13301     char buf[MSG_SIZ];
13302     if (appData.matchGames > 0) {
13303         if(appData.tourneyFile[0]) {
13304           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13305                    gameInfo.white, _("vs."), gameInfo.black,
13306                    nextGame+1, appData.matchGames+1,
13307                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13308         } else 
13309         if (first.twoMachinesColor[0] == 'w') {
13310           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13311                    gameInfo.white, _("vs."),  gameInfo.black,
13312                    first.matchWins, second.matchWins,
13313                    matchGame - 1 - (first.matchWins + second.matchWins));
13314         } else {
13315           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13316                    gameInfo.white, _("vs."), gameInfo.black,
13317                    second.matchWins, first.matchWins,
13318                    matchGame - 1 - (first.matchWins + second.matchWins));
13319         }
13320     } else {
13321       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13322     }
13323     DisplayTitle(buf);
13324 }
13325
13326 void
13327 SettingsMenuIfReady ()
13328 {
13329   if (second.lastPing != second.lastPong) {
13330     DisplayMessage("", _("Waiting for second chess program"));
13331     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13332     return;
13333   }
13334   ThawUI();
13335   DisplayMessage("", "");
13336   SettingsPopUp(&second);
13337 }
13338
13339 int
13340 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13341 {
13342     char buf[MSG_SIZ];
13343     if (cps->pr == NoProc) {
13344         StartChessProgram(cps);
13345         if (cps->protocolVersion == 1) {
13346           retry();
13347         } else {
13348           /* kludge: allow timeout for initial "feature" command */
13349           FreezeUI();
13350           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13351           DisplayMessage("", buf);
13352           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13353         }
13354         return 1;
13355     }
13356     return 0;
13357 }
13358
13359 void
13360 TwoMachinesEvent P((void))
13361 {
13362     int i;
13363     char buf[MSG_SIZ];
13364     ChessProgramState *onmove;
13365     char *bookHit = NULL;
13366     static int stalling = 0;
13367     TimeMark now;
13368     long wait;
13369
13370     if (appData.noChessProgram) return;
13371
13372     switch (gameMode) {
13373       case TwoMachinesPlay:
13374         return;
13375       case MachinePlaysWhite:
13376       case MachinePlaysBlack:
13377         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13378             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13379             return;
13380         }
13381         /* fall through */
13382       case BeginningOfGame:
13383       case PlayFromGameFile:
13384       case EndOfGame:
13385         EditGameEvent();
13386         if (gameMode != EditGame) return;
13387         break;
13388       case EditPosition:
13389         EditPositionDone(TRUE);
13390         break;
13391       case AnalyzeMode:
13392       case AnalyzeFile:
13393         ExitAnalyzeMode();
13394         break;
13395       case EditGame:
13396       default:
13397         break;
13398     }
13399
13400 //    forwardMostMove = currentMove;
13401     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13402
13403     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13404
13405     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13406     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13407       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13408       return;
13409     }
13410     if(!stalling) {
13411       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13412       SendToProgram("force\n", &second);
13413       stalling = 1;
13414       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13415       return;
13416     }
13417     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13418     if(appData.matchPause>10000 || appData.matchPause<10)
13419                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13420     wait = SubtractTimeMarks(&now, &pauseStart);
13421     if(wait < appData.matchPause) {
13422         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13423         return;
13424     }
13425     // we are now committed to starting the game
13426     stalling = 0;
13427     DisplayMessage("", "");
13428     if (startedFromSetupPosition) {
13429         SendBoard(&second, backwardMostMove);
13430     if (appData.debugMode) {
13431         fprintf(debugFP, "Two Machines\n");
13432     }
13433     }
13434     for (i = backwardMostMove; i < forwardMostMove; i++) {
13435         SendMoveToProgram(i, &second);
13436     }
13437
13438     gameMode = TwoMachinesPlay;
13439     pausing = FALSE;
13440     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13441     SetGameInfo();
13442     DisplayTwoMachinesTitle();
13443     firstMove = TRUE;
13444     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13445         onmove = &first;
13446     } else {
13447         onmove = &second;
13448     }
13449     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13450     SendToProgram(first.computerString, &first);
13451     if (first.sendName) {
13452       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13453       SendToProgram(buf, &first);
13454     }
13455     SendToProgram(second.computerString, &second);
13456     if (second.sendName) {
13457       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13458       SendToProgram(buf, &second);
13459     }
13460
13461     ResetClocks();
13462     if (!first.sendTime || !second.sendTime) {
13463         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13464         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13465     }
13466     if (onmove->sendTime) {
13467       if (onmove->useColors) {
13468         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13469       }
13470       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13471     }
13472     if (onmove->useColors) {
13473       SendToProgram(onmove->twoMachinesColor, onmove);
13474     }
13475     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13476 //    SendToProgram("go\n", onmove);
13477     onmove->maybeThinking = TRUE;
13478     SetMachineThinkingEnables();
13479
13480     StartClocks();
13481
13482     if(bookHit) { // [HGM] book: simulate book reply
13483         static char bookMove[MSG_SIZ]; // a bit generous?
13484
13485         programStats.nodes = programStats.depth = programStats.time =
13486         programStats.score = programStats.got_only_move = 0;
13487         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13488
13489         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13490         strcat(bookMove, bookHit);
13491         savedMessage = bookMove; // args for deferred call
13492         savedState = onmove;
13493         ScheduleDelayedEvent(DeferredBookMove, 1);
13494     }
13495 }
13496
13497 void
13498 TrainingEvent ()
13499 {
13500     if (gameMode == Training) {
13501       SetTrainingModeOff();
13502       gameMode = PlayFromGameFile;
13503       DisplayMessage("", _("Training mode off"));
13504     } else {
13505       gameMode = Training;
13506       animateTraining = appData.animate;
13507
13508       /* make sure we are not already at the end of the game */
13509       if (currentMove < forwardMostMove) {
13510         SetTrainingModeOn();
13511         DisplayMessage("", _("Training mode on"));
13512       } else {
13513         gameMode = PlayFromGameFile;
13514         DisplayError(_("Already at end of game"), 0);
13515       }
13516     }
13517     ModeHighlight();
13518 }
13519
13520 void
13521 IcsClientEvent ()
13522 {
13523     if (!appData.icsActive) return;
13524     switch (gameMode) {
13525       case IcsPlayingWhite:
13526       case IcsPlayingBlack:
13527       case IcsObserving:
13528       case IcsIdle:
13529       case BeginningOfGame:
13530       case IcsExamining:
13531         return;
13532
13533       case EditGame:
13534         break;
13535
13536       case EditPosition:
13537         EditPositionDone(TRUE);
13538         break;
13539
13540       case AnalyzeMode:
13541       case AnalyzeFile:
13542         ExitAnalyzeMode();
13543         break;
13544
13545       default:
13546         EditGameEvent();
13547         break;
13548     }
13549
13550     gameMode = IcsIdle;
13551     ModeHighlight();
13552     return;
13553 }
13554
13555 void
13556 EditGameEvent ()
13557 {
13558     int i;
13559
13560     switch (gameMode) {
13561       case Training:
13562         SetTrainingModeOff();
13563         break;
13564       case MachinePlaysWhite:
13565       case MachinePlaysBlack:
13566       case BeginningOfGame:
13567         SendToProgram("force\n", &first);
13568         SetUserThinkingEnables();
13569         break;
13570       case PlayFromGameFile:
13571         (void) StopLoadGameTimer();
13572         if (gameFileFP != NULL) {
13573             gameFileFP = NULL;
13574         }
13575         break;
13576       case EditPosition:
13577         EditPositionDone(TRUE);
13578         break;
13579       case AnalyzeMode:
13580       case AnalyzeFile:
13581         ExitAnalyzeMode();
13582         SendToProgram("force\n", &first);
13583         break;
13584       case TwoMachinesPlay:
13585         GameEnds(EndOfFile, NULL, GE_PLAYER);
13586         ResurrectChessProgram();
13587         SetUserThinkingEnables();
13588         break;
13589       case EndOfGame:
13590         ResurrectChessProgram();
13591         break;
13592       case IcsPlayingBlack:
13593       case IcsPlayingWhite:
13594         DisplayError(_("Warning: You are still playing a game"), 0);
13595         break;
13596       case IcsObserving:
13597         DisplayError(_("Warning: You are still observing a game"), 0);
13598         break;
13599       case IcsExamining:
13600         DisplayError(_("Warning: You are still examining a game"), 0);
13601         break;
13602       case IcsIdle:
13603         break;
13604       case EditGame:
13605       default:
13606         return;
13607     }
13608
13609     pausing = FALSE;
13610     StopClocks();
13611     first.offeredDraw = second.offeredDraw = 0;
13612
13613     if (gameMode == PlayFromGameFile) {
13614         whiteTimeRemaining = timeRemaining[0][currentMove];
13615         blackTimeRemaining = timeRemaining[1][currentMove];
13616         DisplayTitle("");
13617     }
13618
13619     if (gameMode == MachinePlaysWhite ||
13620         gameMode == MachinePlaysBlack ||
13621         gameMode == TwoMachinesPlay ||
13622         gameMode == EndOfGame) {
13623         i = forwardMostMove;
13624         while (i > currentMove) {
13625             SendToProgram("undo\n", &first);
13626             i--;
13627         }
13628         if(!adjustedClock) {
13629         whiteTimeRemaining = timeRemaining[0][currentMove];
13630         blackTimeRemaining = timeRemaining[1][currentMove];
13631         DisplayBothClocks();
13632         }
13633         if (whiteFlag || blackFlag) {
13634             whiteFlag = blackFlag = 0;
13635         }
13636         DisplayTitle("");
13637     }
13638
13639     gameMode = EditGame;
13640     ModeHighlight();
13641     SetGameInfo();
13642 }
13643
13644
13645 void
13646 EditPositionEvent ()
13647 {
13648     if (gameMode == EditPosition) {
13649         EditGameEvent();
13650         return;
13651     }
13652
13653     EditGameEvent();
13654     if (gameMode != EditGame) return;
13655
13656     gameMode = EditPosition;
13657     ModeHighlight();
13658     SetGameInfo();
13659     if (currentMove > 0)
13660       CopyBoard(boards[0], boards[currentMove]);
13661
13662     blackPlaysFirst = !WhiteOnMove(currentMove);
13663     ResetClocks();
13664     currentMove = forwardMostMove = backwardMostMove = 0;
13665     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13666     DisplayMove(-1);
13667 }
13668
13669 void
13670 ExitAnalyzeMode ()
13671 {
13672     /* [DM] icsEngineAnalyze - possible call from other functions */
13673     if (appData.icsEngineAnalyze) {
13674         appData.icsEngineAnalyze = FALSE;
13675
13676         DisplayMessage("",_("Close ICS engine analyze..."));
13677     }
13678     if (first.analysisSupport && first.analyzing) {
13679       SendToProgram("exit\n", &first);
13680       first.analyzing = FALSE;
13681     }
13682     thinkOutput[0] = NULLCHAR;
13683 }
13684
13685 void
13686 EditPositionDone (Boolean fakeRights)
13687 {
13688     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13689
13690     startedFromSetupPosition = TRUE;
13691     InitChessProgram(&first, FALSE);
13692     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13693       boards[0][EP_STATUS] = EP_NONE;
13694       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13695     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13696         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13697         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13698       } else boards[0][CASTLING][2] = NoRights;
13699     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13700         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13701         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13702       } else boards[0][CASTLING][5] = NoRights;
13703     }
13704     SendToProgram("force\n", &first);
13705     if (blackPlaysFirst) {
13706         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13707         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13708         currentMove = forwardMostMove = backwardMostMove = 1;
13709         CopyBoard(boards[1], boards[0]);
13710     } else {
13711         currentMove = forwardMostMove = backwardMostMove = 0;
13712     }
13713     SendBoard(&first, forwardMostMove);
13714     if (appData.debugMode) {
13715         fprintf(debugFP, "EditPosDone\n");
13716     }
13717     DisplayTitle("");
13718     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13719     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13720     gameMode = EditGame;
13721     ModeHighlight();
13722     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13723     ClearHighlights(); /* [AS] */
13724 }
13725
13726 /* Pause for `ms' milliseconds */
13727 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13728 void
13729 TimeDelay (long ms)
13730 {
13731     TimeMark m1, m2;
13732
13733     GetTimeMark(&m1);
13734     do {
13735         GetTimeMark(&m2);
13736     } while (SubtractTimeMarks(&m2, &m1) < ms);
13737 }
13738
13739 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13740 void
13741 SendMultiLineToICS (char *buf)
13742 {
13743     char temp[MSG_SIZ+1], *p;
13744     int len;
13745
13746     len = strlen(buf);
13747     if (len > MSG_SIZ)
13748       len = MSG_SIZ;
13749
13750     strncpy(temp, buf, len);
13751     temp[len] = 0;
13752
13753     p = temp;
13754     while (*p) {
13755         if (*p == '\n' || *p == '\r')
13756           *p = ' ';
13757         ++p;
13758     }
13759
13760     strcat(temp, "\n");
13761     SendToICS(temp);
13762     SendToPlayer(temp, strlen(temp));
13763 }
13764
13765 void
13766 SetWhiteToPlayEvent ()
13767 {
13768     if (gameMode == EditPosition) {
13769         blackPlaysFirst = FALSE;
13770         DisplayBothClocks();    /* works because currentMove is 0 */
13771     } else if (gameMode == IcsExamining) {
13772         SendToICS(ics_prefix);
13773         SendToICS("tomove white\n");
13774     }
13775 }
13776
13777 void
13778 SetBlackToPlayEvent ()
13779 {
13780     if (gameMode == EditPosition) {
13781         blackPlaysFirst = TRUE;
13782         currentMove = 1;        /* kludge */
13783         DisplayBothClocks();
13784         currentMove = 0;
13785     } else if (gameMode == IcsExamining) {
13786         SendToICS(ics_prefix);
13787         SendToICS("tomove black\n");
13788     }
13789 }
13790
13791 void
13792 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13793 {
13794     char buf[MSG_SIZ];
13795     ChessSquare piece = boards[0][y][x];
13796
13797     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13798
13799     switch (selection) {
13800       case ClearBoard:
13801         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13802             SendToICS(ics_prefix);
13803             SendToICS("bsetup clear\n");
13804         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13805             SendToICS(ics_prefix);
13806             SendToICS("clearboard\n");
13807         } else {
13808             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13809                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13810                 for (y = 0; y < BOARD_HEIGHT; y++) {
13811                     if (gameMode == IcsExamining) {
13812                         if (boards[currentMove][y][x] != EmptySquare) {
13813                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13814                                     AAA + x, ONE + y);
13815                             SendToICS(buf);
13816                         }
13817                     } else {
13818                         boards[0][y][x] = p;
13819                     }
13820                 }
13821             }
13822         }
13823         if (gameMode == EditPosition) {
13824             DrawPosition(FALSE, boards[0]);
13825         }
13826         break;
13827
13828       case WhitePlay:
13829         SetWhiteToPlayEvent();
13830         break;
13831
13832       case BlackPlay:
13833         SetBlackToPlayEvent();
13834         break;
13835
13836       case EmptySquare:
13837         if (gameMode == IcsExamining) {
13838             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13839             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13840             SendToICS(buf);
13841         } else {
13842             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13843                 if(x == BOARD_LEFT-2) {
13844                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13845                     boards[0][y][1] = 0;
13846                 } else
13847                 if(x == BOARD_RGHT+1) {
13848                     if(y >= gameInfo.holdingsSize) break;
13849                     boards[0][y][BOARD_WIDTH-2] = 0;
13850                 } else break;
13851             }
13852             boards[0][y][x] = EmptySquare;
13853             DrawPosition(FALSE, boards[0]);
13854         }
13855         break;
13856
13857       case PromotePiece:
13858         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13859            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13860             selection = (ChessSquare) (PROMOTED piece);
13861         } else if(piece == EmptySquare) selection = WhiteSilver;
13862         else selection = (ChessSquare)((int)piece - 1);
13863         goto defaultlabel;
13864
13865       case DemotePiece:
13866         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13867            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13868             selection = (ChessSquare) (DEMOTED piece);
13869         } else if(piece == EmptySquare) selection = BlackSilver;
13870         else selection = (ChessSquare)((int)piece + 1);
13871         goto defaultlabel;
13872
13873       case WhiteQueen:
13874       case BlackQueen:
13875         if(gameInfo.variant == VariantShatranj ||
13876            gameInfo.variant == VariantXiangqi  ||
13877            gameInfo.variant == VariantCourier  ||
13878            gameInfo.variant == VariantMakruk     )
13879             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13880         goto defaultlabel;
13881
13882       case WhiteKing:
13883       case BlackKing:
13884         if(gameInfo.variant == VariantXiangqi)
13885             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13886         if(gameInfo.variant == VariantKnightmate)
13887             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13888       default:
13889         defaultlabel:
13890         if (gameMode == IcsExamining) {
13891             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13892             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13893                      PieceToChar(selection), AAA + x, ONE + y);
13894             SendToICS(buf);
13895         } else {
13896             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13897                 int n;
13898                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13899                     n = PieceToNumber(selection - BlackPawn);
13900                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13901                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13902                     boards[0][BOARD_HEIGHT-1-n][1]++;
13903                 } else
13904                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13905                     n = PieceToNumber(selection);
13906                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13907                     boards[0][n][BOARD_WIDTH-1] = selection;
13908                     boards[0][n][BOARD_WIDTH-2]++;
13909                 }
13910             } else
13911             boards[0][y][x] = selection;
13912             DrawPosition(TRUE, boards[0]);
13913             ClearHighlights();
13914             fromX = fromY = -1;
13915         }
13916         break;
13917     }
13918 }
13919
13920
13921 void
13922 DropMenuEvent (ChessSquare selection, int x, int y)
13923 {
13924     ChessMove moveType;
13925
13926     switch (gameMode) {
13927       case IcsPlayingWhite:
13928       case MachinePlaysBlack:
13929         if (!WhiteOnMove(currentMove)) {
13930             DisplayMoveError(_("It is Black's turn"));
13931             return;
13932         }
13933         moveType = WhiteDrop;
13934         break;
13935       case IcsPlayingBlack:
13936       case MachinePlaysWhite:
13937         if (WhiteOnMove(currentMove)) {
13938             DisplayMoveError(_("It is White's turn"));
13939             return;
13940         }
13941         moveType = BlackDrop;
13942         break;
13943       case EditGame:
13944         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13945         break;
13946       default:
13947         return;
13948     }
13949
13950     if (moveType == BlackDrop && selection < BlackPawn) {
13951       selection = (ChessSquare) ((int) selection
13952                                  + (int) BlackPawn - (int) WhitePawn);
13953     }
13954     if (boards[currentMove][y][x] != EmptySquare) {
13955         DisplayMoveError(_("That square is occupied"));
13956         return;
13957     }
13958
13959     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13960 }
13961
13962 void
13963 AcceptEvent ()
13964 {
13965     /* Accept a pending offer of any kind from opponent */
13966
13967     if (appData.icsActive) {
13968         SendToICS(ics_prefix);
13969         SendToICS("accept\n");
13970     } else if (cmailMsgLoaded) {
13971         if (currentMove == cmailOldMove &&
13972             commentList[cmailOldMove] != NULL &&
13973             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13974                    "Black offers a draw" : "White offers a draw")) {
13975             TruncateGame();
13976             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13977             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13978         } else {
13979             DisplayError(_("There is no pending offer on this move"), 0);
13980             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13981         }
13982     } else {
13983         /* Not used for offers from chess program */
13984     }
13985 }
13986
13987 void
13988 DeclineEvent ()
13989 {
13990     /* Decline a pending offer of any kind from opponent */
13991
13992     if (appData.icsActive) {
13993         SendToICS(ics_prefix);
13994         SendToICS("decline\n");
13995     } else if (cmailMsgLoaded) {
13996         if (currentMove == cmailOldMove &&
13997             commentList[cmailOldMove] != NULL &&
13998             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13999                    "Black offers a draw" : "White offers a draw")) {
14000 #ifdef NOTDEF
14001             AppendComment(cmailOldMove, "Draw declined", TRUE);
14002             DisplayComment(cmailOldMove - 1, "Draw declined");
14003 #endif /*NOTDEF*/
14004         } else {
14005             DisplayError(_("There is no pending offer on this move"), 0);
14006         }
14007     } else {
14008         /* Not used for offers from chess program */
14009     }
14010 }
14011
14012 void
14013 RematchEvent ()
14014 {
14015     /* Issue ICS rematch command */
14016     if (appData.icsActive) {
14017         SendToICS(ics_prefix);
14018         SendToICS("rematch\n");
14019     }
14020 }
14021
14022 void
14023 CallFlagEvent ()
14024 {
14025     /* Call your opponent's flag (claim a win on time) */
14026     if (appData.icsActive) {
14027         SendToICS(ics_prefix);
14028         SendToICS("flag\n");
14029     } else {
14030         switch (gameMode) {
14031           default:
14032             return;
14033           case MachinePlaysWhite:
14034             if (whiteFlag) {
14035                 if (blackFlag)
14036                   GameEnds(GameIsDrawn, "Both players ran out of time",
14037                            GE_PLAYER);
14038                 else
14039                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14040             } else {
14041                 DisplayError(_("Your opponent is not out of time"), 0);
14042             }
14043             break;
14044           case MachinePlaysBlack:
14045             if (blackFlag) {
14046                 if (whiteFlag)
14047                   GameEnds(GameIsDrawn, "Both players ran out of time",
14048                            GE_PLAYER);
14049                 else
14050                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14051             } else {
14052                 DisplayError(_("Your opponent is not out of time"), 0);
14053             }
14054             break;
14055         }
14056     }
14057 }
14058
14059 void
14060 ClockClick (int which)
14061 {       // [HGM] code moved to back-end from winboard.c
14062         if(which) { // black clock
14063           if (gameMode == EditPosition || gameMode == IcsExamining) {
14064             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14065             SetBlackToPlayEvent();
14066           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14067           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14068           } else if (shiftKey) {
14069             AdjustClock(which, -1);
14070           } else if (gameMode == IcsPlayingWhite ||
14071                      gameMode == MachinePlaysBlack) {
14072             CallFlagEvent();
14073           }
14074         } else { // white clock
14075           if (gameMode == EditPosition || gameMode == IcsExamining) {
14076             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14077             SetWhiteToPlayEvent();
14078           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14079           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14080           } else if (shiftKey) {
14081             AdjustClock(which, -1);
14082           } else if (gameMode == IcsPlayingBlack ||
14083                    gameMode == MachinePlaysWhite) {
14084             CallFlagEvent();
14085           }
14086         }
14087 }
14088
14089 void
14090 DrawEvent ()
14091 {
14092     /* Offer draw or accept pending draw offer from opponent */
14093
14094     if (appData.icsActive) {
14095         /* Note: tournament rules require draw offers to be
14096            made after you make your move but before you punch
14097            your clock.  Currently ICS doesn't let you do that;
14098            instead, you immediately punch your clock after making
14099            a move, but you can offer a draw at any time. */
14100
14101         SendToICS(ics_prefix);
14102         SendToICS("draw\n");
14103         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14104     } else if (cmailMsgLoaded) {
14105         if (currentMove == cmailOldMove &&
14106             commentList[cmailOldMove] != NULL &&
14107             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14108                    "Black offers a draw" : "White offers a draw")) {
14109             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14110             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14111         } else if (currentMove == cmailOldMove + 1) {
14112             char *offer = WhiteOnMove(cmailOldMove) ?
14113               "White offers a draw" : "Black offers a draw";
14114             AppendComment(currentMove, offer, TRUE);
14115             DisplayComment(currentMove - 1, offer);
14116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14117         } else {
14118             DisplayError(_("You must make your move before offering a draw"), 0);
14119             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14120         }
14121     } else if (first.offeredDraw) {
14122         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14123     } else {
14124         if (first.sendDrawOffers) {
14125             SendToProgram("draw\n", &first);
14126             userOfferedDraw = TRUE;
14127         }
14128     }
14129 }
14130
14131 void
14132 AdjournEvent ()
14133 {
14134     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14135
14136     if (appData.icsActive) {
14137         SendToICS(ics_prefix);
14138         SendToICS("adjourn\n");
14139     } else {
14140         /* Currently GNU Chess doesn't offer or accept Adjourns */
14141     }
14142 }
14143
14144
14145 void
14146 AbortEvent ()
14147 {
14148     /* Offer Abort or accept pending Abort offer from opponent */
14149
14150     if (appData.icsActive) {
14151         SendToICS(ics_prefix);
14152         SendToICS("abort\n");
14153     } else {
14154         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14155     }
14156 }
14157
14158 void
14159 ResignEvent ()
14160 {
14161     /* Resign.  You can do this even if it's not your turn. */
14162
14163     if (appData.icsActive) {
14164         SendToICS(ics_prefix);
14165         SendToICS("resign\n");
14166     } else {
14167         switch (gameMode) {
14168           case MachinePlaysWhite:
14169             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14170             break;
14171           case MachinePlaysBlack:
14172             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14173             break;
14174           case EditGame:
14175             if (cmailMsgLoaded) {
14176                 TruncateGame();
14177                 if (WhiteOnMove(cmailOldMove)) {
14178                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14179                 } else {
14180                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14181                 }
14182                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14183             }
14184             break;
14185           default:
14186             break;
14187         }
14188     }
14189 }
14190
14191
14192 void
14193 StopObservingEvent ()
14194 {
14195     /* Stop observing current games */
14196     SendToICS(ics_prefix);
14197     SendToICS("unobserve\n");
14198 }
14199
14200 void
14201 StopExaminingEvent ()
14202 {
14203     /* Stop observing current game */
14204     SendToICS(ics_prefix);
14205     SendToICS("unexamine\n");
14206 }
14207
14208 void
14209 ForwardInner (int target)
14210 {
14211     int limit; int oldSeekGraphUp = seekGraphUp;
14212
14213     if (appData.debugMode)
14214         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14215                 target, currentMove, forwardMostMove);
14216
14217     if (gameMode == EditPosition)
14218       return;
14219
14220     seekGraphUp = FALSE;
14221     MarkTargetSquares(1);
14222
14223     if (gameMode == PlayFromGameFile && !pausing)
14224       PauseEvent();
14225
14226     if (gameMode == IcsExamining && pausing)
14227       limit = pauseExamForwardMostMove;
14228     else
14229       limit = forwardMostMove;
14230
14231     if (target > limit) target = limit;
14232
14233     if (target > 0 && moveList[target - 1][0]) {
14234         int fromX, fromY, toX, toY;
14235         toX = moveList[target - 1][2] - AAA;
14236         toY = moveList[target - 1][3] - ONE;
14237         if (moveList[target - 1][1] == '@') {
14238             if (appData.highlightLastMove) {
14239                 SetHighlights(-1, -1, toX, toY);
14240             }
14241         } else {
14242             fromX = moveList[target - 1][0] - AAA;
14243             fromY = moveList[target - 1][1] - ONE;
14244             if (target == currentMove + 1) {
14245                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14246             }
14247             if (appData.highlightLastMove) {
14248                 SetHighlights(fromX, fromY, toX, toY);
14249             }
14250         }
14251     }
14252     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14253         gameMode == Training || gameMode == PlayFromGameFile ||
14254         gameMode == AnalyzeFile) {
14255         while (currentMove < target) {
14256             SendMoveToProgram(currentMove++, &first);
14257         }
14258     } else {
14259         currentMove = target;
14260     }
14261
14262     if (gameMode == EditGame || gameMode == EndOfGame) {
14263         whiteTimeRemaining = timeRemaining[0][currentMove];
14264         blackTimeRemaining = timeRemaining[1][currentMove];
14265     }
14266     DisplayBothClocks();
14267     DisplayMove(currentMove - 1);
14268     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14269     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14270     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14271         DisplayComment(currentMove - 1, commentList[currentMove]);
14272     }
14273 }
14274
14275
14276 void
14277 ForwardEvent ()
14278 {
14279     if (gameMode == IcsExamining && !pausing) {
14280         SendToICS(ics_prefix);
14281         SendToICS("forward\n");
14282     } else {
14283         ForwardInner(currentMove + 1);
14284     }
14285 }
14286
14287 void
14288 ToEndEvent ()
14289 {
14290     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14291         /* to optimze, we temporarily turn off analysis mode while we feed
14292          * the remaining moves to the engine. Otherwise we get analysis output
14293          * after each move.
14294          */
14295         if (first.analysisSupport) {
14296           SendToProgram("exit\nforce\n", &first);
14297           first.analyzing = FALSE;
14298         }
14299     }
14300
14301     if (gameMode == IcsExamining && !pausing) {
14302         SendToICS(ics_prefix);
14303         SendToICS("forward 999999\n");
14304     } else {
14305         ForwardInner(forwardMostMove);
14306     }
14307
14308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14309         /* we have fed all the moves, so reactivate analysis mode */
14310         SendToProgram("analyze\n", &first);
14311         first.analyzing = TRUE;
14312         /*first.maybeThinking = TRUE;*/
14313         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14314     }
14315 }
14316
14317 void
14318 BackwardInner (int target)
14319 {
14320     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14321
14322     if (appData.debugMode)
14323         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14324                 target, currentMove, forwardMostMove);
14325
14326     if (gameMode == EditPosition) return;
14327     seekGraphUp = FALSE;
14328     MarkTargetSquares(1);
14329     if (currentMove <= backwardMostMove) {
14330         ClearHighlights();
14331         DrawPosition(full_redraw, boards[currentMove]);
14332         return;
14333     }
14334     if (gameMode == PlayFromGameFile && !pausing)
14335       PauseEvent();
14336
14337     if (moveList[target][0]) {
14338         int fromX, fromY, toX, toY;
14339         toX = moveList[target][2] - AAA;
14340         toY = moveList[target][3] - ONE;
14341         if (moveList[target][1] == '@') {
14342             if (appData.highlightLastMove) {
14343                 SetHighlights(-1, -1, toX, toY);
14344             }
14345         } else {
14346             fromX = moveList[target][0] - AAA;
14347             fromY = moveList[target][1] - ONE;
14348             if (target == currentMove - 1) {
14349                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14350             }
14351             if (appData.highlightLastMove) {
14352                 SetHighlights(fromX, fromY, toX, toY);
14353             }
14354         }
14355     }
14356     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14357         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14358         while (currentMove > target) {
14359             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14360                 // null move cannot be undone. Reload program with move history before it.
14361                 int i;
14362                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14363                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14364                 }
14365                 SendBoard(&first, i); 
14366                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14367                 break;
14368             }
14369             SendToProgram("undo\n", &first);
14370             currentMove--;
14371         }
14372     } else {
14373         currentMove = target;
14374     }
14375
14376     if (gameMode == EditGame || gameMode == EndOfGame) {
14377         whiteTimeRemaining = timeRemaining[0][currentMove];
14378         blackTimeRemaining = timeRemaining[1][currentMove];
14379     }
14380     DisplayBothClocks();
14381     DisplayMove(currentMove - 1);
14382     DrawPosition(full_redraw, boards[currentMove]);
14383     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14384     // [HGM] PV info: routine tests if comment empty
14385     DisplayComment(currentMove - 1, commentList[currentMove]);
14386 }
14387
14388 void
14389 BackwardEvent ()
14390 {
14391     if (gameMode == IcsExamining && !pausing) {
14392         SendToICS(ics_prefix);
14393         SendToICS("backward\n");
14394     } else {
14395         BackwardInner(currentMove - 1);
14396     }
14397 }
14398
14399 void
14400 ToStartEvent ()
14401 {
14402     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14403         /* to optimize, we temporarily turn off analysis mode while we undo
14404          * all the moves. Otherwise we get analysis output after each undo.
14405          */
14406         if (first.analysisSupport) {
14407           SendToProgram("exit\nforce\n", &first);
14408           first.analyzing = FALSE;
14409         }
14410     }
14411
14412     if (gameMode == IcsExamining && !pausing) {
14413         SendToICS(ics_prefix);
14414         SendToICS("backward 999999\n");
14415     } else {
14416         BackwardInner(backwardMostMove);
14417     }
14418
14419     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14420         /* we have fed all the moves, so reactivate analysis mode */
14421         SendToProgram("analyze\n", &first);
14422         first.analyzing = TRUE;
14423         /*first.maybeThinking = TRUE;*/
14424         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14425     }
14426 }
14427
14428 void
14429 ToNrEvent (int to)
14430 {
14431   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14432   if (to >= forwardMostMove) to = forwardMostMove;
14433   if (to <= backwardMostMove) to = backwardMostMove;
14434   if (to < currentMove) {
14435     BackwardInner(to);
14436   } else {
14437     ForwardInner(to);
14438   }
14439 }
14440
14441 void
14442 RevertEvent (Boolean annotate)
14443 {
14444     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14445         return;
14446     }
14447     if (gameMode != IcsExamining) {
14448         DisplayError(_("You are not examining a game"), 0);
14449         return;
14450     }
14451     if (pausing) {
14452         DisplayError(_("You can't revert while pausing"), 0);
14453         return;
14454     }
14455     SendToICS(ics_prefix);
14456     SendToICS("revert\n");
14457 }
14458
14459 void
14460 RetractMoveEvent ()
14461 {
14462     switch (gameMode) {
14463       case MachinePlaysWhite:
14464       case MachinePlaysBlack:
14465         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14466             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14467             return;
14468         }
14469         if (forwardMostMove < 2) return;
14470         currentMove = forwardMostMove = forwardMostMove - 2;
14471         whiteTimeRemaining = timeRemaining[0][currentMove];
14472         blackTimeRemaining = timeRemaining[1][currentMove];
14473         DisplayBothClocks();
14474         DisplayMove(currentMove - 1);
14475         ClearHighlights();/*!! could figure this out*/
14476         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14477         SendToProgram("remove\n", &first);
14478         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14479         break;
14480
14481       case BeginningOfGame:
14482       default:
14483         break;
14484
14485       case IcsPlayingWhite:
14486       case IcsPlayingBlack:
14487         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14488             SendToICS(ics_prefix);
14489             SendToICS("takeback 2\n");
14490         } else {
14491             SendToICS(ics_prefix);
14492             SendToICS("takeback 1\n");
14493         }
14494         break;
14495     }
14496 }
14497
14498 void
14499 MoveNowEvent ()
14500 {
14501     ChessProgramState *cps;
14502
14503     switch (gameMode) {
14504       case MachinePlaysWhite:
14505         if (!WhiteOnMove(forwardMostMove)) {
14506             DisplayError(_("It is your turn"), 0);
14507             return;
14508         }
14509         cps = &first;
14510         break;
14511       case MachinePlaysBlack:
14512         if (WhiteOnMove(forwardMostMove)) {
14513             DisplayError(_("It is your turn"), 0);
14514             return;
14515         }
14516         cps = &first;
14517         break;
14518       case TwoMachinesPlay:
14519         if (WhiteOnMove(forwardMostMove) ==
14520             (first.twoMachinesColor[0] == 'w')) {
14521             cps = &first;
14522         } else {
14523             cps = &second;
14524         }
14525         break;
14526       case BeginningOfGame:
14527       default:
14528         return;
14529     }
14530     SendToProgram("?\n", cps);
14531 }
14532
14533 void
14534 TruncateGameEvent ()
14535 {
14536     EditGameEvent();
14537     if (gameMode != EditGame) return;
14538     TruncateGame();
14539 }
14540
14541 void
14542 TruncateGame ()
14543 {
14544     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14545     if (forwardMostMove > currentMove) {
14546         if (gameInfo.resultDetails != NULL) {
14547             free(gameInfo.resultDetails);
14548             gameInfo.resultDetails = NULL;
14549             gameInfo.result = GameUnfinished;
14550         }
14551         forwardMostMove = currentMove;
14552         HistorySet(parseList, backwardMostMove, forwardMostMove,
14553                    currentMove-1);
14554     }
14555 }
14556
14557 void
14558 HintEvent ()
14559 {
14560     if (appData.noChessProgram) return;
14561     switch (gameMode) {
14562       case MachinePlaysWhite:
14563         if (WhiteOnMove(forwardMostMove)) {
14564             DisplayError(_("Wait until your turn"), 0);
14565             return;
14566         }
14567         break;
14568       case BeginningOfGame:
14569       case MachinePlaysBlack:
14570         if (!WhiteOnMove(forwardMostMove)) {
14571             DisplayError(_("Wait until your turn"), 0);
14572             return;
14573         }
14574         break;
14575       default:
14576         DisplayError(_("No hint available"), 0);
14577         return;
14578     }
14579     SendToProgram("hint\n", &first);
14580     hintRequested = TRUE;
14581 }
14582
14583 void
14584 BookEvent ()
14585 {
14586     if (appData.noChessProgram) return;
14587     switch (gameMode) {
14588       case MachinePlaysWhite:
14589         if (WhiteOnMove(forwardMostMove)) {
14590             DisplayError(_("Wait until your turn"), 0);
14591             return;
14592         }
14593         break;
14594       case BeginningOfGame:
14595       case MachinePlaysBlack:
14596         if (!WhiteOnMove(forwardMostMove)) {
14597             DisplayError(_("Wait until your turn"), 0);
14598             return;
14599         }
14600         break;
14601       case EditPosition:
14602         EditPositionDone(TRUE);
14603         break;
14604       case TwoMachinesPlay:
14605         return;
14606       default:
14607         break;
14608     }
14609     SendToProgram("bk\n", &first);
14610     bookOutput[0] = NULLCHAR;
14611     bookRequested = TRUE;
14612 }
14613
14614 void
14615 AboutGameEvent ()
14616 {
14617     char *tags = PGNTags(&gameInfo);
14618     TagsPopUp(tags, CmailMsg());
14619     free(tags);
14620 }
14621
14622 /* end button procedures */
14623
14624 void
14625 PrintPosition (FILE *fp, int move)
14626 {
14627     int i, j;
14628
14629     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14630         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14631             char c = PieceToChar(boards[move][i][j]);
14632             fputc(c == 'x' ? '.' : c, fp);
14633             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14634         }
14635     }
14636     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14637       fprintf(fp, "white to play\n");
14638     else
14639       fprintf(fp, "black to play\n");
14640 }
14641
14642 void
14643 PrintOpponents (FILE *fp)
14644 {
14645     if (gameInfo.white != NULL) {
14646         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14647     } else {
14648         fprintf(fp, "\n");
14649     }
14650 }
14651
14652 /* Find last component of program's own name, using some heuristics */
14653 void
14654 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14655 {
14656     char *p, *q, c;
14657     int local = (strcmp(host, "localhost") == 0);
14658     while (!local && (p = strchr(prog, ';')) != NULL) {
14659         p++;
14660         while (*p == ' ') p++;
14661         prog = p;
14662     }
14663     if (*prog == '"' || *prog == '\'') {
14664         q = strchr(prog + 1, *prog);
14665     } else {
14666         q = strchr(prog, ' ');
14667     }
14668     if (q == NULL) q = prog + strlen(prog);
14669     p = q;
14670     while (p >= prog && *p != '/' && *p != '\\') p--;
14671     p++;
14672     if(p == prog && *p == '"') p++;
14673     c = *q; *q = 0;
14674     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14675     memcpy(buf, p, q - p);
14676     buf[q - p] = NULLCHAR;
14677     if (!local) {
14678         strcat(buf, "@");
14679         strcat(buf, host);
14680     }
14681 }
14682
14683 char *
14684 TimeControlTagValue ()
14685 {
14686     char buf[MSG_SIZ];
14687     if (!appData.clockMode) {
14688       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14689     } else if (movesPerSession > 0) {
14690       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14691     } else if (timeIncrement == 0) {
14692       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14693     } else {
14694       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14695     }
14696     return StrSave(buf);
14697 }
14698
14699 void
14700 SetGameInfo ()
14701 {
14702     /* This routine is used only for certain modes */
14703     VariantClass v = gameInfo.variant;
14704     ChessMove r = GameUnfinished;
14705     char *p = NULL;
14706
14707     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14708         r = gameInfo.result;
14709         p = gameInfo.resultDetails;
14710         gameInfo.resultDetails = NULL;
14711     }
14712     ClearGameInfo(&gameInfo);
14713     gameInfo.variant = v;
14714
14715     switch (gameMode) {
14716       case MachinePlaysWhite:
14717         gameInfo.event = StrSave( appData.pgnEventHeader );
14718         gameInfo.site = StrSave(HostName());
14719         gameInfo.date = PGNDate();
14720         gameInfo.round = StrSave("-");
14721         gameInfo.white = StrSave(first.tidy);
14722         gameInfo.black = StrSave(UserName());
14723         gameInfo.timeControl = TimeControlTagValue();
14724         break;
14725
14726       case MachinePlaysBlack:
14727         gameInfo.event = StrSave( appData.pgnEventHeader );
14728         gameInfo.site = StrSave(HostName());
14729         gameInfo.date = PGNDate();
14730         gameInfo.round = StrSave("-");
14731         gameInfo.white = StrSave(UserName());
14732         gameInfo.black = StrSave(first.tidy);
14733         gameInfo.timeControl = TimeControlTagValue();
14734         break;
14735
14736       case TwoMachinesPlay:
14737         gameInfo.event = StrSave( appData.pgnEventHeader );
14738         gameInfo.site = StrSave(HostName());
14739         gameInfo.date = PGNDate();
14740         if (roundNr > 0) {
14741             char buf[MSG_SIZ];
14742             snprintf(buf, MSG_SIZ, "%d", roundNr);
14743             gameInfo.round = StrSave(buf);
14744         } else {
14745             gameInfo.round = StrSave("-");
14746         }
14747         if (first.twoMachinesColor[0] == 'w') {
14748             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14749             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14750         } else {
14751             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14752             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14753         }
14754         gameInfo.timeControl = TimeControlTagValue();
14755         break;
14756
14757       case EditGame:
14758         gameInfo.event = StrSave("Edited game");
14759         gameInfo.site = StrSave(HostName());
14760         gameInfo.date = PGNDate();
14761         gameInfo.round = StrSave("-");
14762         gameInfo.white = StrSave("-");
14763         gameInfo.black = StrSave("-");
14764         gameInfo.result = r;
14765         gameInfo.resultDetails = p;
14766         break;
14767
14768       case EditPosition:
14769         gameInfo.event = StrSave("Edited position");
14770         gameInfo.site = StrSave(HostName());
14771         gameInfo.date = PGNDate();
14772         gameInfo.round = StrSave("-");
14773         gameInfo.white = StrSave("-");
14774         gameInfo.black = StrSave("-");
14775         break;
14776
14777       case IcsPlayingWhite:
14778       case IcsPlayingBlack:
14779       case IcsObserving:
14780       case IcsExamining:
14781         break;
14782
14783       case PlayFromGameFile:
14784         gameInfo.event = StrSave("Game from non-PGN file");
14785         gameInfo.site = StrSave(HostName());
14786         gameInfo.date = PGNDate();
14787         gameInfo.round = StrSave("-");
14788         gameInfo.white = StrSave("?");
14789         gameInfo.black = StrSave("?");
14790         break;
14791
14792       default:
14793         break;
14794     }
14795 }
14796
14797 void
14798 ReplaceComment (int index, char *text)
14799 {
14800     int len;
14801     char *p;
14802     float score;
14803
14804     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14805        pvInfoList[index-1].depth == len &&
14806        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14807        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14808     while (*text == '\n') text++;
14809     len = strlen(text);
14810     while (len > 0 && text[len - 1] == '\n') len--;
14811
14812     if (commentList[index] != NULL)
14813       free(commentList[index]);
14814
14815     if (len == 0) {
14816         commentList[index] = NULL;
14817         return;
14818     }
14819   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14820       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14821       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14822     commentList[index] = (char *) malloc(len + 2);
14823     strncpy(commentList[index], text, len);
14824     commentList[index][len] = '\n';
14825     commentList[index][len + 1] = NULLCHAR;
14826   } else {
14827     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14828     char *p;
14829     commentList[index] = (char *) malloc(len + 7);
14830     safeStrCpy(commentList[index], "{\n", 3);
14831     safeStrCpy(commentList[index]+2, text, len+1);
14832     commentList[index][len+2] = NULLCHAR;
14833     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14834     strcat(commentList[index], "\n}\n");
14835   }
14836 }
14837
14838 void
14839 CrushCRs (char *text)
14840 {
14841   char *p = text;
14842   char *q = text;
14843   char ch;
14844
14845   do {
14846     ch = *p++;
14847     if (ch == '\r') continue;
14848     *q++ = ch;
14849   } while (ch != '\0');
14850 }
14851
14852 void
14853 AppendComment (int index, char *text, Boolean addBraces)
14854 /* addBraces  tells if we should add {} */
14855 {
14856     int oldlen, len;
14857     char *old;
14858
14859 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14860     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14861
14862     CrushCRs(text);
14863     while (*text == '\n') text++;
14864     len = strlen(text);
14865     while (len > 0 && text[len - 1] == '\n') len--;
14866     text[len] = NULLCHAR;
14867
14868     if (len == 0) return;
14869
14870     if (commentList[index] != NULL) {
14871       Boolean addClosingBrace = addBraces;
14872         old = commentList[index];
14873         oldlen = strlen(old);
14874         while(commentList[index][oldlen-1] ==  '\n')
14875           commentList[index][--oldlen] = NULLCHAR;
14876         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14877         safeStrCpy(commentList[index], old, oldlen + len + 6);
14878         free(old);
14879         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14880         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14881           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14882           while (*text == '\n') { text++; len--; }
14883           commentList[index][--oldlen] = NULLCHAR;
14884       }
14885         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14886         else          strcat(commentList[index], "\n");
14887         strcat(commentList[index], text);
14888         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14889         else          strcat(commentList[index], "\n");
14890     } else {
14891         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14892         if(addBraces)
14893           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14894         else commentList[index][0] = NULLCHAR;
14895         strcat(commentList[index], text);
14896         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14897         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14898     }
14899 }
14900
14901 static char *
14902 FindStr (char * text, char * sub_text)
14903 {
14904     char * result = strstr( text, sub_text );
14905
14906     if( result != NULL ) {
14907         result += strlen( sub_text );
14908     }
14909
14910     return result;
14911 }
14912
14913 /* [AS] Try to extract PV info from PGN comment */
14914 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14915 char *
14916 GetInfoFromComment (int index, char * text)
14917 {
14918     char * sep = text, *p;
14919
14920     if( text != NULL && index > 0 ) {
14921         int score = 0;
14922         int depth = 0;
14923         int time = -1, sec = 0, deci;
14924         char * s_eval = FindStr( text, "[%eval " );
14925         char * s_emt = FindStr( text, "[%emt " );
14926
14927         if( s_eval != NULL || s_emt != NULL ) {
14928             /* New style */
14929             char delim;
14930
14931             if( s_eval != NULL ) {
14932                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14933                     return text;
14934                 }
14935
14936                 if( delim != ']' ) {
14937                     return text;
14938                 }
14939             }
14940
14941             if( s_emt != NULL ) {
14942             }
14943                 return text;
14944         }
14945         else {
14946             /* We expect something like: [+|-]nnn.nn/dd */
14947             int score_lo = 0;
14948
14949             if(*text != '{') return text; // [HGM] braces: must be normal comment
14950
14951             sep = strchr( text, '/' );
14952             if( sep == NULL || sep < (text+4) ) {
14953                 return text;
14954             }
14955
14956             p = text;
14957             if(p[1] == '(') { // comment starts with PV
14958                p = strchr(p, ')'); // locate end of PV
14959                if(p == NULL || sep < p+5) return text;
14960                // at this point we have something like "{(.*) +0.23/6 ..."
14961                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14962                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14963                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14964             }
14965             time = -1; sec = -1; deci = -1;
14966             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14967                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14968                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14969                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14970                 return text;
14971             }
14972
14973             if( score_lo < 0 || score_lo >= 100 ) {
14974                 return text;
14975             }
14976
14977             if(sec >= 0) time = 600*time + 10*sec; else
14978             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14979
14980             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14981
14982             /* [HGM] PV time: now locate end of PV info */
14983             while( *++sep >= '0' && *sep <= '9'); // strip depth
14984             if(time >= 0)
14985             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14986             if(sec >= 0)
14987             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14988             if(deci >= 0)
14989             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14990             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14991         }
14992
14993         if( depth <= 0 ) {
14994             return text;
14995         }
14996
14997         if( time < 0 ) {
14998             time = -1;
14999         }
15000
15001         pvInfoList[index-1].depth = depth;
15002         pvInfoList[index-1].score = score;
15003         pvInfoList[index-1].time  = 10*time; // centi-sec
15004         if(*sep == '}') *sep = 0; else *--sep = '{';
15005         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15006     }
15007     return sep;
15008 }
15009
15010 void
15011 SendToProgram (char *message, ChessProgramState *cps)
15012 {
15013     int count, outCount, error;
15014     char buf[MSG_SIZ];
15015
15016     if (cps->pr == NoProc) return;
15017     Attention(cps);
15018
15019     if (appData.debugMode) {
15020         TimeMark now;
15021         GetTimeMark(&now);
15022         fprintf(debugFP, "%ld >%-6s: %s",
15023                 SubtractTimeMarks(&now, &programStartTime),
15024                 cps->which, message);
15025     }
15026
15027     count = strlen(message);
15028     outCount = OutputToProcess(cps->pr, message, count, &error);
15029     if (outCount < count && !exiting
15030                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15031       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15032       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15033         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15034             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15035                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15036                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15037                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15038             } else {
15039                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15040                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15041                 gameInfo.result = res;
15042             }
15043             gameInfo.resultDetails = StrSave(buf);
15044         }
15045         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15046         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15047     }
15048 }
15049
15050 void
15051 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15052 {
15053     char *end_str;
15054     char buf[MSG_SIZ];
15055     ChessProgramState *cps = (ChessProgramState *)closure;
15056
15057     if (isr != cps->isr) return; /* Killed intentionally */
15058     if (count <= 0) {
15059         if (count == 0) {
15060             RemoveInputSource(cps->isr);
15061             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15062             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15063                     _(cps->which), cps->program);
15064         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15065                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15066                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15067                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15068                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15069                 } else {
15070                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15071                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15072                     gameInfo.result = res;
15073                 }
15074                 gameInfo.resultDetails = StrSave(buf);
15075             }
15076             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15077             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15078         } else {
15079             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15080                     _(cps->which), cps->program);
15081             RemoveInputSource(cps->isr);
15082
15083             /* [AS] Program is misbehaving badly... kill it */
15084             if( count == -2 ) {
15085                 DestroyChildProcess( cps->pr, 9 );
15086                 cps->pr = NoProc;
15087             }
15088
15089             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15090         }
15091         return;
15092     }
15093
15094     if ((end_str = strchr(message, '\r')) != NULL)
15095       *end_str = NULLCHAR;
15096     if ((end_str = strchr(message, '\n')) != NULL)
15097       *end_str = NULLCHAR;
15098
15099     if (appData.debugMode) {
15100         TimeMark now; int print = 1;
15101         char *quote = ""; char c; int i;
15102
15103         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15104                 char start = message[0];
15105                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15106                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15107                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15108                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15109                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15110                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15111                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15112                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15113                    sscanf(message, "hint: %c", &c)!=1 && 
15114                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15115                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15116                     print = (appData.engineComments >= 2);
15117                 }
15118                 message[0] = start; // restore original message
15119         }
15120         if(print) {
15121                 GetTimeMark(&now);
15122                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15123                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15124                         quote,
15125                         message);
15126         }
15127     }
15128
15129     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15130     if (appData.icsEngineAnalyze) {
15131         if (strstr(message, "whisper") != NULL ||
15132              strstr(message, "kibitz") != NULL ||
15133             strstr(message, "tellics") != NULL) return;
15134     }
15135
15136     HandleMachineMove(message, cps);
15137 }
15138
15139
15140 void
15141 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15142 {
15143     char buf[MSG_SIZ];
15144     int seconds;
15145
15146     if( timeControl_2 > 0 ) {
15147         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15148             tc = timeControl_2;
15149         }
15150     }
15151     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15152     inc /= cps->timeOdds;
15153     st  /= cps->timeOdds;
15154
15155     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15156
15157     if (st > 0) {
15158       /* Set exact time per move, normally using st command */
15159       if (cps->stKludge) {
15160         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15161         seconds = st % 60;
15162         if (seconds == 0) {
15163           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15164         } else {
15165           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15166         }
15167       } else {
15168         snprintf(buf, MSG_SIZ, "st %d\n", st);
15169       }
15170     } else {
15171       /* Set conventional or incremental time control, using level command */
15172       if (seconds == 0) {
15173         /* Note old gnuchess bug -- minutes:seconds used to not work.
15174            Fixed in later versions, but still avoid :seconds
15175            when seconds is 0. */
15176         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15177       } else {
15178         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15179                  seconds, inc/1000.);
15180       }
15181     }
15182     SendToProgram(buf, cps);
15183
15184     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15185     /* Orthogonally, limit search to given depth */
15186     if (sd > 0) {
15187       if (cps->sdKludge) {
15188         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15189       } else {
15190         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15191       }
15192       SendToProgram(buf, cps);
15193     }
15194
15195     if(cps->nps >= 0) { /* [HGM] nps */
15196         if(cps->supportsNPS == FALSE)
15197           cps->nps = -1; // don't use if engine explicitly says not supported!
15198         else {
15199           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15200           SendToProgram(buf, cps);
15201         }
15202     }
15203 }
15204
15205 ChessProgramState *
15206 WhitePlayer ()
15207 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15208 {
15209     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15210        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15211         return &second;
15212     return &first;
15213 }
15214
15215 void
15216 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15217 {
15218     char message[MSG_SIZ];
15219     long time, otime;
15220
15221     /* Note: this routine must be called when the clocks are stopped
15222        or when they have *just* been set or switched; otherwise
15223        it will be off by the time since the current tick started.
15224     */
15225     if (machineWhite) {
15226         time = whiteTimeRemaining / 10;
15227         otime = blackTimeRemaining / 10;
15228     } else {
15229         time = blackTimeRemaining / 10;
15230         otime = whiteTimeRemaining / 10;
15231     }
15232     /* [HGM] translate opponent's time by time-odds factor */
15233     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15234
15235     if (time <= 0) time = 1;
15236     if (otime <= 0) otime = 1;
15237
15238     snprintf(message, MSG_SIZ, "time %ld\n", time);
15239     SendToProgram(message, cps);
15240
15241     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15242     SendToProgram(message, cps);
15243 }
15244
15245 int
15246 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15247 {
15248   char buf[MSG_SIZ];
15249   int len = strlen(name);
15250   int val;
15251
15252   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15253     (*p) += len + 1;
15254     sscanf(*p, "%d", &val);
15255     *loc = (val != 0);
15256     while (**p && **p != ' ')
15257       (*p)++;
15258     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15259     SendToProgram(buf, cps);
15260     return TRUE;
15261   }
15262   return FALSE;
15263 }
15264
15265 int
15266 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15267 {
15268   char buf[MSG_SIZ];
15269   int len = strlen(name);
15270   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15271     (*p) += len + 1;
15272     sscanf(*p, "%d", loc);
15273     while (**p && **p != ' ') (*p)++;
15274     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15275     SendToProgram(buf, cps);
15276     return TRUE;
15277   }
15278   return FALSE;
15279 }
15280
15281 int
15282 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15283 {
15284   char buf[MSG_SIZ];
15285   int len = strlen(name);
15286   if (strncmp((*p), name, len) == 0
15287       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15288     (*p) += len + 2;
15289     sscanf(*p, "%[^\"]", loc);
15290     while (**p && **p != '\"') (*p)++;
15291     if (**p == '\"') (*p)++;
15292     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15293     SendToProgram(buf, cps);
15294     return TRUE;
15295   }
15296   return FALSE;
15297 }
15298
15299 int
15300 ParseOption (Option *opt, ChessProgramState *cps)
15301 // [HGM] options: process the string that defines an engine option, and determine
15302 // name, type, default value, and allowed value range
15303 {
15304         char *p, *q, buf[MSG_SIZ];
15305         int n, min = (-1)<<31, max = 1<<31, def;
15306
15307         if(p = strstr(opt->name, " -spin ")) {
15308             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15309             if(max < min) max = min; // enforce consistency
15310             if(def < min) def = min;
15311             if(def > max) def = max;
15312             opt->value = def;
15313             opt->min = min;
15314             opt->max = max;
15315             opt->type = Spin;
15316         } else if((p = strstr(opt->name, " -slider "))) {
15317             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15318             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15319             if(max < min) max = min; // enforce consistency
15320             if(def < min) def = min;
15321             if(def > max) def = max;
15322             opt->value = def;
15323             opt->min = min;
15324             opt->max = max;
15325             opt->type = Spin; // Slider;
15326         } else if((p = strstr(opt->name, " -string "))) {
15327             opt->textValue = p+9;
15328             opt->type = TextBox;
15329         } else if((p = strstr(opt->name, " -file "))) {
15330             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15331             opt->textValue = p+7;
15332             opt->type = FileName; // FileName;
15333         } else if((p = strstr(opt->name, " -path "))) {
15334             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15335             opt->textValue = p+7;
15336             opt->type = PathName; // PathName;
15337         } else if(p = strstr(opt->name, " -check ")) {
15338             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15339             opt->value = (def != 0);
15340             opt->type = CheckBox;
15341         } else if(p = strstr(opt->name, " -combo ")) {
15342             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15343             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15344             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15345             opt->value = n = 0;
15346             while(q = StrStr(q, " /// ")) {
15347                 n++; *q = 0;    // count choices, and null-terminate each of them
15348                 q += 5;
15349                 if(*q == '*') { // remember default, which is marked with * prefix
15350                     q++;
15351                     opt->value = n;
15352                 }
15353                 cps->comboList[cps->comboCnt++] = q;
15354             }
15355             cps->comboList[cps->comboCnt++] = NULL;
15356             opt->max = n + 1;
15357             opt->type = ComboBox;
15358         } else if(p = strstr(opt->name, " -button")) {
15359             opt->type = Button;
15360         } else if(p = strstr(opt->name, " -save")) {
15361             opt->type = SaveButton;
15362         } else return FALSE;
15363         *p = 0; // terminate option name
15364         // now look if the command-line options define a setting for this engine option.
15365         if(cps->optionSettings && cps->optionSettings[0])
15366             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15367         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15368           snprintf(buf, MSG_SIZ, "option %s", p);
15369                 if(p = strstr(buf, ",")) *p = 0;
15370                 if(q = strchr(buf, '=')) switch(opt->type) {
15371                     case ComboBox:
15372                         for(n=0; n<opt->max; n++)
15373                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15374                         break;
15375                     case TextBox:
15376                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15377                         break;
15378                     case Spin:
15379                     case CheckBox:
15380                         opt->value = atoi(q+1);
15381                     default:
15382                         break;
15383                 }
15384                 strcat(buf, "\n");
15385                 SendToProgram(buf, cps);
15386         }
15387         return TRUE;
15388 }
15389
15390 void
15391 FeatureDone (ChessProgramState *cps, int val)
15392 {
15393   DelayedEventCallback cb = GetDelayedEvent();
15394   if ((cb == InitBackEnd3 && cps == &first) ||
15395       (cb == SettingsMenuIfReady && cps == &second) ||
15396       (cb == LoadEngine) ||
15397       (cb == TwoMachinesEventIfReady)) {
15398     CancelDelayedEvent();
15399     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15400   }
15401   cps->initDone = val;
15402 }
15403
15404 /* Parse feature command from engine */
15405 void
15406 ParseFeatures (char *args, ChessProgramState *cps)
15407 {
15408   char *p = args;
15409   char *q;
15410   int val;
15411   char buf[MSG_SIZ];
15412
15413   for (;;) {
15414     while (*p == ' ') p++;
15415     if (*p == NULLCHAR) return;
15416
15417     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15418     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15419     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15420     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15421     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15422     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15423     if (BoolFeature(&p, "reuse", &val, cps)) {
15424       /* Engine can disable reuse, but can't enable it if user said no */
15425       if (!val) cps->reuse = FALSE;
15426       continue;
15427     }
15428     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15429     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15430       if (gameMode == TwoMachinesPlay) {
15431         DisplayTwoMachinesTitle();
15432       } else {
15433         DisplayTitle("");
15434       }
15435       continue;
15436     }
15437     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15438     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15439     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15440     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15441     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15442     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15443     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15444     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15445     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15446     if (IntFeature(&p, "done", &val, cps)) {
15447       FeatureDone(cps, val);
15448       continue;
15449     }
15450     /* Added by Tord: */
15451     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15452     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15453     /* End of additions by Tord */
15454
15455     /* [HGM] added features: */
15456     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15457     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15458     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15459     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15460     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15461     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15462     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15463         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15464           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15465             SendToProgram(buf, cps);
15466             continue;
15467         }
15468         if(cps->nrOptions >= MAX_OPTIONS) {
15469             cps->nrOptions--;
15470             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15471             DisplayError(buf, 0);
15472         }
15473         continue;
15474     }
15475     /* End of additions by HGM */
15476
15477     /* unknown feature: complain and skip */
15478     q = p;
15479     while (*q && *q != '=') q++;
15480     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15481     SendToProgram(buf, cps);
15482     p = q;
15483     if (*p == '=') {
15484       p++;
15485       if (*p == '\"') {
15486         p++;
15487         while (*p && *p != '\"') p++;
15488         if (*p == '\"') p++;
15489       } else {
15490         while (*p && *p != ' ') p++;
15491       }
15492     }
15493   }
15494
15495 }
15496
15497 void
15498 PeriodicUpdatesEvent (int newState)
15499 {
15500     if (newState == appData.periodicUpdates)
15501       return;
15502
15503     appData.periodicUpdates=newState;
15504
15505     /* Display type changes, so update it now */
15506 //    DisplayAnalysis();
15507
15508     /* Get the ball rolling again... */
15509     if (newState) {
15510         AnalysisPeriodicEvent(1);
15511         StartAnalysisClock();
15512     }
15513 }
15514
15515 void
15516 PonderNextMoveEvent (int newState)
15517 {
15518     if (newState == appData.ponderNextMove) return;
15519     if (gameMode == EditPosition) EditPositionDone(TRUE);
15520     if (newState) {
15521         SendToProgram("hard\n", &first);
15522         if (gameMode == TwoMachinesPlay) {
15523             SendToProgram("hard\n", &second);
15524         }
15525     } else {
15526         SendToProgram("easy\n", &first);
15527         thinkOutput[0] = NULLCHAR;
15528         if (gameMode == TwoMachinesPlay) {
15529             SendToProgram("easy\n", &second);
15530         }
15531     }
15532     appData.ponderNextMove = newState;
15533 }
15534
15535 void
15536 NewSettingEvent (int option, int *feature, char *command, int value)
15537 {
15538     char buf[MSG_SIZ];
15539
15540     if (gameMode == EditPosition) EditPositionDone(TRUE);
15541     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15542     if(feature == NULL || *feature) SendToProgram(buf, &first);
15543     if (gameMode == TwoMachinesPlay) {
15544         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15545     }
15546 }
15547
15548 void
15549 ShowThinkingEvent ()
15550 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15551 {
15552     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15553     int newState = appData.showThinking
15554         // [HGM] thinking: other features now need thinking output as well
15555         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15556
15557     if (oldState == newState) return;
15558     oldState = newState;
15559     if (gameMode == EditPosition) EditPositionDone(TRUE);
15560     if (oldState) {
15561         SendToProgram("post\n", &first);
15562         if (gameMode == TwoMachinesPlay) {
15563             SendToProgram("post\n", &second);
15564         }
15565     } else {
15566         SendToProgram("nopost\n", &first);
15567         thinkOutput[0] = NULLCHAR;
15568         if (gameMode == TwoMachinesPlay) {
15569             SendToProgram("nopost\n", &second);
15570         }
15571     }
15572 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15573 }
15574
15575 void
15576 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15577 {
15578   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15579   if (pr == NoProc) return;
15580   AskQuestion(title, question, replyPrefix, pr);
15581 }
15582
15583 void
15584 TypeInEvent (char firstChar)
15585 {
15586     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15587         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15588         gameMode == AnalyzeMode || gameMode == EditGame || 
15589         gameMode == EditPosition || gameMode == IcsExamining ||
15590         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15591         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15592                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15593                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15594         gameMode == Training) PopUpMoveDialog(firstChar);
15595 }
15596
15597 void
15598 TypeInDoneEvent (char *move)
15599 {
15600         Board board;
15601         int n, fromX, fromY, toX, toY;
15602         char promoChar;
15603         ChessMove moveType;
15604
15605         // [HGM] FENedit
15606         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15607                 EditPositionPasteFEN(move);
15608                 return;
15609         }
15610         // [HGM] movenum: allow move number to be typed in any mode
15611         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15612           ToNrEvent(2*n-1);
15613           return;
15614         }
15615         // undocumented kludge: allow command-line option to be typed in!
15616         // (potentially fatal, and does not implement the effect of the option.)
15617         // should only be used for options that are values on which future decisions will be made,
15618         // and definitely not on options that would be used during initialization.
15619         if(strstr(move, "!!! -") == move) {
15620             ParseArgsFromString(move+4);
15621             return;
15622         }
15623
15624       if (gameMode != EditGame && currentMove != forwardMostMove && 
15625         gameMode != Training) {
15626         DisplayMoveError(_("Displayed move is not current"));
15627       } else {
15628         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15629           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15630         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15631         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15632           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15633           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15634         } else {
15635           DisplayMoveError(_("Could not parse move"));
15636         }
15637       }
15638 }
15639
15640 void
15641 DisplayMove (int moveNumber)
15642 {
15643     char message[MSG_SIZ];
15644     char res[MSG_SIZ];
15645     char cpThinkOutput[MSG_SIZ];
15646
15647     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15648
15649     if (moveNumber == forwardMostMove - 1 ||
15650         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15651
15652         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15653
15654         if (strchr(cpThinkOutput, '\n')) {
15655             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15656         }
15657     } else {
15658         *cpThinkOutput = NULLCHAR;
15659     }
15660
15661     /* [AS] Hide thinking from human user */
15662     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15663         *cpThinkOutput = NULLCHAR;
15664         if( thinkOutput[0] != NULLCHAR ) {
15665             int i;
15666
15667             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15668                 cpThinkOutput[i] = '.';
15669             }
15670             cpThinkOutput[i] = NULLCHAR;
15671             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15672         }
15673     }
15674
15675     if (moveNumber == forwardMostMove - 1 &&
15676         gameInfo.resultDetails != NULL) {
15677         if (gameInfo.resultDetails[0] == NULLCHAR) {
15678           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15679         } else {
15680           snprintf(res, MSG_SIZ, " {%s} %s",
15681                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15682         }
15683     } else {
15684         res[0] = NULLCHAR;
15685     }
15686
15687     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15688         DisplayMessage(res, cpThinkOutput);
15689     } else {
15690       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15691                 WhiteOnMove(moveNumber) ? " " : ".. ",
15692                 parseList[moveNumber], res);
15693         DisplayMessage(message, cpThinkOutput);
15694     }
15695 }
15696
15697 void
15698 DisplayComment (int moveNumber, char *text)
15699 {
15700     char title[MSG_SIZ];
15701
15702     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15703       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15704     } else {
15705       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15706               WhiteOnMove(moveNumber) ? " " : ".. ",
15707               parseList[moveNumber]);
15708     }
15709     if (text != NULL && (appData.autoDisplayComment || commentUp))
15710         CommentPopUp(title, text);
15711 }
15712
15713 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15714  * might be busy thinking or pondering.  It can be omitted if your
15715  * gnuchess is configured to stop thinking immediately on any user
15716  * input.  However, that gnuchess feature depends on the FIONREAD
15717  * ioctl, which does not work properly on some flavors of Unix.
15718  */
15719 void
15720 Attention (ChessProgramState *cps)
15721 {
15722 #if ATTENTION
15723     if (!cps->useSigint) return;
15724     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15725     switch (gameMode) {
15726       case MachinePlaysWhite:
15727       case MachinePlaysBlack:
15728       case TwoMachinesPlay:
15729       case IcsPlayingWhite:
15730       case IcsPlayingBlack:
15731       case AnalyzeMode:
15732       case AnalyzeFile:
15733         /* Skip if we know it isn't thinking */
15734         if (!cps->maybeThinking) return;
15735         if (appData.debugMode)
15736           fprintf(debugFP, "Interrupting %s\n", cps->which);
15737         InterruptChildProcess(cps->pr);
15738         cps->maybeThinking = FALSE;
15739         break;
15740       default:
15741         break;
15742     }
15743 #endif /*ATTENTION*/
15744 }
15745
15746 int
15747 CheckFlags ()
15748 {
15749     if (whiteTimeRemaining <= 0) {
15750         if (!whiteFlag) {
15751             whiteFlag = TRUE;
15752             if (appData.icsActive) {
15753                 if (appData.autoCallFlag &&
15754                     gameMode == IcsPlayingBlack && !blackFlag) {
15755                   SendToICS(ics_prefix);
15756                   SendToICS("flag\n");
15757                 }
15758             } else {
15759                 if (blackFlag) {
15760                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15761                 } else {
15762                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15763                     if (appData.autoCallFlag) {
15764                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15765                         return TRUE;
15766                     }
15767                 }
15768             }
15769         }
15770     }
15771     if (blackTimeRemaining <= 0) {
15772         if (!blackFlag) {
15773             blackFlag = TRUE;
15774             if (appData.icsActive) {
15775                 if (appData.autoCallFlag &&
15776                     gameMode == IcsPlayingWhite && !whiteFlag) {
15777                   SendToICS(ics_prefix);
15778                   SendToICS("flag\n");
15779                 }
15780             } else {
15781                 if (whiteFlag) {
15782                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15783                 } else {
15784                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15785                     if (appData.autoCallFlag) {
15786                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15787                         return TRUE;
15788                     }
15789                 }
15790             }
15791         }
15792     }
15793     return FALSE;
15794 }
15795
15796 void
15797 CheckTimeControl ()
15798 {
15799     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15800         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15801
15802     /*
15803      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15804      */
15805     if ( !WhiteOnMove(forwardMostMove) ) {
15806         /* White made time control */
15807         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15808         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15809         /* [HGM] time odds: correct new time quota for time odds! */
15810                                             / WhitePlayer()->timeOdds;
15811         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15812     } else {
15813         lastBlack -= blackTimeRemaining;
15814         /* Black made time control */
15815         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15816                                             / WhitePlayer()->other->timeOdds;
15817         lastWhite = whiteTimeRemaining;
15818     }
15819 }
15820
15821 void
15822 DisplayBothClocks ()
15823 {
15824     int wom = gameMode == EditPosition ?
15825       !blackPlaysFirst : WhiteOnMove(currentMove);
15826     DisplayWhiteClock(whiteTimeRemaining, wom);
15827     DisplayBlackClock(blackTimeRemaining, !wom);
15828 }
15829
15830
15831 /* Timekeeping seems to be a portability nightmare.  I think everyone
15832    has ftime(), but I'm really not sure, so I'm including some ifdefs
15833    to use other calls if you don't.  Clocks will be less accurate if
15834    you have neither ftime nor gettimeofday.
15835 */
15836
15837 /* VS 2008 requires the #include outside of the function */
15838 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15839 #include <sys/timeb.h>
15840 #endif
15841
15842 /* Get the current time as a TimeMark */
15843 void
15844 GetTimeMark (TimeMark *tm)
15845 {
15846 #if HAVE_GETTIMEOFDAY
15847
15848     struct timeval timeVal;
15849     struct timezone timeZone;
15850
15851     gettimeofday(&timeVal, &timeZone);
15852     tm->sec = (long) timeVal.tv_sec;
15853     tm->ms = (int) (timeVal.tv_usec / 1000L);
15854
15855 #else /*!HAVE_GETTIMEOFDAY*/
15856 #if HAVE_FTIME
15857
15858 // include <sys/timeb.h> / moved to just above start of function
15859     struct timeb timeB;
15860
15861     ftime(&timeB);
15862     tm->sec = (long) timeB.time;
15863     tm->ms = (int) timeB.millitm;
15864
15865 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15866     tm->sec = (long) time(NULL);
15867     tm->ms = 0;
15868 #endif
15869 #endif
15870 }
15871
15872 /* Return the difference in milliseconds between two
15873    time marks.  We assume the difference will fit in a long!
15874 */
15875 long
15876 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15877 {
15878     return 1000L*(tm2->sec - tm1->sec) +
15879            (long) (tm2->ms - tm1->ms);
15880 }
15881
15882
15883 /*
15884  * Code to manage the game clocks.
15885  *
15886  * In tournament play, black starts the clock and then white makes a move.
15887  * We give the human user a slight advantage if he is playing white---the
15888  * clocks don't run until he makes his first move, so it takes zero time.
15889  * Also, we don't account for network lag, so we could get out of sync
15890  * with GNU Chess's clock -- but then, referees are always right.
15891  */
15892
15893 static TimeMark tickStartTM;
15894 static long intendedTickLength;
15895
15896 long
15897 NextTickLength (long timeRemaining)
15898 {
15899     long nominalTickLength, nextTickLength;
15900
15901     if (timeRemaining > 0L && timeRemaining <= 10000L)
15902       nominalTickLength = 100L;
15903     else
15904       nominalTickLength = 1000L;
15905     nextTickLength = timeRemaining % nominalTickLength;
15906     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15907
15908     return nextTickLength;
15909 }
15910
15911 /* Adjust clock one minute up or down */
15912 void
15913 AdjustClock (Boolean which, int dir)
15914 {
15915     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15916     if(which) blackTimeRemaining += 60000*dir;
15917     else      whiteTimeRemaining += 60000*dir;
15918     DisplayBothClocks();
15919     adjustedClock = TRUE;
15920 }
15921
15922 /* Stop clocks and reset to a fresh time control */
15923 void
15924 ResetClocks ()
15925 {
15926     (void) StopClockTimer();
15927     if (appData.icsActive) {
15928         whiteTimeRemaining = blackTimeRemaining = 0;
15929     } else if (searchTime) {
15930         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15931         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15932     } else { /* [HGM] correct new time quote for time odds */
15933         whiteTC = blackTC = fullTimeControlString;
15934         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15935         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15936     }
15937     if (whiteFlag || blackFlag) {
15938         DisplayTitle("");
15939         whiteFlag = blackFlag = FALSE;
15940     }
15941     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15942     DisplayBothClocks();
15943     adjustedClock = FALSE;
15944 }
15945
15946 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15947
15948 /* Decrement running clock by amount of time that has passed */
15949 void
15950 DecrementClocks ()
15951 {
15952     long timeRemaining;
15953     long lastTickLength, fudge;
15954     TimeMark now;
15955
15956     if (!appData.clockMode) return;
15957     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15958
15959     GetTimeMark(&now);
15960
15961     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15962
15963     /* Fudge if we woke up a little too soon */
15964     fudge = intendedTickLength - lastTickLength;
15965     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15966
15967     if (WhiteOnMove(forwardMostMove)) {
15968         if(whiteNPS >= 0) lastTickLength = 0;
15969         timeRemaining = whiteTimeRemaining -= lastTickLength;
15970         if(timeRemaining < 0 && !appData.icsActive) {
15971             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15972             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15973                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15974                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15975             }
15976         }
15977         DisplayWhiteClock(whiteTimeRemaining - fudge,
15978                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15979     } else {
15980         if(blackNPS >= 0) lastTickLength = 0;
15981         timeRemaining = blackTimeRemaining -= lastTickLength;
15982         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15983             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15984             if(suddenDeath) {
15985                 blackStartMove = forwardMostMove;
15986                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15987             }
15988         }
15989         DisplayBlackClock(blackTimeRemaining - fudge,
15990                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15991     }
15992     if (CheckFlags()) return;
15993
15994     tickStartTM = now;
15995     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15996     StartClockTimer(intendedTickLength);
15997
15998     /* if the time remaining has fallen below the alarm threshold, sound the
15999      * alarm. if the alarm has sounded and (due to a takeback or time control
16000      * with increment) the time remaining has increased to a level above the
16001      * threshold, reset the alarm so it can sound again.
16002      */
16003
16004     if (appData.icsActive && appData.icsAlarm) {
16005
16006         /* make sure we are dealing with the user's clock */
16007         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16008                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16009            )) return;
16010
16011         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16012             alarmSounded = FALSE;
16013         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16014             PlayAlarmSound();
16015             alarmSounded = TRUE;
16016         }
16017     }
16018 }
16019
16020
16021 /* A player has just moved, so stop the previously running
16022    clock and (if in clock mode) start the other one.
16023    We redisplay both clocks in case we're in ICS mode, because
16024    ICS gives us an update to both clocks after every move.
16025    Note that this routine is called *after* forwardMostMove
16026    is updated, so the last fractional tick must be subtracted
16027    from the color that is *not* on move now.
16028 */
16029 void
16030 SwitchClocks (int newMoveNr)
16031 {
16032     long lastTickLength;
16033     TimeMark now;
16034     int flagged = FALSE;
16035
16036     GetTimeMark(&now);
16037
16038     if (StopClockTimer() && appData.clockMode) {
16039         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16040         if (!WhiteOnMove(forwardMostMove)) {
16041             if(blackNPS >= 0) lastTickLength = 0;
16042             blackTimeRemaining -= lastTickLength;
16043            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16044 //         if(pvInfoList[forwardMostMove].time == -1)
16045                  pvInfoList[forwardMostMove].time =               // use GUI time
16046                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16047         } else {
16048            if(whiteNPS >= 0) lastTickLength = 0;
16049            whiteTimeRemaining -= lastTickLength;
16050            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16051 //         if(pvInfoList[forwardMostMove].time == -1)
16052                  pvInfoList[forwardMostMove].time =
16053                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16054         }
16055         flagged = CheckFlags();
16056     }
16057     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16058     CheckTimeControl();
16059
16060     if (flagged || !appData.clockMode) return;
16061
16062     switch (gameMode) {
16063       case MachinePlaysBlack:
16064       case MachinePlaysWhite:
16065       case BeginningOfGame:
16066         if (pausing) return;
16067         break;
16068
16069       case EditGame:
16070       case PlayFromGameFile:
16071       case IcsExamining:
16072         return;
16073
16074       default:
16075         break;
16076     }
16077
16078     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16079         if(WhiteOnMove(forwardMostMove))
16080              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16081         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16082     }
16083
16084     tickStartTM = now;
16085     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16086       whiteTimeRemaining : blackTimeRemaining);
16087     StartClockTimer(intendedTickLength);
16088 }
16089
16090
16091 /* Stop both clocks */
16092 void
16093 StopClocks ()
16094 {
16095     long lastTickLength;
16096     TimeMark now;
16097
16098     if (!StopClockTimer()) return;
16099     if (!appData.clockMode) return;
16100
16101     GetTimeMark(&now);
16102
16103     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16104     if (WhiteOnMove(forwardMostMove)) {
16105         if(whiteNPS >= 0) lastTickLength = 0;
16106         whiteTimeRemaining -= lastTickLength;
16107         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16108     } else {
16109         if(blackNPS >= 0) lastTickLength = 0;
16110         blackTimeRemaining -= lastTickLength;
16111         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16112     }
16113     CheckFlags();
16114 }
16115
16116 /* Start clock of player on move.  Time may have been reset, so
16117    if clock is already running, stop and restart it. */
16118 void
16119 StartClocks ()
16120 {
16121     (void) StopClockTimer(); /* in case it was running already */
16122     DisplayBothClocks();
16123     if (CheckFlags()) return;
16124
16125     if (!appData.clockMode) return;
16126     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16127
16128     GetTimeMark(&tickStartTM);
16129     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16130       whiteTimeRemaining : blackTimeRemaining);
16131
16132    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16133     whiteNPS = blackNPS = -1;
16134     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16135        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16136         whiteNPS = first.nps;
16137     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16138        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16139         blackNPS = first.nps;
16140     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16141         whiteNPS = second.nps;
16142     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16143         blackNPS = second.nps;
16144     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16145
16146     StartClockTimer(intendedTickLength);
16147 }
16148
16149 char *
16150 TimeString (long ms)
16151 {
16152     long second, minute, hour, day;
16153     char *sign = "";
16154     static char buf[32];
16155
16156     if (ms > 0 && ms <= 9900) {
16157       /* convert milliseconds to tenths, rounding up */
16158       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16159
16160       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16161       return buf;
16162     }
16163
16164     /* convert milliseconds to seconds, rounding up */
16165     /* use floating point to avoid strangeness of integer division
16166        with negative dividends on many machines */
16167     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16168
16169     if (second < 0) {
16170         sign = "-";
16171         second = -second;
16172     }
16173
16174     day = second / (60 * 60 * 24);
16175     second = second % (60 * 60 * 24);
16176     hour = second / (60 * 60);
16177     second = second % (60 * 60);
16178     minute = second / 60;
16179     second = second % 60;
16180
16181     if (day > 0)
16182       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16183               sign, day, hour, minute, second);
16184     else if (hour > 0)
16185       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16186     else
16187       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16188
16189     return buf;
16190 }
16191
16192
16193 /*
16194  * This is necessary because some C libraries aren't ANSI C compliant yet.
16195  */
16196 char *
16197 StrStr (char *string, char *match)
16198 {
16199     int i, length;
16200
16201     length = strlen(match);
16202
16203     for (i = strlen(string) - length; i >= 0; i--, string++)
16204       if (!strncmp(match, string, length))
16205         return string;
16206
16207     return NULL;
16208 }
16209
16210 char *
16211 StrCaseStr (char *string, char *match)
16212 {
16213     int i, j, length;
16214
16215     length = strlen(match);
16216
16217     for (i = strlen(string) - length; i >= 0; i--, string++) {
16218         for (j = 0; j < length; j++) {
16219             if (ToLower(match[j]) != ToLower(string[j]))
16220               break;
16221         }
16222         if (j == length) return string;
16223     }
16224
16225     return NULL;
16226 }
16227
16228 #ifndef _amigados
16229 int
16230 StrCaseCmp (char *s1, char *s2)
16231 {
16232     char c1, c2;
16233
16234     for (;;) {
16235         c1 = ToLower(*s1++);
16236         c2 = ToLower(*s2++);
16237         if (c1 > c2) return 1;
16238         if (c1 < c2) return -1;
16239         if (c1 == NULLCHAR) return 0;
16240     }
16241 }
16242
16243
16244 int
16245 ToLower (int c)
16246 {
16247     return isupper(c) ? tolower(c) : c;
16248 }
16249
16250
16251 int
16252 ToUpper (int c)
16253 {
16254     return islower(c) ? toupper(c) : c;
16255 }
16256 #endif /* !_amigados    */
16257
16258 char *
16259 StrSave (char *s)
16260 {
16261   char *ret;
16262
16263   if ((ret = (char *) malloc(strlen(s) + 1)))
16264     {
16265       safeStrCpy(ret, s, strlen(s)+1);
16266     }
16267   return ret;
16268 }
16269
16270 char *
16271 StrSavePtr (char *s, char **savePtr)
16272 {
16273     if (*savePtr) {
16274         free(*savePtr);
16275     }
16276     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16277       safeStrCpy(*savePtr, s, strlen(s)+1);
16278     }
16279     return(*savePtr);
16280 }
16281
16282 char *
16283 PGNDate ()
16284 {
16285     time_t clock;
16286     struct tm *tm;
16287     char buf[MSG_SIZ];
16288
16289     clock = time((time_t *)NULL);
16290     tm = localtime(&clock);
16291     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16292             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16293     return StrSave(buf);
16294 }
16295
16296
16297 char *
16298 PositionToFEN (int move, char *overrideCastling)
16299 {
16300     int i, j, fromX, fromY, toX, toY;
16301     int whiteToPlay;
16302     char buf[MSG_SIZ];
16303     char *p, *q;
16304     int emptycount;
16305     ChessSquare piece;
16306
16307     whiteToPlay = (gameMode == EditPosition) ?
16308       !blackPlaysFirst : (move % 2 == 0);
16309     p = buf;
16310
16311     /* Piece placement data */
16312     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16313         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16314         emptycount = 0;
16315         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16316             if (boards[move][i][j] == EmptySquare) {
16317                 emptycount++;
16318             } else { ChessSquare piece = boards[move][i][j];
16319                 if (emptycount > 0) {
16320                     if(emptycount<10) /* [HGM] can be >= 10 */
16321                         *p++ = '0' + emptycount;
16322                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16323                     emptycount = 0;
16324                 }
16325                 if(PieceToChar(piece) == '+') {
16326                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16327                     *p++ = '+';
16328                     piece = (ChessSquare)(DEMOTED piece);
16329                 }
16330                 *p++ = PieceToChar(piece);
16331                 if(p[-1] == '~') {
16332                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16333                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16334                     *p++ = '~';
16335                 }
16336             }
16337         }
16338         if (emptycount > 0) {
16339             if(emptycount<10) /* [HGM] can be >= 10 */
16340                 *p++ = '0' + emptycount;
16341             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16342             emptycount = 0;
16343         }
16344         *p++ = '/';
16345     }
16346     *(p - 1) = ' ';
16347
16348     /* [HGM] print Crazyhouse or Shogi holdings */
16349     if( gameInfo.holdingsWidth ) {
16350         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16351         q = p;
16352         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16353             piece = boards[move][i][BOARD_WIDTH-1];
16354             if( piece != EmptySquare )
16355               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16356                   *p++ = PieceToChar(piece);
16357         }
16358         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16359             piece = boards[move][BOARD_HEIGHT-i-1][0];
16360             if( piece != EmptySquare )
16361               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16362                   *p++ = PieceToChar(piece);
16363         }
16364
16365         if( q == p ) *p++ = '-';
16366         *p++ = ']';
16367         *p++ = ' ';
16368     }
16369
16370     /* Active color */
16371     *p++ = whiteToPlay ? 'w' : 'b';
16372     *p++ = ' ';
16373
16374   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16375     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16376   } else {
16377   if(nrCastlingRights) {
16378      q = p;
16379      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16380        /* [HGM] write directly from rights */
16381            if(boards[move][CASTLING][2] != NoRights &&
16382               boards[move][CASTLING][0] != NoRights   )
16383                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16384            if(boards[move][CASTLING][2] != NoRights &&
16385               boards[move][CASTLING][1] != NoRights   )
16386                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16387            if(boards[move][CASTLING][5] != NoRights &&
16388               boards[move][CASTLING][3] != NoRights   )
16389                 *p++ = boards[move][CASTLING][3] + AAA;
16390            if(boards[move][CASTLING][5] != NoRights &&
16391               boards[move][CASTLING][4] != NoRights   )
16392                 *p++ = boards[move][CASTLING][4] + AAA;
16393      } else {
16394
16395         /* [HGM] write true castling rights */
16396         if( nrCastlingRights == 6 ) {
16397             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16398                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16399             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16400                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16401             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16402                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16403             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16404                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16405         }
16406      }
16407      if (q == p) *p++ = '-'; /* No castling rights */
16408      *p++ = ' ';
16409   }
16410
16411   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16412      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16413     /* En passant target square */
16414     if (move > backwardMostMove) {
16415         fromX = moveList[move - 1][0] - AAA;
16416         fromY = moveList[move - 1][1] - ONE;
16417         toX = moveList[move - 1][2] - AAA;
16418         toY = moveList[move - 1][3] - ONE;
16419         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16420             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16421             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16422             fromX == toX) {
16423             /* 2-square pawn move just happened */
16424             *p++ = toX + AAA;
16425             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16426         } else {
16427             *p++ = '-';
16428         }
16429     } else if(move == backwardMostMove) {
16430         // [HGM] perhaps we should always do it like this, and forget the above?
16431         if((signed char)boards[move][EP_STATUS] >= 0) {
16432             *p++ = boards[move][EP_STATUS] + AAA;
16433             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16434         } else {
16435             *p++ = '-';
16436         }
16437     } else {
16438         *p++ = '-';
16439     }
16440     *p++ = ' ';
16441   }
16442   }
16443
16444     /* [HGM] find reversible plies */
16445     {   int i = 0, j=move;
16446
16447         if (appData.debugMode) { int k;
16448             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16449             for(k=backwardMostMove; k<=forwardMostMove; k++)
16450                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16451
16452         }
16453
16454         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16455         if( j == backwardMostMove ) i += initialRulePlies;
16456         sprintf(p, "%d ", i);
16457         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16458     }
16459     /* Fullmove number */
16460     sprintf(p, "%d", (move / 2) + 1);
16461
16462     return StrSave(buf);
16463 }
16464
16465 Boolean
16466 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16467 {
16468     int i, j;
16469     char *p, c;
16470     int emptycount;
16471     ChessSquare piece;
16472
16473     p = fen;
16474
16475     /* [HGM] by default clear Crazyhouse holdings, if present */
16476     if(gameInfo.holdingsWidth) {
16477        for(i=0; i<BOARD_HEIGHT; i++) {
16478            board[i][0]             = EmptySquare; /* black holdings */
16479            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16480            board[i][1]             = (ChessSquare) 0; /* black counts */
16481            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16482        }
16483     }
16484
16485     /* Piece placement data */
16486     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16487         j = 0;
16488         for (;;) {
16489             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16490                 if (*p == '/') p++;
16491                 emptycount = gameInfo.boardWidth - j;
16492                 while (emptycount--)
16493                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16494                 break;
16495 #if(BOARD_FILES >= 10)
16496             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16497                 p++; emptycount=10;
16498                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16499                 while (emptycount--)
16500                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16501 #endif
16502             } else if (isdigit(*p)) {
16503                 emptycount = *p++ - '0';
16504                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16505                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16506                 while (emptycount--)
16507                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16508             } else if (*p == '+' || isalpha(*p)) {
16509                 if (j >= gameInfo.boardWidth) return FALSE;
16510                 if(*p=='+') {
16511                     piece = CharToPiece(*++p);
16512                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16513                     piece = (ChessSquare) (PROMOTED piece ); p++;
16514                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16515                 } else piece = CharToPiece(*p++);
16516
16517                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16518                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16519                     piece = (ChessSquare) (PROMOTED piece);
16520                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16521                     p++;
16522                 }
16523                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16524             } else {
16525                 return FALSE;
16526             }
16527         }
16528     }
16529     while (*p == '/' || *p == ' ') p++;
16530
16531     /* [HGM] look for Crazyhouse holdings here */
16532     while(*p==' ') p++;
16533     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16534         if(*p == '[') p++;
16535         if(*p == '-' ) p++; /* empty holdings */ else {
16536             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16537             /* if we would allow FEN reading to set board size, we would   */
16538             /* have to add holdings and shift the board read so far here   */
16539             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16540                 p++;
16541                 if((int) piece >= (int) BlackPawn ) {
16542                     i = (int)piece - (int)BlackPawn;
16543                     i = PieceToNumber((ChessSquare)i);
16544                     if( i >= gameInfo.holdingsSize ) return FALSE;
16545                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16546                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16547                 } else {
16548                     i = (int)piece - (int)WhitePawn;
16549                     i = PieceToNumber((ChessSquare)i);
16550                     if( i >= gameInfo.holdingsSize ) return FALSE;
16551                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16552                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16553                 }
16554             }
16555         }
16556         if(*p == ']') p++;
16557     }
16558
16559     while(*p == ' ') p++;
16560
16561     /* Active color */
16562     c = *p++;
16563     if(appData.colorNickNames) {
16564       if( c == appData.colorNickNames[0] ) c = 'w'; else
16565       if( c == appData.colorNickNames[1] ) c = 'b';
16566     }
16567     switch (c) {
16568       case 'w':
16569         *blackPlaysFirst = FALSE;
16570         break;
16571       case 'b':
16572         *blackPlaysFirst = TRUE;
16573         break;
16574       default:
16575         return FALSE;
16576     }
16577
16578     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16579     /* return the extra info in global variiables             */
16580
16581     /* set defaults in case FEN is incomplete */
16582     board[EP_STATUS] = EP_UNKNOWN;
16583     for(i=0; i<nrCastlingRights; i++ ) {
16584         board[CASTLING][i] =
16585             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16586     }   /* assume possible unless obviously impossible */
16587     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16588     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16589     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16590                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16591     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16592     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16593     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16594                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16595     FENrulePlies = 0;
16596
16597     while(*p==' ') p++;
16598     if(nrCastlingRights) {
16599       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16600           /* castling indicator present, so default becomes no castlings */
16601           for(i=0; i<nrCastlingRights; i++ ) {
16602                  board[CASTLING][i] = NoRights;
16603           }
16604       }
16605       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16606              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16607              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16608              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16609         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16610
16611         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16612             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16613             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16614         }
16615         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16616             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16617         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16618                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16619         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16620                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16621         switch(c) {
16622           case'K':
16623               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16624               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16625               board[CASTLING][2] = whiteKingFile;
16626               break;
16627           case'Q':
16628               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16629               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16630               board[CASTLING][2] = whiteKingFile;
16631               break;
16632           case'k':
16633               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16634               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16635               board[CASTLING][5] = blackKingFile;
16636               break;
16637           case'q':
16638               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16639               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16640               board[CASTLING][5] = blackKingFile;
16641           case '-':
16642               break;
16643           default: /* FRC castlings */
16644               if(c >= 'a') { /* black rights */
16645                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16646                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16647                   if(i == BOARD_RGHT) break;
16648                   board[CASTLING][5] = i;
16649                   c -= AAA;
16650                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16651                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16652                   if(c > i)
16653                       board[CASTLING][3] = c;
16654                   else
16655                       board[CASTLING][4] = c;
16656               } else { /* white rights */
16657                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16658                     if(board[0][i] == WhiteKing) break;
16659                   if(i == BOARD_RGHT) break;
16660                   board[CASTLING][2] = i;
16661                   c -= AAA - 'a' + 'A';
16662                   if(board[0][c] >= WhiteKing) break;
16663                   if(c > i)
16664                       board[CASTLING][0] = c;
16665                   else
16666                       board[CASTLING][1] = c;
16667               }
16668         }
16669       }
16670       for(i=0; i<nrCastlingRights; i++)
16671         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16672     if (appData.debugMode) {
16673         fprintf(debugFP, "FEN castling rights:");
16674         for(i=0; i<nrCastlingRights; i++)
16675         fprintf(debugFP, " %d", board[CASTLING][i]);
16676         fprintf(debugFP, "\n");
16677     }
16678
16679       while(*p==' ') p++;
16680     }
16681
16682     /* read e.p. field in games that know e.p. capture */
16683     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16684        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16685       if(*p=='-') {
16686         p++; board[EP_STATUS] = EP_NONE;
16687       } else {
16688          char c = *p++ - AAA;
16689
16690          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16691          if(*p >= '0' && *p <='9') p++;
16692          board[EP_STATUS] = c;
16693       }
16694     }
16695
16696
16697     if(sscanf(p, "%d", &i) == 1) {
16698         FENrulePlies = i; /* 50-move ply counter */
16699         /* (The move number is still ignored)    */
16700     }
16701
16702     return TRUE;
16703 }
16704
16705 void
16706 EditPositionPasteFEN (char *fen)
16707 {
16708   if (fen != NULL) {
16709     Board initial_position;
16710
16711     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16712       DisplayError(_("Bad FEN position in clipboard"), 0);
16713       return ;
16714     } else {
16715       int savedBlackPlaysFirst = blackPlaysFirst;
16716       EditPositionEvent();
16717       blackPlaysFirst = savedBlackPlaysFirst;
16718       CopyBoard(boards[0], initial_position);
16719       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16720       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16721       DisplayBothClocks();
16722       DrawPosition(FALSE, boards[currentMove]);
16723     }
16724   }
16725 }
16726
16727 static char cseq[12] = "\\   ";
16728
16729 Boolean
16730 set_cont_sequence (char *new_seq)
16731 {
16732     int len;
16733     Boolean ret;
16734
16735     // handle bad attempts to set the sequence
16736         if (!new_seq)
16737                 return 0; // acceptable error - no debug
16738
16739     len = strlen(new_seq);
16740     ret = (len > 0) && (len < sizeof(cseq));
16741     if (ret)
16742       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16743     else if (appData.debugMode)
16744       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16745     return ret;
16746 }
16747
16748 /*
16749     reformat a source message so words don't cross the width boundary.  internal
16750     newlines are not removed.  returns the wrapped size (no null character unless
16751     included in source message).  If dest is NULL, only calculate the size required
16752     for the dest buffer.  lp argument indicats line position upon entry, and it's
16753     passed back upon exit.
16754 */
16755 int
16756 wrap (char *dest, char *src, int count, int width, int *lp)
16757 {
16758     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16759
16760     cseq_len = strlen(cseq);
16761     old_line = line = *lp;
16762     ansi = len = clen = 0;
16763
16764     for (i=0; i < count; i++)
16765     {
16766         if (src[i] == '\033')
16767             ansi = 1;
16768
16769         // if we hit the width, back up
16770         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16771         {
16772             // store i & len in case the word is too long
16773             old_i = i, old_len = len;
16774
16775             // find the end of the last word
16776             while (i && src[i] != ' ' && src[i] != '\n')
16777             {
16778                 i--;
16779                 len--;
16780             }
16781
16782             // word too long?  restore i & len before splitting it
16783             if ((old_i-i+clen) >= width)
16784             {
16785                 i = old_i;
16786                 len = old_len;
16787             }
16788
16789             // extra space?
16790             if (i && src[i-1] == ' ')
16791                 len--;
16792
16793             if (src[i] != ' ' && src[i] != '\n')
16794             {
16795                 i--;
16796                 if (len)
16797                     len--;
16798             }
16799
16800             // now append the newline and continuation sequence
16801             if (dest)
16802                 dest[len] = '\n';
16803             len++;
16804             if (dest)
16805                 strncpy(dest+len, cseq, cseq_len);
16806             len += cseq_len;
16807             line = cseq_len;
16808             clen = cseq_len;
16809             continue;
16810         }
16811
16812         if (dest)
16813             dest[len] = src[i];
16814         len++;
16815         if (!ansi)
16816             line++;
16817         if (src[i] == '\n')
16818             line = 0;
16819         if (src[i] == 'm')
16820             ansi = 0;
16821     }
16822     if (dest && appData.debugMode)
16823     {
16824         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16825             count, width, line, len, *lp);
16826         show_bytes(debugFP, src, count);
16827         fprintf(debugFP, "\ndest: ");
16828         show_bytes(debugFP, dest, len);
16829         fprintf(debugFP, "\n");
16830     }
16831     *lp = dest ? line : old_line;
16832
16833     return len;
16834 }
16835
16836 // [HGM] vari: routines for shelving variations
16837 Boolean modeRestore = FALSE;
16838
16839 void
16840 PushInner (int firstMove, int lastMove)
16841 {
16842         int i, j, nrMoves = lastMove - firstMove;
16843
16844         // push current tail of game on stack
16845         savedResult[storedGames] = gameInfo.result;
16846         savedDetails[storedGames] = gameInfo.resultDetails;
16847         gameInfo.resultDetails = NULL;
16848         savedFirst[storedGames] = firstMove;
16849         savedLast [storedGames] = lastMove;
16850         savedFramePtr[storedGames] = framePtr;
16851         framePtr -= nrMoves; // reserve space for the boards
16852         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16853             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16854             for(j=0; j<MOVE_LEN; j++)
16855                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16856             for(j=0; j<2*MOVE_LEN; j++)
16857                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16858             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16859             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16860             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16861             pvInfoList[firstMove+i-1].depth = 0;
16862             commentList[framePtr+i] = commentList[firstMove+i];
16863             commentList[firstMove+i] = NULL;
16864         }
16865
16866         storedGames++;
16867         forwardMostMove = firstMove; // truncate game so we can start variation
16868 }
16869
16870 void
16871 PushTail (int firstMove, int lastMove)
16872 {
16873         if(appData.icsActive) { // only in local mode
16874                 forwardMostMove = currentMove; // mimic old ICS behavior
16875                 return;
16876         }
16877         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16878
16879         PushInner(firstMove, lastMove);
16880         if(storedGames == 1) GreyRevert(FALSE);
16881         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16882 }
16883
16884 void
16885 PopInner (Boolean annotate)
16886 {
16887         int i, j, nrMoves;
16888         char buf[8000], moveBuf[20];
16889
16890         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16891         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16892         nrMoves = savedLast[storedGames] - currentMove;
16893         if(annotate) {
16894                 int cnt = 10;
16895                 if(!WhiteOnMove(currentMove))
16896                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16897                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16898                 for(i=currentMove; i<forwardMostMove; i++) {
16899                         if(WhiteOnMove(i))
16900                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16901                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16902                         strcat(buf, moveBuf);
16903                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16904                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16905                 }
16906                 strcat(buf, ")");
16907         }
16908         for(i=1; i<=nrMoves; i++) { // copy last variation back
16909             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16910             for(j=0; j<MOVE_LEN; j++)
16911                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16912             for(j=0; j<2*MOVE_LEN; j++)
16913                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16914             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16915             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16916             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16917             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16918             commentList[currentMove+i] = commentList[framePtr+i];
16919             commentList[framePtr+i] = NULL;
16920         }
16921         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16922         framePtr = savedFramePtr[storedGames];
16923         gameInfo.result = savedResult[storedGames];
16924         if(gameInfo.resultDetails != NULL) {
16925             free(gameInfo.resultDetails);
16926       }
16927         gameInfo.resultDetails = savedDetails[storedGames];
16928         forwardMostMove = currentMove + nrMoves;
16929 }
16930
16931 Boolean
16932 PopTail (Boolean annotate)
16933 {
16934         if(appData.icsActive) return FALSE; // only in local mode
16935         if(!storedGames) return FALSE; // sanity
16936         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16937
16938         PopInner(annotate);
16939         if(currentMove < forwardMostMove) ForwardEvent(); else
16940         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16941
16942         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16943         return TRUE;
16944 }
16945
16946 void
16947 CleanupTail ()
16948 {       // remove all shelved variations
16949         int i;
16950         for(i=0; i<storedGames; i++) {
16951             if(savedDetails[i])
16952                 free(savedDetails[i]);
16953             savedDetails[i] = NULL;
16954         }
16955         for(i=framePtr; i<MAX_MOVES; i++) {
16956                 if(commentList[i]) free(commentList[i]);
16957                 commentList[i] = NULL;
16958         }
16959         framePtr = MAX_MOVES-1;
16960         storedGames = 0;
16961 }
16962
16963 void
16964 LoadVariation (int index, char *text)
16965 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16966         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16967         int level = 0, move;
16968
16969         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16970         // first find outermost bracketing variation
16971         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16972             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16973                 if(*p == '{') wait = '}'; else
16974                 if(*p == '[') wait = ']'; else
16975                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16976                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16977             }
16978             if(*p == wait) wait = NULLCHAR; // closing ]} found
16979             p++;
16980         }
16981         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16982         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16983         end[1] = NULLCHAR; // clip off comment beyond variation
16984         ToNrEvent(currentMove-1);
16985         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16986         // kludge: use ParsePV() to append variation to game
16987         move = currentMove;
16988         ParsePV(start, TRUE, TRUE);
16989         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16990         ClearPremoveHighlights();
16991         CommentPopDown();
16992         ToNrEvent(currentMove+1);
16993 }
16994