Implement -serverFile option
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = _(engineNames[n]);
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091         if( (len >= MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 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   if(appData.autoCopyPV) CopyFENToClipboard();
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV (int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void
5513 GetPositionNumber ()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int
5526 put (Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void
5545 AddOnePiece (Board board, int pieceType, int rank, int shade)
5546 // calculate where the next piece goes, (any empty square), and put it there
5547 {
5548         int i;
5549
5550         i = seed % squaresLeft[shade];
5551         nrOfShuffles *= squaresLeft[shade];
5552         seed /= squaresLeft[shade];
5553         put(board, pieceType, rank, i, shade);
5554 }
5555
5556 void
5557 AddTwoPieces (Board board, int pieceType, int rank)
5558 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5559 {
5560         int i, n=squaresLeft[ANY], j=n-1, k;
5561
5562         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5563         i = seed % k;  // pick one
5564         nrOfShuffles *= k;
5565         seed /= k;
5566         while(i >= j) i -= j--;
5567         j = n - 1 - j; i += j;
5568         put(board, pieceType, rank, j, ANY);
5569         put(board, pieceType, rank, i, ANY);
5570 }
5571
5572 void
5573 SetUpShuffle (Board board, int number)
5574 {
5575         int i, p, first=1;
5576
5577         GetPositionNumber(); nrOfShuffles = 1;
5578
5579         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5580         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5581         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5582
5583         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5584
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5586             p = (int) board[0][i];
5587             if(p < (int) BlackPawn) piecesLeft[p] ++;
5588             board[0][i] = EmptySquare;
5589         }
5590
5591         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5592             // shuffles restricted to allow normal castling put KRR first
5593             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5594                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5596                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5598                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5600                 put(board, WhiteRook, 0, 0, ANY);
5601             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5602         }
5603
5604         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5605             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5606             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5607                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5608                 while(piecesLeft[p] >= 2) {
5609                     AddOnePiece(board, p, 0, LITE);
5610                     AddOnePiece(board, p, 0, DARK);
5611                 }
5612                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5613             }
5614
5615         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5616             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5617             // but we leave King and Rooks for last, to possibly obey FRC restriction
5618             if(p == (int)WhiteRook) continue;
5619             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5620             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5621         }
5622
5623         // now everything is placed, except perhaps King (Unicorn) and Rooks
5624
5625         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5626             // Last King gets castling rights
5627             while(piecesLeft[(int)WhiteUnicorn]) {
5628                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632             while(piecesLeft[(int)WhiteKing]) {
5633                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637
5638         } else {
5639             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5640             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5641         }
5642
5643         // Only Rooks can be left; simply place them all
5644         while(piecesLeft[(int)WhiteRook]) {
5645                 i = put(board, WhiteRook, 0, 0, ANY);
5646                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5647                         if(first) {
5648                                 first=0;
5649                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5650                         }
5651                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5652                 }
5653         }
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5655             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5656         }
5657
5658         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5659 }
5660
5661 int
5662 SetCharTable (char *table, const char * map)
5663 /* [HGM] moved here from winboard.c because of its general usefulness */
5664 /*       Basically a safe strcpy that uses the last character as King */
5665 {
5666     int result = FALSE; int NrPieces;
5667
5668     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5669                     && NrPieces >= 12 && !(NrPieces&1)) {
5670         int i; /* [HGM] Accept even length from 12 to 34 */
5671
5672         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5673         for( i=0; i<NrPieces/2-1; i++ ) {
5674             table[i] = map[i];
5675             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5676         }
5677         table[(int) WhiteKing]  = map[NrPieces/2-1];
5678         table[(int) BlackKing]  = map[NrPieces-1];
5679
5680         result = TRUE;
5681     }
5682
5683     return result;
5684 }
5685
5686 void
5687 Prelude (Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition (int redraw)
5725 {
5726     ChessSquare (* pieces)[BOARD_FILES];
5727     int i, j, pawnRow, overrule,
5728     oldx = gameInfo.boardWidth,
5729     oldy = gameInfo.boardHeight,
5730     oldh = gameInfo.holdingsWidth;
5731     static int oldv;
5732
5733     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5734
5735     /* [AS] Initialize pv info list [HGM] and game status */
5736     {
5737         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5738             pvInfoList[i].depth = 0;
5739             boards[i][EP_STATUS] = EP_NONE;
5740             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5741         }
5742
5743         initialRulePlies = 0; /* 50-move counter start */
5744
5745         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5746         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5747     }
5748
5749
5750     /* [HGM] logic here is completely changed. In stead of full positions */
5751     /* the initialized data only consist of the two backranks. The switch */
5752     /* selects which one we will use, which is than copied to the Board   */
5753     /* initialPosition, which for the rest is initialized by Pawns and    */
5754     /* empty squares. This initial position is then copied to boards[0],  */
5755     /* possibly after shuffling, so that it remains available.            */
5756
5757     gameInfo.holdingsWidth = 0; /* default board sizes */
5758     gameInfo.boardWidth    = 8;
5759     gameInfo.boardHeight   = 8;
5760     gameInfo.holdingsSize  = 0;
5761     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5762     for(i=0; i<BOARD_FILES-2; i++)
5763       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5764     initialPosition[EP_STATUS] = EP_NONE;
5765     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5766     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5767          SetCharTable(pieceNickName, appData.pieceNickNames);
5768     else SetCharTable(pieceNickName, "............");
5769     pieces = FIDEArray;
5770
5771     switch (gameInfo.variant) {
5772     case VariantFischeRandom:
5773       shuffleOpenings = TRUE;
5774     default:
5775       break;
5776     case VariantShatranj:
5777       pieces = ShatranjArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5780       break;
5781     case VariantMakruk:
5782       pieces = makrukArray;
5783       nrCastlingRights = 0;
5784       startedFromSetupPosition = TRUE;
5785       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5786       break;
5787     case VariantTwoKings:
5788       pieces = twoKingsArray;
5789       break;
5790     case VariantGrand:
5791       pieces = GrandArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       gameInfo.boardWidth = 10;
5795       gameInfo.boardHeight = 10;
5796       gameInfo.holdingsSize = 7;
5797       break;
5798     case VariantCapaRandom:
5799       shuffleOpenings = TRUE;
5800     case VariantCapablanca:
5801       pieces = CapablancaArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantGothic:
5806       pieces = GothicArray;
5807       gameInfo.boardWidth = 10;
5808       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5809       break;
5810     case VariantSChess:
5811       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5812       gameInfo.holdingsSize = 7;
5813       break;
5814     case VariantJanus:
5815       pieces = JanusArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5818       nrCastlingRights = 6;
5819         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5822         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5825       break;
5826     case VariantFalcon:
5827       pieces = FalconArray;
5828       gameInfo.boardWidth = 10;
5829       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5830       break;
5831     case VariantXiangqi:
5832       pieces = XiangqiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 10;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5837       break;
5838     case VariantShogi:
5839       pieces = ShogiArray;
5840       gameInfo.boardWidth  = 9;
5841       gameInfo.boardHeight = 9;
5842       gameInfo.holdingsSize = 7;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5845       break;
5846     case VariantCourier:
5847       pieces = CourierArray;
5848       gameInfo.boardWidth  = 12;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5851       break;
5852     case VariantKnightmate:
5853       pieces = KnightmateArray;
5854       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5855       break;
5856     case VariantSpartan:
5857       pieces = SpartanArray;
5858       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5859       break;
5860     case VariantFairy:
5861       pieces = fairyArray;
5862       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5863       break;
5864     case VariantGreat:
5865       pieces = GreatArray;
5866       gameInfo.boardWidth = 10;
5867       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5868       gameInfo.holdingsSize = 8;
5869       break;
5870     case VariantSuper:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5873       gameInfo.holdingsSize = 8;
5874       startedFromSetupPosition = TRUE;
5875       break;
5876     case VariantCrazyhouse:
5877     case VariantBughouse:
5878       pieces = FIDEArray;
5879       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5880       gameInfo.holdingsSize = 5;
5881       break;
5882     case VariantWildCastle:
5883       pieces = FIDEArray;
5884       /* !!?shuffle with kings guaranteed to be on d or e file */
5885       shuffleOpenings = 1;
5886       break;
5887     case VariantNoCastle:
5888       pieces = FIDEArray;
5889       nrCastlingRights = 0;
5890       /* !!?unconstrained back-rank shuffle */
5891       shuffleOpenings = 1;
5892       break;
5893     }
5894
5895     overrule = 0;
5896     if(appData.NrFiles >= 0) {
5897         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5898         gameInfo.boardWidth = appData.NrFiles;
5899     }
5900     if(appData.NrRanks >= 0) {
5901         gameInfo.boardHeight = appData.NrRanks;
5902     }
5903     if(appData.holdingsSize >= 0) {
5904         i = appData.holdingsSize;
5905         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5906         gameInfo.holdingsSize = i;
5907     }
5908     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5909     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5910         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5911
5912     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5913     if(pawnRow < 1) pawnRow = 1;
5914     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5915
5916     /* User pieceToChar list overrules defaults */
5917     if(appData.pieceToCharTable != NULL)
5918         SetCharTable(pieceToChar, appData.pieceToCharTable);
5919
5920     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5921
5922         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5923             s = (ChessSquare) 0; /* account holding counts in guard band */
5924         for( i=0; i<BOARD_HEIGHT; i++ )
5925             initialPosition[i][j] = s;
5926
5927         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5928         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5929         initialPosition[pawnRow][j] = WhitePawn;
5930         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5931         if(gameInfo.variant == VariantXiangqi) {
5932             if(j&1) {
5933                 initialPosition[pawnRow][j] =
5934                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5935                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5936                    initialPosition[2][j] = WhiteCannon;
5937                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5938                 }
5939             }
5940         }
5941         if(gameInfo.variant == VariantGrand) {
5942             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5943                initialPosition[0][j] = WhiteRook;
5944                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5945             }
5946         }
5947         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5948     }
5949     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5950
5951             j=BOARD_LEFT+1;
5952             initialPosition[1][j] = WhiteBishop;
5953             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5954             j=BOARD_RGHT-2;
5955             initialPosition[1][j] = WhiteRook;
5956             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5957     }
5958
5959     if( nrCastlingRights == -1) {
5960         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5961         /*       This sets default castling rights from none to normal corners   */
5962         /* Variants with other castling rights must set them themselves above    */
5963         nrCastlingRights = 6;
5964
5965         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5966         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5967         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5968         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5969         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5970         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5971      }
5972
5973      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5974      if(gameInfo.variant == VariantGreat) { // promotion commoners
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5979      }
5980      if( gameInfo.variant == VariantSChess ) {
5981       initialPosition[1][0] = BlackMarshall;
5982       initialPosition[2][0] = BlackAngel;
5983       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5984       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5985       initialPosition[1][1] = initialPosition[2][1] = 
5986       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5987      }
5988   if (appData.debugMode) {
5989     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5990   }
5991     if(shuffleOpenings) {
5992         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5993         startedFromSetupPosition = TRUE;
5994     }
5995     if(startedFromPositionFile) {
5996       /* [HGM] loadPos: use PositionFile for every new game */
5997       CopyBoard(initialPosition, filePosition);
5998       for(i=0; i<nrCastlingRights; i++)
5999           initialRights[i] = filePosition[CASTLING][i];
6000       startedFromSetupPosition = TRUE;
6001     }
6002
6003     CopyBoard(boards[0], initialPosition);
6004
6005     if(oldx != gameInfo.boardWidth ||
6006        oldy != gameInfo.boardHeight ||
6007        oldv != gameInfo.variant ||
6008        oldh != gameInfo.holdingsWidth
6009                                          )
6010             InitDrawingSizes(-2 ,0);
6011
6012     oldv = gameInfo.variant;
6013     if (redraw)
6014       DrawPosition(TRUE, boards[currentMove]);
6015 }
6016
6017 void
6018 SendBoard (ChessProgramState *cps, int moveNum)
6019 {
6020     char message[MSG_SIZ];
6021
6022     if (cps->useSetboard) {
6023       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6024       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6025       SendToProgram(message, cps);
6026       free(fen);
6027
6028     } else {
6029       ChessSquare *bp;
6030       int i, j, left=0, right=BOARD_WIDTH;
6031       /* Kludge to set black to move, avoiding the troublesome and now
6032        * deprecated "black" command.
6033        */
6034       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6035         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6036
6037       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6038
6039       SendToProgram("edit\n", cps);
6040       SendToProgram("#\n", cps);
6041       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6042         bp = &boards[moveNum][i][left];
6043         for (j = left; j < right; j++, bp++) {
6044           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6045           if ((int) *bp < (int) BlackPawn) {
6046             if(j == BOARD_RGHT+1)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6048             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6049             if(message[0] == '+' || message[0] == '~') {
6050               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6052                         AAA + j, ONE + i);
6053             }
6054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6055                 message[1] = BOARD_RGHT   - 1 - j + '1';
6056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6057             }
6058             SendToProgram(message, cps);
6059           }
6060         }
6061       }
6062
6063       SendToProgram("c\n", cps);
6064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6065         bp = &boards[moveNum][i][left];
6066         for (j = left; j < right; j++, bp++) {
6067           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6068           if (((int) *bp != (int) EmptySquare)
6069               && ((int) *bp >= (int) BlackPawn)) {
6070             if(j == BOARD_LEFT-2)
6071                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6072             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6073                     AAA + j, ONE + i);
6074             if(message[0] == '+' || message[0] == '~') {
6075               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6076                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6077                         AAA + j, ONE + i);
6078             }
6079             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6080                 message[1] = BOARD_RGHT   - 1 - j + '1';
6081                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082             }
6083             SendToProgram(message, cps);
6084           }
6085         }
6086       }
6087
6088       SendToProgram(".\n", cps);
6089     }
6090     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6091 }
6092
6093 ChessSquare
6094 DefaultPromoChoice (int white)
6095 {
6096     ChessSquare result;
6097     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6098         result = WhiteFerz; // no choice
6099     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6100         result= WhiteKing; // in Suicide Q is the last thing we want
6101     else if(gameInfo.variant == VariantSpartan)
6102         result = white ? WhiteQueen : WhiteAngel;
6103     else result = WhiteQueen;
6104     if(!white) result = WHITE_TO_BLACK result;
6105     return result;
6106 }
6107
6108 static int autoQueen; // [HGM] oneclick
6109
6110 int
6111 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6112 {
6113     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6114     /* [HGM] add Shogi promotions */
6115     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6116     ChessSquare piece;
6117     ChessMove moveType;
6118     Boolean premove;
6119
6120     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6121     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6122
6123     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6124       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6125         return FALSE;
6126
6127     piece = boards[currentMove][fromY][fromX];
6128     if(gameInfo.variant == VariantShogi) {
6129         promotionZoneSize = BOARD_HEIGHT/3;
6130         highestPromotingPiece = (int)WhiteFerz;
6131     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6132         promotionZoneSize = 3;
6133     }
6134
6135     // Treat Lance as Pawn when it is not representing Amazon
6136     if(gameInfo.variant != VariantSuper) {
6137         if(piece == WhiteLance) piece = WhitePawn; else
6138         if(piece == BlackLance) piece = BlackPawn;
6139     }
6140
6141     // next weed out all moves that do not touch the promotion zone at all
6142     if((int)piece >= BlackPawn) {
6143         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6144              return FALSE;
6145         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6146     } else {
6147         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6148            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6149     }
6150
6151     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6152
6153     // weed out mandatory Shogi promotions
6154     if(gameInfo.variant == VariantShogi) {
6155         if(piece >= BlackPawn) {
6156             if(toY == 0 && piece == BlackPawn ||
6157                toY == 0 && piece == BlackQueen ||
6158                toY <= 1 && piece == BlackKnight) {
6159                 *promoChoice = '+';
6160                 return FALSE;
6161             }
6162         } else {
6163             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6164                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6165                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6166                 *promoChoice = '+';
6167                 return FALSE;
6168             }
6169         }
6170     }
6171
6172     // weed out obviously illegal Pawn moves
6173     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6174         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6175         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6176         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6177         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6178         // note we are not allowed to test for valid (non-)capture, due to premove
6179     }
6180
6181     // we either have a choice what to promote to, or (in Shogi) whether to promote
6182     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6183         *promoChoice = PieceToChar(BlackFerz);  // no choice
6184         return FALSE;
6185     }
6186     // no sense asking what we must promote to if it is going to explode...
6187     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6188         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6189         return FALSE;
6190     }
6191     // give caller the default choice even if we will not make it
6192     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6193     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6194     if(        sweepSelect && gameInfo.variant != VariantGreat
6195                            && gameInfo.variant != VariantGrand
6196                            && gameInfo.variant != VariantSuper) return FALSE;
6197     if(autoQueen) return FALSE; // predetermined
6198
6199     // suppress promotion popup on illegal moves that are not premoves
6200     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6201               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6202     if(appData.testLegality && !premove) {
6203         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6204                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6205         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6206             return FALSE;
6207     }
6208
6209     return TRUE;
6210 }
6211
6212 int
6213 InPalace (int row, int column)
6214 {   /* [HGM] for Xiangqi */
6215     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6216          column < (BOARD_WIDTH + 4)/2 &&
6217          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6218     return FALSE;
6219 }
6220
6221 int
6222 PieceForSquare (int x, int y)
6223 {
6224   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6225      return -1;
6226   else
6227      return boards[currentMove][y][x];
6228 }
6229
6230 int
6231 OKToStartUserMove (int x, int y)
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove (int *x, int *y, Boolean captures) 
6332 {
6333     DisambiguateClosure cl;
6334     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6335     switch(gameMode) {
6336       case MachinePlaysBlack:
6337       case IcsPlayingWhite:
6338       case BeginningOfGame:
6339         if(!WhiteOnMove(currentMove)) return FALSE;
6340         break;
6341       case MachinePlaysWhite:
6342       case IcsPlayingBlack:
6343         if(WhiteOnMove(currentMove)) return FALSE;
6344         break;
6345       case EditGame:
6346         break;
6347       default:
6348         return FALSE;
6349     }
6350     cl.pieceIn = EmptySquare;
6351     cl.rfIn = *y;
6352     cl.ffIn = *x;
6353     cl.rtIn = -1;
6354     cl.ftIn = -1;
6355     cl.promoCharIn = NULLCHAR;
6356     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6357     if( cl.kind == NormalMove ||
6358         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6359         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6360         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6361       fromX = cl.ff;
6362       fromY = cl.rf;
6363       *x = cl.ft;
6364       *y = cl.rt;
6365       return TRUE;
6366     }
6367     if(cl.kind != ImpossibleMove) return FALSE;
6368     cl.pieceIn = EmptySquare;
6369     cl.rfIn = -1;
6370     cl.ffIn = -1;
6371     cl.rtIn = *y;
6372     cl.ftIn = *x;
6373     cl.promoCharIn = NULLCHAR;
6374     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6375     if( cl.kind == NormalMove ||
6376         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6377         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6378         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6379       fromX = cl.ff;
6380       fromY = cl.rf;
6381       *x = cl.ft;
6382       *y = cl.rt;
6383       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384       return TRUE;
6385     }
6386     return FALSE;
6387 }
6388
6389 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6390 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6391 int lastLoadGameUseList = FALSE;
6392 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6393 ChessMove lastLoadGameStart = EndOfFile;
6394
6395 void
6396 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6397 {
6398     ChessMove moveType;
6399     ChessSquare pdown, pup;
6400
6401     /* Check if the user is playing in turn.  This is complicated because we
6402        let the user "pick up" a piece before it is his turn.  So the piece he
6403        tried to pick up may have been captured by the time he puts it down!
6404        Therefore we use the color the user is supposed to be playing in this
6405        test, not the color of the piece that is currently on the starting
6406        square---except in EditGame mode, where the user is playing both
6407        sides; fortunately there the capture race can't happen.  (It can
6408        now happen in IcsExamining mode, but that's just too bad.  The user
6409        will get a somewhat confusing message in that case.)
6410        */
6411
6412     switch (gameMode) {
6413       case AnalyzeFile:
6414       case TwoMachinesPlay:
6415       case EndOfGame:
6416       case IcsObserving:
6417       case IcsIdle:
6418         /* We switched into a game mode where moves are not accepted,
6419            perhaps while the mouse button was down. */
6420         return;
6421
6422       case MachinePlaysWhite:
6423         /* User is moving for Black */
6424         if (WhiteOnMove(currentMove)) {
6425             DisplayMoveError(_("It is White's turn"));
6426             return;
6427         }
6428         break;
6429
6430       case MachinePlaysBlack:
6431         /* User is moving for White */
6432         if (!WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is Black's turn"));
6434             return;
6435         }
6436         break;
6437
6438       case PlayFromGameFile:
6439             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6440       case EditGame:
6441       case IcsExamining:
6442       case BeginningOfGame:
6443       case AnalyzeMode:
6444       case Training:
6445         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6446         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6447             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6448             /* User is moving for Black */
6449             if (WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is White's turn"));
6451                 return;
6452             }
6453         } else {
6454             /* User is moving for White */
6455             if (!WhiteOnMove(currentMove)) {
6456                 DisplayMoveError(_("It is Black's turn"));
6457                 return;
6458             }
6459         }
6460         break;
6461
6462       case IcsPlayingBlack:
6463         /* User is moving for Black */
6464         if (WhiteOnMove(currentMove)) {
6465             if (!appData.premove) {
6466                 DisplayMoveError(_("It is White's turn"));
6467             } else if (toX >= 0 && toY >= 0) {
6468                 premoveToX = toX;
6469                 premoveToY = toY;
6470                 premoveFromX = fromX;
6471                 premoveFromY = fromY;
6472                 premovePromoChar = promoChar;
6473                 gotPremove = 1;
6474                 if (appData.debugMode)
6475                     fprintf(debugFP, "Got premove: fromX %d,"
6476                             "fromY %d, toX %d, toY %d\n",
6477                             fromX, fromY, toX, toY);
6478             }
6479             return;
6480         }
6481         break;
6482
6483       case IcsPlayingWhite:
6484         /* User is moving for White */
6485         if (!WhiteOnMove(currentMove)) {
6486             if (!appData.premove) {
6487                 DisplayMoveError(_("It is Black's turn"));
6488             } else if (toX >= 0 && toY >= 0) {
6489                 premoveToX = toX;
6490                 premoveToY = toY;
6491                 premoveFromX = fromX;
6492                 premoveFromY = fromY;
6493                 premovePromoChar = promoChar;
6494                 gotPremove = 1;
6495                 if (appData.debugMode)
6496                     fprintf(debugFP, "Got premove: fromX %d,"
6497                             "fromY %d, toX %d, toY %d\n",
6498                             fromX, fromY, toX, toY);
6499             }
6500             return;
6501         }
6502         break;
6503
6504       default:
6505         break;
6506
6507       case EditPosition:
6508         /* EditPosition, empty square, or different color piece;
6509            click-click move is possible */
6510         if (toX == -2 || toY == -2) {
6511             boards[0][fromY][fromX] = EmptySquare;
6512             DrawPosition(FALSE, boards[currentMove]);
6513             return;
6514         } else if (toX >= 0 && toY >= 0) {
6515             boards[0][toY][toX] = boards[0][fromY][fromX];
6516             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6517                 if(boards[0][fromY][0] != EmptySquare) {
6518                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6519                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6520                 }
6521             } else
6522             if(fromX == BOARD_RGHT+1) {
6523                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6524                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6525                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6526                 }
6527             } else
6528             boards[0][fromY][fromX] = EmptySquare;
6529             DrawPosition(FALSE, boards[currentMove]);
6530             return;
6531         }
6532         return;
6533     }
6534
6535     if(toX < 0 || toY < 0) return;
6536     pdown = boards[currentMove][fromY][fromX];
6537     pup = boards[currentMove][toY][toX];
6538
6539     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6540     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6541          if( pup != EmptySquare ) return;
6542          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6543            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6544                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6545            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6546            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6547            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6548            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6549          fromY = DROP_RANK;
6550     }
6551
6552     /* [HGM] always test for legality, to get promotion info */
6553     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6554                                          fromY, fromX, toY, toX, promoChar);
6555
6556     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6557
6558     /* [HGM] but possibly ignore an IllegalMove result */
6559     if (appData.testLegality) {
6560         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6561             DisplayMoveError(_("Illegal move"));
6562             return;
6563         }
6564     }
6565
6566     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6567 }
6568
6569 /* Common tail of UserMoveEvent and DropMenuEvent */
6570 int
6571 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6572 {
6573     char *bookHit = 0;
6574
6575     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6576         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6577         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6578         if(WhiteOnMove(currentMove)) {
6579             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6580         } else {
6581             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6582         }
6583     }
6584
6585     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6586        move type in caller when we know the move is a legal promotion */
6587     if(moveType == NormalMove && promoChar)
6588         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6589
6590     /* [HGM] <popupFix> The following if has been moved here from
6591        UserMoveEvent(). Because it seemed to belong here (why not allow
6592        piece drops in training games?), and because it can only be
6593        performed after it is known to what we promote. */
6594     if (gameMode == Training) {
6595       /* compare the move played on the board to the next move in the
6596        * game. If they match, display the move and the opponent's response.
6597        * If they don't match, display an error message.
6598        */
6599       int saveAnimate;
6600       Board testBoard;
6601       CopyBoard(testBoard, boards[currentMove]);
6602       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6603
6604       if (CompareBoards(testBoard, boards[currentMove+1])) {
6605         ForwardInner(currentMove+1);
6606
6607         /* Autoplay the opponent's response.
6608          * if appData.animate was TRUE when Training mode was entered,
6609          * the response will be animated.
6610          */
6611         saveAnimate = appData.animate;
6612         appData.animate = animateTraining;
6613         ForwardInner(currentMove+1);
6614         appData.animate = saveAnimate;
6615
6616         /* check for the end of the game */
6617         if (currentMove >= forwardMostMove) {
6618           gameMode = PlayFromGameFile;
6619           ModeHighlight();
6620           SetTrainingModeOff();
6621           DisplayInformation(_("End of game"));
6622         }
6623       } else {
6624         DisplayError(_("Incorrect move"), 0);
6625       }
6626       return 1;
6627     }
6628
6629   /* Ok, now we know that the move is good, so we can kill
6630      the previous line in Analysis Mode */
6631   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6632                                 && currentMove < forwardMostMove) {
6633     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6634     else forwardMostMove = currentMove;
6635   }
6636
6637   /* If we need the chess program but it's dead, restart it */
6638   ResurrectChessProgram();
6639
6640   /* A user move restarts a paused game*/
6641   if (pausing)
6642     PauseEvent();
6643
6644   thinkOutput[0] = NULLCHAR;
6645
6646   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6647
6648   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6649     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650     return 1;
6651   }
6652
6653   if (gameMode == BeginningOfGame) {
6654     if (appData.noChessProgram) {
6655       gameMode = EditGame;
6656       SetGameInfo();
6657     } else {
6658       char buf[MSG_SIZ];
6659       gameMode = MachinePlaysBlack;
6660       StartClocks();
6661       SetGameInfo();
6662       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6663       DisplayTitle(buf);
6664       if (first.sendName) {
6665         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6666         SendToProgram(buf, &first);
6667       }
6668       StartClocks();
6669     }
6670     ModeHighlight();
6671   }
6672
6673   /* Relay move to ICS or chess engine */
6674   if (appData.icsActive) {
6675     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6676         gameMode == IcsExamining) {
6677       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6678         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6679         SendToICS("draw ");
6680         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       }
6682       // also send plain move, in case ICS does not understand atomic claims
6683       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6684       ics_user_moved = 1;
6685     }
6686   } else {
6687     if (first.sendTime && (gameMode == BeginningOfGame ||
6688                            gameMode == MachinePlaysWhite ||
6689                            gameMode == MachinePlaysBlack)) {
6690       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6691     }
6692     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6693          // [HGM] book: if program might be playing, let it use book
6694         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6695         first.maybeThinking = TRUE;
6696     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6697         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6698         SendBoard(&first, currentMove+1);
6699     } else SendMoveToProgram(forwardMostMove-1, &first);
6700     if (currentMove == cmailOldMove + 1) {
6701       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6702     }
6703   }
6704
6705   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706
6707   switch (gameMode) {
6708   case EditGame:
6709     if(appData.testLegality)
6710     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6711     case MT_NONE:
6712     case MT_CHECK:
6713       break;
6714     case MT_CHECKMATE:
6715     case MT_STAINMATE:
6716       if (WhiteOnMove(currentMove)) {
6717         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6718       } else {
6719         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6720       }
6721       break;
6722     case MT_STALEMATE:
6723       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6724       break;
6725     }
6726     break;
6727
6728   case MachinePlaysBlack:
6729   case MachinePlaysWhite:
6730     /* disable certain menu options while machine is thinking */
6731     SetMachineThinkingEnables();
6732     break;
6733
6734   default:
6735     break;
6736   }
6737
6738   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6739   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6740
6741   if(bookHit) { // [HGM] book: simulate book reply
6742         static char bookMove[MSG_SIZ]; // a bit generous?
6743
6744         programStats.nodes = programStats.depth = programStats.time =
6745         programStats.score = programStats.got_only_move = 0;
6746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6747
6748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6749         strcat(bookMove, bookHit);
6750         HandleMachineMove(bookMove, &first);
6751   }
6752   return 1;
6753 }
6754
6755 void
6756 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6757 {
6758     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6759     Markers *m = (Markers *) closure;
6760     if(rf == fromY && ff == fromX)
6761         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6762                          || kind == WhiteCapturesEnPassant
6763                          || kind == BlackCapturesEnPassant);
6764     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6765 }
6766
6767 void
6768 MarkTargetSquares (int clear)
6769 {
6770   int x, y;
6771   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6772      !appData.testLegality || gameMode == EditPosition) return;
6773   if(clear) {
6774     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6775   } else {
6776     int capt = 0;
6777     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6778     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6780       if(capt)
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6782     }
6783   }
6784   DrawPosition(TRUE, NULL);
6785 }
6786
6787 int
6788 Explode (Board board, int fromX, int fromY, int toX, int toY)
6789 {
6790     if(gameInfo.variant == VariantAtomic &&
6791        (board[toY][toX] != EmptySquare ||                     // capture?
6792         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6793                          board[fromY][fromX] == BlackPawn   )
6794       )) {
6795         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6796         return TRUE;
6797     }
6798     return FALSE;
6799 }
6800
6801 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6802
6803 int
6804 CanPromote (ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void
6819 LeftClick (ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int
7118 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void
7209 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane (int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings (int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights (Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate (ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566
7567         // Repetition draws and 50-move rule can be applied independently of legality testing
7568
7569                 /* Check for rep-draws */
7570                 count = 0;
7571                 for(k = forwardMostMove-2;
7572                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7573                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7574                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7575                     k-=2)
7576                 {   int rights=0;
7577                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7578                         /* compare castling rights */
7579                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7580                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7581                                 rights++; /* King lost rights, while rook still had them */
7582                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7583                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7584                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7585                                    rights++; /* but at least one rook lost them */
7586                         }
7587                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7588                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7589                                 rights++;
7590                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7591                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7592                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7593                                    rights++;
7594                         }
7595                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7596                             && appData.drawRepeats > 1) {
7597                              /* adjudicate after user-specified nr of repeats */
7598                              int result = GameIsDrawn;
7599                              char *details = "XBoard adjudication: repetition draw";
7600                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7601                                 // [HGM] xiangqi: check for forbidden perpetuals
7602                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7603                                 for(m=forwardMostMove; m>k; m-=2) {
7604                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7605                                         ourPerpetual = 0; // the current mover did not always check
7606                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7607                                         hisPerpetual = 0; // the opponent did not always check
7608                                 }
7609                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7610                                                                         ourPerpetual, hisPerpetual);
7611                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7612                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7613                                     details = "Xboard adjudication: perpetual checking";
7614                                 } else
7615                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7616                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7617                                 } else
7618                                 // Now check for perpetual chases
7619                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7620                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7621                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7622                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7623                                         static char resdet[MSG_SIZ];
7624                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7625                                         details = resdet;
7626                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7627                                     } else
7628                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7629                                         break; // Abort repetition-checking loop.
7630                                 }
7631                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7632                              }
7633                              if(engineOpponent) {
7634                                SendToProgram("force\n", engineOpponent); // suppress reply
7635                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7636                              }
7637                              GameEnds( result, details, GE_XBOARD );
7638                              return 1;
7639                         }
7640                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7641                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7642                     }
7643                 }
7644
7645                 /* Now we test for 50-move draws. Determine ply count */
7646                 count = forwardMostMove;
7647                 /* look for last irreversble move */
7648                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7649                     count--;
7650                 /* if we hit starting position, add initial plies */
7651                 if( count == backwardMostMove )
7652                     count -= initialRulePlies;
7653                 count = forwardMostMove - count;
7654                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7655                         // adjust reversible move counter for checks in Xiangqi
7656                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7657                         if(i < backwardMostMove) i = backwardMostMove;
7658                         while(i <= forwardMostMove) {
7659                                 lastCheck = inCheck; // check evasion does not count
7660                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7661                                 if(inCheck || lastCheck) count--; // check does not count
7662                                 i++;
7663                         }
7664                 }
7665                 if( count >= 100)
7666                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7667                          /* this is used to judge if draw claims are legal */
7668                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7669                          if(engineOpponent) {
7670                            SendToProgram("force\n", engineOpponent); // suppress reply
7671                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7672                          }
7673                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7674                          return 1;
7675                 }
7676
7677                 /* if draw offer is pending, treat it as a draw claim
7678                  * when draw condition present, to allow engines a way to
7679                  * claim draws before making their move to avoid a race
7680                  * condition occurring after their move
7681                  */
7682                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7683                          char *p = NULL;
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7685                              p = "Draw claim: 50-move rule";
7686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7687                              p = "Draw claim: 3-fold repetition";
7688                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7689                              p = "Draw claim: insufficient mating material";
7690                          if( p != NULL && canAdjudicate) {
7691                              if(engineOpponent) {
7692                                SendToProgram("force\n", engineOpponent); // suppress reply
7693                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7694                              }
7695                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7696                              return 1;
7697                          }
7698                 }
7699
7700                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7701                     if(engineOpponent) {
7702                       SendToProgram("force\n", engineOpponent); // suppress reply
7703                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7704                     }
7705                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7706                     return 1;
7707                 }
7708         return 0;
7709 }
7710
7711 char *
7712 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7713 {   // [HGM] book: this routine intercepts moves to simulate book replies
7714     char *bookHit = NULL;
7715
7716     //first determine if the incoming move brings opponent into his book
7717     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7718         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7719     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7720     if(bookHit != NULL && !cps->bookSuspend) {
7721         // make sure opponent is not going to reply after receiving move to book position
7722         SendToProgram("force\n", cps);
7723         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7724     }
7725     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7726     // now arrange restart after book miss
7727     if(bookHit) {
7728         // after a book hit we never send 'go', and the code after the call to this routine
7729         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7730         char buf[MSG_SIZ], *move = bookHit;
7731         if(cps->useSAN) {
7732             int fromX, fromY, toX, toY;
7733             char promoChar;
7734             ChessMove moveType;
7735             move = buf + 30;
7736             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7737                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7738                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7739                                     PosFlags(forwardMostMove),
7740                                     fromY, fromX, toY, toX, promoChar, move);
7741             } else {
7742                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7743                 bookHit = NULL;
7744             }
7745         }
7746         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7747         SendToProgram(buf, cps);
7748         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7749     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7750         SendToProgram("go\n", cps);
7751         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7752     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7753         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7754             SendToProgram("go\n", cps);
7755         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7756     }
7757     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7758 }
7759
7760 char *savedMessage;
7761 ChessProgramState *savedState;
7762 void
7763 DeferredBookMove (void)
7764 {
7765         if(savedState->lastPing != savedState->lastPong)
7766                     ScheduleDelayedEvent(DeferredBookMove, 10);
7767         else
7768         HandleMachineMove(savedMessage, savedState);
7769 }
7770
7771 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7772
7773 void
7774 HandleMachineMove (char *message, ChessProgramState *cps)
7775 {
7776     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7777     char realname[MSG_SIZ];
7778     int fromX, fromY, toX, toY;
7779     ChessMove moveType;
7780     char promoChar;
7781     char *p, *pv=buf1;
7782     int machineWhite;
7783     char *bookHit;
7784
7785     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7786         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7787         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7788             DisplayError(_("Invalid pairing from pairing engine"), 0);
7789             return;
7790         }
7791         pairingReceived = 1;
7792         NextMatchGame();
7793         return; // Skim the pairing messages here.
7794     }
7795
7796     cps->userError = 0;
7797
7798 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7799     /*
7800      * Kludge to ignore BEL characters
7801      */
7802     while (*message == '\007') message++;
7803
7804     /*
7805      * [HGM] engine debug message: ignore lines starting with '#' character
7806      */
7807     if(cps->debug && *message == '#') return;
7808
7809     /*
7810      * Look for book output
7811      */
7812     if (cps == &first && bookRequested) {
7813         if (message[0] == '\t' || message[0] == ' ') {
7814             /* Part of the book output is here; append it */
7815             strcat(bookOutput, message);
7816             strcat(bookOutput, "  \n");
7817             return;
7818         } else if (bookOutput[0] != NULLCHAR) {
7819             /* All of book output has arrived; display it */
7820             char *p = bookOutput;
7821             while (*p != NULLCHAR) {
7822                 if (*p == '\t') *p = ' ';
7823                 p++;
7824             }
7825             DisplayInformation(bookOutput);
7826             bookRequested = FALSE;
7827             /* Fall through to parse the current output */
7828         }
7829     }
7830
7831     /*
7832      * Look for machine move.
7833      */
7834     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7835         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7836     {
7837         /* This method is only useful on engines that support ping */
7838         if (cps->lastPing != cps->lastPong) {
7839           if (gameMode == BeginningOfGame) {
7840             /* Extra move from before last new; ignore */
7841             if (appData.debugMode) {
7842                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7843             }
7844           } else {
7845             if (appData.debugMode) {
7846                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7847                         cps->which, gameMode);
7848             }
7849
7850             SendToProgram("undo\n", cps);
7851           }
7852           return;
7853         }
7854
7855         switch (gameMode) {
7856           case BeginningOfGame:
7857             /* Extra move from before last reset; ignore */
7858             if (appData.debugMode) {
7859                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7860             }
7861             return;
7862
7863           case EndOfGame:
7864           case IcsIdle:
7865           default:
7866             /* Extra move after we tried to stop.  The mode test is
7867                not a reliable way of detecting this problem, but it's
7868                the best we can do on engines that don't support ping.
7869             */
7870             if (appData.debugMode) {
7871                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7872                         cps->which, gameMode);
7873             }
7874             SendToProgram("undo\n", cps);
7875             return;
7876
7877           case MachinePlaysWhite:
7878           case IcsPlayingWhite:
7879             machineWhite = TRUE;
7880             break;
7881
7882           case MachinePlaysBlack:
7883           case IcsPlayingBlack:
7884             machineWhite = FALSE;
7885             break;
7886
7887           case TwoMachinesPlay:
7888             machineWhite = (cps->twoMachinesColor[0] == 'w');
7889             break;
7890         }
7891         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7892             if (appData.debugMode) {
7893                 fprintf(debugFP,
7894                         "Ignoring move out of turn by %s, gameMode %d"
7895                         ", forwardMost %d\n",
7896                         cps->which, gameMode, forwardMostMove);
7897             }
7898             return;
7899         }
7900
7901         if(cps->alphaRank) AlphaRank(machineMove, 4);
7902         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7903                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7904             /* Machine move could not be parsed; ignore it. */
7905           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7906                     machineMove, _(cps->which));
7907             DisplayError(buf1, 0);
7908             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7909                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7910             if (gameMode == TwoMachinesPlay) {
7911               GameEnds(machineWhite ? BlackWins : WhiteWins,
7912                        buf1, GE_XBOARD);
7913             }
7914             return;
7915         }
7916
7917         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7918         /* So we have to redo legality test with true e.p. status here,  */
7919         /* to make sure an illegal e.p. capture does not slip through,   */
7920         /* to cause a forfeit on a justified illegal-move complaint      */
7921         /* of the opponent.                                              */
7922         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7923            ChessMove moveType;
7924            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7925                              fromY, fromX, toY, toX, promoChar);
7926             if(moveType == IllegalMove) {
7927               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7928                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7929                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7930                            buf1, GE_XBOARD);
7931                 return;
7932            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7933            /* [HGM] Kludge to handle engines that send FRC-style castling
7934               when they shouldn't (like TSCP-Gothic) */
7935            switch(moveType) {
7936              case WhiteASideCastleFR:
7937              case BlackASideCastleFR:
7938                toX+=2;
7939                currentMoveString[2]++;
7940                break;
7941              case WhiteHSideCastleFR:
7942              case BlackHSideCastleFR:
7943                toX--;
7944                currentMoveString[2]--;
7945                break;
7946              default: ; // nothing to do, but suppresses warning of pedantic compilers
7947            }
7948         }
7949         hintRequested = FALSE;
7950         lastHint[0] = NULLCHAR;
7951         bookRequested = FALSE;
7952         /* Program may be pondering now */
7953         cps->maybeThinking = TRUE;
7954         if (cps->sendTime == 2) cps->sendTime = 1;
7955         if (cps->offeredDraw) cps->offeredDraw--;
7956
7957         /* [AS] Save move info*/
7958         pvInfoList[ forwardMostMove ].score = programStats.score;
7959         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7960         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7961
7962         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7963
7964         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7965         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7966             int count = 0;
7967
7968             while( count < adjudicateLossPlies ) {
7969                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7970
7971                 if( count & 1 ) {
7972                     score = -score; /* Flip score for winning side */
7973                 }
7974
7975                 if( score > adjudicateLossThreshold ) {
7976                     break;
7977                 }
7978
7979                 count++;
7980             }
7981
7982             if( count >= adjudicateLossPlies ) {
7983                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7984
7985                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7986                     "Xboard adjudication",
7987                     GE_XBOARD );
7988
7989                 return;
7990             }
7991         }
7992
7993         if(Adjudicate(cps)) {
7994             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7995             return; // [HGM] adjudicate: for all automatic game ends
7996         }
7997
7998 #if ZIPPY
7999         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8000             first.initDone) {
8001           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8002                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8003                 SendToICS("draw ");
8004                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8005           }
8006           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8007           ics_user_moved = 1;
8008           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8009                 char buf[3*MSG_SIZ];
8010
8011                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8012                         programStats.score / 100.,
8013                         programStats.depth,
8014                         programStats.time / 100.,
8015                         (unsigned int)programStats.nodes,
8016                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8017                         programStats.movelist);
8018                 SendToICS(buf);
8019 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8020           }
8021         }
8022 #endif
8023
8024         /* [AS] Clear stats for next move */
8025         ClearProgramStats();
8026         thinkOutput[0] = NULLCHAR;
8027         hiddenThinkOutputState = 0;
8028
8029         bookHit = NULL;
8030         if (gameMode == TwoMachinesPlay) {
8031             /* [HGM] relaying draw offers moved to after reception of move */
8032             /* and interpreting offer as claim if it brings draw condition */
8033             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8034                 SendToProgram("draw\n", cps->other);
8035             }
8036             if (cps->other->sendTime) {
8037                 SendTimeRemaining(cps->other,
8038                                   cps->other->twoMachinesColor[0] == 'w');
8039             }
8040             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8041             if (firstMove && !bookHit) {
8042                 firstMove = FALSE;
8043                 if (cps->other->useColors) {
8044                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8045                 }
8046                 SendToProgram("go\n", cps->other);
8047             }
8048             cps->other->maybeThinking = TRUE;
8049         }
8050
8051         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8052
8053         if (!pausing && appData.ringBellAfterMoves) {
8054             RingBell();
8055         }
8056
8057         /*
8058          * Reenable menu items that were disabled while
8059          * machine was thinking
8060          */
8061         if (gameMode != TwoMachinesPlay)
8062             SetUserThinkingEnables();
8063
8064         // [HGM] book: after book hit opponent has received move and is now in force mode
8065         // force the book reply into it, and then fake that it outputted this move by jumping
8066         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8067         if(bookHit) {
8068                 static char bookMove[MSG_SIZ]; // a bit generous?
8069
8070                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8071                 strcat(bookMove, bookHit);
8072                 message = bookMove;
8073                 cps = cps->other;
8074                 programStats.nodes = programStats.depth = programStats.time =
8075                 programStats.score = programStats.got_only_move = 0;
8076                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8077
8078                 if(cps->lastPing != cps->lastPong) {
8079                     savedMessage = message; // args for deferred call
8080                     savedState = cps;
8081                     ScheduleDelayedEvent(DeferredBookMove, 10);
8082                     return;
8083                 }
8084                 goto FakeBookMove;
8085         }
8086
8087         return;
8088     }
8089
8090     /* Set special modes for chess engines.  Later something general
8091      *  could be added here; for now there is just one kludge feature,
8092      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8093      *  when "xboard" is given as an interactive command.
8094      */
8095     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8096         cps->useSigint = FALSE;
8097         cps->useSigterm = FALSE;
8098     }
8099     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8100       ParseFeatures(message+8, cps);
8101       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8102     }
8103
8104     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8105                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8106       int dummy, s=6; char buf[MSG_SIZ];
8107       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8108       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8109       if(startedFromSetupPosition) return;
8110       ParseFEN(boards[0], &dummy, message+s);
8111       DrawPosition(TRUE, boards[0]);
8112       startedFromSetupPosition = TRUE;
8113       return;
8114     }
8115     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8116      * want this, I was asked to put it in, and obliged.
8117      */
8118     if (!strncmp(message, "setboard ", 9)) {
8119         Board initial_position;
8120
8121         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8122
8123         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8124             DisplayError(_("Bad FEN received from engine"), 0);
8125             return ;
8126         } else {
8127            Reset(TRUE, FALSE);
8128            CopyBoard(boards[0], initial_position);
8129            initialRulePlies = FENrulePlies;
8130            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8131            else gameMode = MachinePlaysBlack;
8132            DrawPosition(FALSE, boards[currentMove]);
8133         }
8134         return;
8135     }
8136
8137     /*
8138      * Look for communication commands
8139      */
8140     if (!strncmp(message, "telluser ", 9)) {
8141         if(message[9] == '\\' && message[10] == '\\')
8142             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8143         PlayTellSound();
8144         DisplayNote(message + 9);
8145         return;
8146     }
8147     if (!strncmp(message, "tellusererror ", 14)) {
8148         cps->userError = 1;
8149         if(message[14] == '\\' && message[15] == '\\')
8150             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8151         PlayTellSound();
8152         DisplayError(message + 14, 0);
8153         return;
8154     }
8155     if (!strncmp(message, "tellopponent ", 13)) {
8156       if (appData.icsActive) {
8157         if (loggedOn) {
8158           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8159           SendToICS(buf1);
8160         }
8161       } else {
8162         DisplayNote(message + 13);
8163       }
8164       return;
8165     }
8166     if (!strncmp(message, "tellothers ", 11)) {
8167       if (appData.icsActive) {
8168         if (loggedOn) {
8169           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8170           SendToICS(buf1);
8171         }
8172       }
8173       return;
8174     }
8175     if (!strncmp(message, "tellall ", 8)) {
8176       if (appData.icsActive) {
8177         if (loggedOn) {
8178           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8179           SendToICS(buf1);
8180         }
8181       } else {
8182         DisplayNote(message + 8);
8183       }
8184       return;
8185     }
8186     if (strncmp(message, "warning", 7) == 0) {
8187         /* Undocumented feature, use tellusererror in new code */
8188         DisplayError(message, 0);
8189         return;
8190     }
8191     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8192         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8193         strcat(realname, " query");
8194         AskQuestion(realname, buf2, buf1, cps->pr);
8195         return;
8196     }
8197     /* Commands from the engine directly to ICS.  We don't allow these to be
8198      *  sent until we are logged on. Crafty kibitzes have been known to
8199      *  interfere with the login process.
8200      */
8201     if (loggedOn) {
8202         if (!strncmp(message, "tellics ", 8)) {
8203             SendToICS(message + 8);
8204             SendToICS("\n");
8205             return;
8206         }
8207         if (!strncmp(message, "tellicsnoalias ", 15)) {
8208             SendToICS(ics_prefix);
8209             SendToICS(message + 15);
8210             SendToICS("\n");
8211             return;
8212         }
8213         /* The following are for backward compatibility only */
8214         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8215             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8216             SendToICS(ics_prefix);
8217             SendToICS(message);
8218             SendToICS("\n");
8219             return;
8220         }
8221     }
8222     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8223         return;
8224     }
8225     /*
8226      * If the move is illegal, cancel it and redraw the board.
8227      * Also deal with other error cases.  Matching is rather loose
8228      * here to accommodate engines written before the spec.
8229      */
8230     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8231         strncmp(message, "Error", 5) == 0) {
8232         if (StrStr(message, "name") ||
8233             StrStr(message, "rating") || StrStr(message, "?") ||
8234             StrStr(message, "result") || StrStr(message, "board") ||
8235             StrStr(message, "bk") || StrStr(message, "computer") ||
8236             StrStr(message, "variant") || StrStr(message, "hint") ||
8237             StrStr(message, "random") || StrStr(message, "depth") ||
8238             StrStr(message, "accepted")) {
8239             return;
8240         }
8241         if (StrStr(message, "protover")) {
8242           /* Program is responding to input, so it's apparently done
8243              initializing, and this error message indicates it is
8244              protocol version 1.  So we don't need to wait any longer
8245              for it to initialize and send feature commands. */
8246           FeatureDone(cps, 1);
8247           cps->protocolVersion = 1;
8248           return;
8249         }
8250         cps->maybeThinking = FALSE;
8251
8252         if (StrStr(message, "draw")) {
8253             /* Program doesn't have "draw" command */
8254             cps->sendDrawOffers = 0;
8255             return;
8256         }
8257         if (cps->sendTime != 1 &&
8258             (StrStr(message, "time") || StrStr(message, "otim"))) {
8259           /* Program apparently doesn't have "time" or "otim" command */
8260           cps->sendTime = 0;
8261           return;
8262         }
8263         if (StrStr(message, "analyze")) {
8264             cps->analysisSupport = FALSE;
8265             cps->analyzing = FALSE;
8266 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8267             EditGameEvent(); // [HGM] try to preserve loaded game
8268             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8269             DisplayError(buf2, 0);
8270             return;
8271         }
8272         if (StrStr(message, "(no matching move)st")) {
8273           /* Special kludge for GNU Chess 4 only */
8274           cps->stKludge = TRUE;
8275           SendTimeControl(cps, movesPerSession, timeControl,
8276                           timeIncrement, appData.searchDepth,
8277                           searchTime);
8278           return;
8279         }
8280         if (StrStr(message, "(no matching move)sd")) {
8281           /* Special kludge for GNU Chess 4 only */
8282           cps->sdKludge = TRUE;
8283           SendTimeControl(cps, movesPerSession, timeControl,
8284                           timeIncrement, appData.searchDepth,
8285                           searchTime);
8286           return;
8287         }
8288         if (!StrStr(message, "llegal")) {
8289             return;
8290         }
8291         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8292             gameMode == IcsIdle) return;
8293         if (forwardMostMove <= backwardMostMove) return;
8294         if (pausing) PauseEvent();
8295       if(appData.forceIllegal) {
8296             // [HGM] illegal: machine refused move; force position after move into it
8297           SendToProgram("force\n", cps);
8298           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8299                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8300                 // when black is to move, while there might be nothing on a2 or black
8301                 // might already have the move. So send the board as if white has the move.
8302                 // But first we must change the stm of the engine, as it refused the last move
8303                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8304                 if(WhiteOnMove(forwardMostMove)) {
8305                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8306                     SendBoard(cps, forwardMostMove); // kludgeless board
8307                 } else {
8308                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8309                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8310                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8311                 }
8312           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8313             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8314                  gameMode == TwoMachinesPlay)
8315               SendToProgram("go\n", cps);
8316             return;
8317       } else
8318         if (gameMode == PlayFromGameFile) {
8319             /* Stop reading this game file */
8320             gameMode = EditGame;
8321             ModeHighlight();
8322         }
8323         /* [HGM] illegal-move claim should forfeit game when Xboard */
8324         /* only passes fully legal moves                            */
8325         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8326             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8327                                 "False illegal-move claim", GE_XBOARD );
8328             return; // do not take back move we tested as valid
8329         }
8330         currentMove = forwardMostMove-1;
8331         DisplayMove(currentMove-1); /* before DisplayMoveError */
8332         SwitchClocks(forwardMostMove-1); // [HGM] race
8333         DisplayBothClocks();
8334         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8335                 parseList[currentMove], _(cps->which));
8336         DisplayMoveError(buf1);
8337         DrawPosition(FALSE, boards[currentMove]);
8338
8339         SetUserThinkingEnables();
8340         return;
8341     }
8342     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8343         /* Program has a broken "time" command that
8344            outputs a string not ending in newline.
8345            Don't use it. */
8346         cps->sendTime = 0;
8347     }
8348
8349     /*
8350      * If chess program startup fails, exit with an error message.
8351      * Attempts to recover here are futile.
8352      */
8353     if ((StrStr(message, "unknown host") != NULL)
8354         || (StrStr(message, "No remote directory") != NULL)
8355         || (StrStr(message, "not found") != NULL)
8356         || (StrStr(message, "No such file") != NULL)
8357         || (StrStr(message, "can't alloc") != NULL)
8358         || (StrStr(message, "Permission denied") != NULL)) {
8359
8360         cps->maybeThinking = FALSE;
8361         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8362                 _(cps->which), cps->program, cps->host, message);
8363         RemoveInputSource(cps->isr);
8364         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8365             if(cps == &first) appData.noChessProgram = TRUE;
8366             DisplayError(buf1, 0);
8367         }
8368         return;
8369     }
8370
8371     /*
8372      * Look for hint output
8373      */
8374     if (sscanf(message, "Hint: %s", buf1) == 1) {
8375         if (cps == &first && hintRequested) {
8376             hintRequested = FALSE;
8377             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8378                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8379                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8380                                     PosFlags(forwardMostMove),
8381                                     fromY, fromX, toY, toX, promoChar, buf1);
8382                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8383                 DisplayInformation(buf2);
8384             } else {
8385                 /* Hint move could not be parsed!? */
8386               snprintf(buf2, sizeof(buf2),
8387                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8388                         buf1, _(cps->which));
8389                 DisplayError(buf2, 0);
8390             }
8391         } else {
8392           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8393         }
8394         return;
8395     }
8396
8397     /*
8398      * Ignore other messages if game is not in progress
8399      */
8400     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8401         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8402
8403     /*
8404      * look for win, lose, draw, or draw offer
8405      */
8406     if (strncmp(message, "1-0", 3) == 0) {
8407         char *p, *q, *r = "";
8408         p = strchr(message, '{');
8409         if (p) {
8410             q = strchr(p, '}');
8411             if (q) {
8412                 *q = NULLCHAR;
8413                 r = p + 1;
8414             }
8415         }
8416         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8417         return;
8418     } else if (strncmp(message, "0-1", 3) == 0) {
8419         char *p, *q, *r = "";
8420         p = strchr(message, '{');
8421         if (p) {
8422             q = strchr(p, '}');
8423             if (q) {
8424                 *q = NULLCHAR;
8425                 r = p + 1;
8426             }
8427         }
8428         /* Kludge for Arasan 4.1 bug */
8429         if (strcmp(r, "Black resigns") == 0) {
8430             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8431             return;
8432         }
8433         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8434         return;
8435     } else if (strncmp(message, "1/2", 3) == 0) {
8436         char *p, *q, *r = "";
8437         p = strchr(message, '{');
8438         if (p) {
8439             q = strchr(p, '}');
8440             if (q) {
8441                 *q = NULLCHAR;
8442                 r = p + 1;
8443             }
8444         }
8445
8446         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8447         return;
8448
8449     } else if (strncmp(message, "White resign", 12) == 0) {
8450         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "Black resign", 12) == 0) {
8453         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "White matches", 13) == 0 ||
8456                strncmp(message, "Black matches", 13) == 0   ) {
8457         /* [HGM] ignore GNUShogi noises */
8458         return;
8459     } else if (strncmp(message, "White", 5) == 0 &&
8460                message[5] != '(' &&
8461                StrStr(message, "Black") == NULL) {
8462         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8463         return;
8464     } else if (strncmp(message, "Black", 5) == 0 &&
8465                message[5] != '(') {
8466         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strcmp(message, "resign") == 0 ||
8469                strcmp(message, "computer resigns") == 0) {
8470         switch (gameMode) {
8471           case MachinePlaysBlack:
8472           case IcsPlayingBlack:
8473             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8474             break;
8475           case MachinePlaysWhite:
8476           case IcsPlayingWhite:
8477             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8478             break;
8479           case TwoMachinesPlay:
8480             if (cps->twoMachinesColor[0] == 'w')
8481               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8482             else
8483               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8484             break;
8485           default:
8486             /* can't happen */
8487             break;
8488         }
8489         return;
8490     } else if (strncmp(message, "opponent mates", 14) == 0) {
8491         switch (gameMode) {
8492           case MachinePlaysBlack:
8493           case IcsPlayingBlack:
8494             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8495             break;
8496           case MachinePlaysWhite:
8497           case IcsPlayingWhite:
8498             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8499             break;
8500           case TwoMachinesPlay:
8501             if (cps->twoMachinesColor[0] == 'w')
8502               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8503             else
8504               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8505             break;
8506           default:
8507             /* can't happen */
8508             break;
8509         }
8510         return;
8511     } else if (strncmp(message, "computer mates", 14) == 0) {
8512         switch (gameMode) {
8513           case MachinePlaysBlack:
8514           case IcsPlayingBlack:
8515             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8516             break;
8517           case MachinePlaysWhite:
8518           case IcsPlayingWhite:
8519             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8520             break;
8521           case TwoMachinesPlay:
8522             if (cps->twoMachinesColor[0] == 'w')
8523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8524             else
8525               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526             break;
8527           default:
8528             /* can't happen */
8529             break;
8530         }
8531         return;
8532     } else if (strncmp(message, "checkmate", 9) == 0) {
8533         if (WhiteOnMove(forwardMostMove)) {
8534             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8535         } else {
8536             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537         }
8538         return;
8539     } else if (strstr(message, "Draw") != NULL ||
8540                strstr(message, "game is a draw") != NULL) {
8541         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8542         return;
8543     } else if (strstr(message, "offer") != NULL &&
8544                strstr(message, "draw") != NULL) {
8545 #if ZIPPY
8546         if (appData.zippyPlay && first.initDone) {
8547             /* Relay offer to ICS */
8548             SendToICS(ics_prefix);
8549             SendToICS("draw\n");
8550         }
8551 #endif
8552         cps->offeredDraw = 2; /* valid until this engine moves twice */
8553         if (gameMode == TwoMachinesPlay) {
8554             if (cps->other->offeredDraw) {
8555                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8556             /* [HGM] in two-machine mode we delay relaying draw offer      */
8557             /* until after we also have move, to see if it is really claim */
8558             }
8559         } else if (gameMode == MachinePlaysWhite ||
8560                    gameMode == MachinePlaysBlack) {
8561           if (userOfferedDraw) {
8562             DisplayInformation(_("Machine accepts your draw offer"));
8563             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8564           } else {
8565             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8566           }
8567         }
8568     }
8569
8570
8571     /*
8572      * Look for thinking output
8573      */
8574     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8575           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8576                                 ) {
8577         int plylev, mvleft, mvtot, curscore, time;
8578         char mvname[MOVE_LEN];
8579         u64 nodes; // [DM]
8580         char plyext;
8581         int ignore = FALSE;
8582         int prefixHint = FALSE;
8583         mvname[0] = NULLCHAR;
8584
8585         switch (gameMode) {
8586           case MachinePlaysBlack:
8587           case IcsPlayingBlack:
8588             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8589             break;
8590           case MachinePlaysWhite:
8591           case IcsPlayingWhite:
8592             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8593             break;
8594           case AnalyzeMode:
8595           case AnalyzeFile:
8596             break;
8597           case IcsObserving: /* [DM] icsEngineAnalyze */
8598             if (!appData.icsEngineAnalyze) ignore = TRUE;
8599             break;
8600           case TwoMachinesPlay:
8601             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8602                 ignore = TRUE;
8603             }
8604             break;
8605           default:
8606             ignore = TRUE;
8607             break;
8608         }
8609
8610         if (!ignore) {
8611             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8612             buf1[0] = NULLCHAR;
8613             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8614                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8615
8616                 if (plyext != ' ' && plyext != '\t') {
8617                     time *= 100;
8618                 }
8619
8620                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8621                 if( cps->scoreIsAbsolute &&
8622                     ( gameMode == MachinePlaysBlack ||
8623                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8624                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8625                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8626                      !WhiteOnMove(currentMove)
8627                     ) )
8628                 {
8629                     curscore = -curscore;
8630                 }
8631
8632                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8633
8634                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8635                         char buf[MSG_SIZ];
8636                         FILE *f;
8637                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8638                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8639                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8640                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8641                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8642                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8643                                 fclose(f);
8644                         } else DisplayError(_("failed writing PV"), 0);
8645                 }
8646
8647                 tempStats.depth = plylev;
8648                 tempStats.nodes = nodes;
8649                 tempStats.time = time;
8650                 tempStats.score = curscore;
8651                 tempStats.got_only_move = 0;
8652
8653                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8654                         int ticklen;
8655
8656                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8657                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8658                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8659                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8660                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8661                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8662                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8663                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8664                 }
8665
8666                 /* Buffer overflow protection */
8667                 if (pv[0] != NULLCHAR) {
8668                     if (strlen(pv) >= sizeof(tempStats.movelist)
8669                         && appData.debugMode) {
8670                         fprintf(debugFP,
8671                                 "PV is too long; using the first %u bytes.\n",
8672                                 (unsigned) sizeof(tempStats.movelist) - 1);
8673                     }
8674
8675                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8676                 } else {
8677                     sprintf(tempStats.movelist, " no PV\n");
8678                 }
8679
8680                 if (tempStats.seen_stat) {
8681                     tempStats.ok_to_send = 1;
8682                 }
8683
8684                 if (strchr(tempStats.movelist, '(') != NULL) {
8685                     tempStats.line_is_book = 1;
8686                     tempStats.nr_moves = 0;
8687                     tempStats.moves_left = 0;
8688                 } else {
8689                     tempStats.line_is_book = 0;
8690                 }
8691
8692                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8693                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8694
8695                 SendProgramStatsToFrontend( cps, &tempStats );
8696
8697                 /*
8698                     [AS] Protect the thinkOutput buffer from overflow... this
8699                     is only useful if buf1 hasn't overflowed first!
8700                 */
8701                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8702                          plylev,
8703                          (gameMode == TwoMachinesPlay ?
8704                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8705                          ((double) curscore) / 100.0,
8706                          prefixHint ? lastHint : "",
8707                          prefixHint ? " " : "" );
8708
8709                 if( buf1[0] != NULLCHAR ) {
8710                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8711
8712                     if( strlen(pv) > max_len ) {
8713                         if( appData.debugMode) {
8714                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8715                         }
8716                         pv[max_len+1] = '\0';
8717                     }
8718
8719                     strcat( thinkOutput, pv);
8720                 }
8721
8722                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8723                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8724                     DisplayMove(currentMove - 1);
8725                 }
8726                 return;
8727
8728             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8729                 /* crafty (9.25+) says "(only move) <move>"
8730                  * if there is only 1 legal move
8731                  */
8732                 sscanf(p, "(only move) %s", buf1);
8733                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8734                 sprintf(programStats.movelist, "%s (only move)", buf1);
8735                 programStats.depth = 1;
8736                 programStats.nr_moves = 1;
8737                 programStats.moves_left = 1;
8738                 programStats.nodes = 1;
8739                 programStats.time = 1;
8740                 programStats.got_only_move = 1;
8741
8742                 /* Not really, but we also use this member to
8743                    mean "line isn't going to change" (Crafty
8744                    isn't searching, so stats won't change) */
8745                 programStats.line_is_book = 1;
8746
8747                 SendProgramStatsToFrontend( cps, &programStats );
8748
8749                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8750                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8751                     DisplayMove(currentMove - 1);
8752                 }
8753                 return;
8754             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8755                               &time, &nodes, &plylev, &mvleft,
8756                               &mvtot, mvname) >= 5) {
8757                 /* The stat01: line is from Crafty (9.29+) in response
8758                    to the "." command */
8759                 programStats.seen_stat = 1;
8760                 cps->maybeThinking = TRUE;
8761
8762                 if (programStats.got_only_move || !appData.periodicUpdates)
8763                   return;
8764
8765                 programStats.depth = plylev;
8766                 programStats.time = time;
8767                 programStats.nodes = nodes;
8768                 programStats.moves_left = mvleft;
8769                 programStats.nr_moves = mvtot;
8770                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8771                 programStats.ok_to_send = 1;
8772                 programStats.movelist[0] = '\0';
8773
8774                 SendProgramStatsToFrontend( cps, &programStats );
8775
8776                 return;
8777
8778             } else if (strncmp(message,"++",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 2;
8781                 return;
8782
8783             } else if (strncmp(message,"--",2) == 0) {
8784                 /* Crafty 9.29+ outputs this */
8785                 programStats.got_fail = 1;
8786                 return;
8787
8788             } else if (thinkOutput[0] != NULLCHAR &&
8789                        strncmp(message, "    ", 4) == 0) {
8790                 unsigned message_len;
8791
8792                 p = message;
8793                 while (*p && *p == ' ') p++;
8794
8795                 message_len = strlen( p );
8796
8797                 /* [AS] Avoid buffer overflow */
8798                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8799                     strcat(thinkOutput, " ");
8800                     strcat(thinkOutput, p);
8801                 }
8802
8803                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8804                     strcat(programStats.movelist, " ");
8805                     strcat(programStats.movelist, p);
8806                 }
8807
8808                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8809                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8810                     DisplayMove(currentMove - 1);
8811                 }
8812                 return;
8813             }
8814         }
8815         else {
8816             buf1[0] = NULLCHAR;
8817
8818             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8819                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8820             {
8821                 ChessProgramStats cpstats;
8822
8823                 if (plyext != ' ' && plyext != '\t') {
8824                     time *= 100;
8825                 }
8826
8827                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8828                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8829                     curscore = -curscore;
8830                 }
8831
8832                 cpstats.depth = plylev;
8833                 cpstats.nodes = nodes;
8834                 cpstats.time = time;
8835                 cpstats.score = curscore;
8836                 cpstats.got_only_move = 0;
8837                 cpstats.movelist[0] = '\0';
8838
8839                 if (buf1[0] != NULLCHAR) {
8840                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8841                 }
8842
8843                 cpstats.ok_to_send = 0;
8844                 cpstats.line_is_book = 0;
8845                 cpstats.nr_moves = 0;
8846                 cpstats.moves_left = 0;
8847
8848                 SendProgramStatsToFrontend( cps, &cpstats );
8849             }
8850         }
8851     }
8852 }
8853
8854
8855 /* Parse a game score from the character string "game", and
8856    record it as the history of the current game.  The game
8857    score is NOT assumed to start from the standard position.
8858    The display is not updated in any way.
8859    */
8860 void
8861 ParseGameHistory (char *game)
8862 {
8863     ChessMove moveType;
8864     int fromX, fromY, toX, toY, boardIndex;
8865     char promoChar;
8866     char *p, *q;
8867     char buf[MSG_SIZ];
8868
8869     if (appData.debugMode)
8870       fprintf(debugFP, "Parsing game history: %s\n", game);
8871
8872     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8873     gameInfo.site = StrSave(appData.icsHost);
8874     gameInfo.date = PGNDate();
8875     gameInfo.round = StrSave("-");
8876
8877     /* Parse out names of players */
8878     while (*game == ' ') game++;
8879     p = buf;
8880     while (*game != ' ') *p++ = *game++;
8881     *p = NULLCHAR;
8882     gameInfo.white = StrSave(buf);
8883     while (*game == ' ') game++;
8884     p = buf;
8885     while (*game != ' ' && *game != '\n') *p++ = *game++;
8886     *p = NULLCHAR;
8887     gameInfo.black = StrSave(buf);
8888
8889     /* Parse moves */
8890     boardIndex = blackPlaysFirst ? 1 : 0;
8891     yynewstr(game);
8892     for (;;) {
8893         yyboardindex = boardIndex;
8894         moveType = (ChessMove) Myylex();
8895         switch (moveType) {
8896           case IllegalMove:             /* maybe suicide chess, etc. */
8897   if (appData.debugMode) {
8898     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8899     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8900     setbuf(debugFP, NULL);
8901   }
8902           case WhitePromotion:
8903           case BlackPromotion:
8904           case WhiteNonPromotion:
8905           case BlackNonPromotion:
8906           case NormalMove:
8907           case WhiteCapturesEnPassant:
8908           case BlackCapturesEnPassant:
8909           case WhiteKingSideCastle:
8910           case WhiteQueenSideCastle:
8911           case BlackKingSideCastle:
8912           case BlackQueenSideCastle:
8913           case WhiteKingSideCastleWild:
8914           case WhiteQueenSideCastleWild:
8915           case BlackKingSideCastleWild:
8916           case BlackQueenSideCastleWild:
8917           /* PUSH Fabien */
8918           case WhiteHSideCastleFR:
8919           case WhiteASideCastleFR:
8920           case BlackHSideCastleFR:
8921           case BlackASideCastleFR:
8922           /* POP Fabien */
8923             fromX = currentMoveString[0] - AAA;
8924             fromY = currentMoveString[1] - ONE;
8925             toX = currentMoveString[2] - AAA;
8926             toY = currentMoveString[3] - ONE;
8927             promoChar = currentMoveString[4];
8928             break;
8929           case WhiteDrop:
8930           case BlackDrop:
8931             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8932             fromX = moveType == WhiteDrop ?
8933               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8934             (int) CharToPiece(ToLower(currentMoveString[0]));
8935             fromY = DROP_RANK;
8936             toX = currentMoveString[2] - AAA;
8937             toY = currentMoveString[3] - ONE;
8938             promoChar = NULLCHAR;
8939             break;
8940           case AmbiguousMove:
8941             /* bug? */
8942             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8943   if (appData.debugMode) {
8944     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8946     setbuf(debugFP, NULL);
8947   }
8948             DisplayError(buf, 0);
8949             return;
8950           case ImpossibleMove:
8951             /* bug? */
8952             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8953   if (appData.debugMode) {
8954     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8955     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8956     setbuf(debugFP, NULL);
8957   }
8958             DisplayError(buf, 0);
8959             return;
8960           case EndOfFile:
8961             if (boardIndex < backwardMostMove) {
8962                 /* Oops, gap.  How did that happen? */
8963                 DisplayError(_("Gap in move list"), 0);
8964                 return;
8965             }
8966             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8967             if (boardIndex > forwardMostMove) {
8968                 forwardMostMove = boardIndex;
8969             }
8970             return;
8971           case ElapsedTime:
8972             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8973                 strcat(parseList[boardIndex-1], " ");
8974                 strcat(parseList[boardIndex-1], yy_text);
8975             }
8976             continue;
8977           case Comment:
8978           case PGNTag:
8979           case NAG:
8980           default:
8981             /* ignore */
8982             continue;
8983           case WhiteWins:
8984           case BlackWins:
8985           case GameIsDrawn:
8986           case GameUnfinished:
8987             if (gameMode == IcsExamining) {
8988                 if (boardIndex < backwardMostMove) {
8989                     /* Oops, gap.  How did that happen? */
8990                     return;
8991                 }
8992                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8993                 return;
8994             }
8995             gameInfo.result = moveType;
8996             p = strchr(yy_text, '{');
8997             if (p == NULL) p = strchr(yy_text, '(');
8998             if (p == NULL) {
8999                 p = yy_text;
9000                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9001             } else {
9002                 q = strchr(p, *p == '{' ? '}' : ')');
9003                 if (q != NULL) *q = NULLCHAR;
9004                 p++;
9005             }
9006             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9007             gameInfo.resultDetails = StrSave(p);
9008             continue;
9009         }
9010         if (boardIndex >= forwardMostMove &&
9011             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9012             backwardMostMove = blackPlaysFirst ? 1 : 0;
9013             return;
9014         }
9015         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9016                                  fromY, fromX, toY, toX, promoChar,
9017                                  parseList[boardIndex]);
9018         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9019         /* currentMoveString is set as a side-effect of yylex */
9020         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9021         strcat(moveList[boardIndex], "\n");
9022         boardIndex++;
9023         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9024         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9025           case MT_NONE:
9026           case MT_STALEMATE:
9027           default:
9028             break;
9029           case MT_CHECK:
9030             if(gameInfo.variant != VariantShogi)
9031                 strcat(parseList[boardIndex - 1], "+");
9032             break;
9033           case MT_CHECKMATE:
9034           case MT_STAINMATE:
9035             strcat(parseList[boardIndex - 1], "#");
9036             break;
9037         }
9038     }
9039 }
9040
9041
9042 /* Apply a move to the given board  */
9043 void
9044 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9045 {
9046   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9047   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9048
9049     /* [HGM] compute & store e.p. status and castling rights for new position */
9050     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9051
9052       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9053       oldEP = (signed char)board[EP_STATUS];
9054       board[EP_STATUS] = EP_NONE;
9055
9056   if (fromY == DROP_RANK) {
9057         /* must be first */
9058         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9059             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9060             return;
9061         }
9062         piece = board[toY][toX] = (ChessSquare) fromX;
9063   } else {
9064       int i;
9065
9066       if( board[toY][toX] != EmptySquare )
9067            board[EP_STATUS] = EP_CAPTURE;
9068
9069       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9070            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9071                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9072       } else
9073       if( board[fromY][fromX] == WhitePawn ) {
9074            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9075                board[EP_STATUS] = EP_PAWN_MOVE;
9076            if( toY-fromY==2) {
9077                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9078                         gameInfo.variant != VariantBerolina || toX < fromX)
9079                       board[EP_STATUS] = toX | berolina;
9080                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9081                         gameInfo.variant != VariantBerolina || toX > fromX)
9082                       board[EP_STATUS] = toX;
9083            }
9084       } else
9085       if( board[fromY][fromX] == BlackPawn ) {
9086            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9087                board[EP_STATUS] = EP_PAWN_MOVE;
9088            if( toY-fromY== -2) {
9089                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9090                         gameInfo.variant != VariantBerolina || toX < fromX)
9091                       board[EP_STATUS] = toX | berolina;
9092                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9093                         gameInfo.variant != VariantBerolina || toX > fromX)
9094                       board[EP_STATUS] = toX;
9095            }
9096        }
9097
9098        for(i=0; i<nrCastlingRights; i++) {
9099            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9100               board[CASTLING][i] == toX   && castlingRank[i] == toY
9101              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9102        }
9103
9104      if (fromX == toX && fromY == toY) return;
9105
9106      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9107      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9108      if(gameInfo.variant == VariantKnightmate)
9109          king += (int) WhiteUnicorn - (int) WhiteKing;
9110
9111     /* Code added by Tord: */
9112     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9113     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9114         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9115       board[fromY][fromX] = EmptySquare;
9116       board[toY][toX] = EmptySquare;
9117       if((toX > fromX) != (piece == WhiteRook)) {
9118         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9119       } else {
9120         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9121       }
9122     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9123                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9124       board[fromY][fromX] = EmptySquare;
9125       board[toY][toX] = EmptySquare;
9126       if((toX > fromX) != (piece == BlackRook)) {
9127         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9128       } else {
9129         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9130       }
9131     /* End of code added by Tord */
9132
9133     } else if (board[fromY][fromX] == king
9134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9135         && toY == fromY && toX > fromX+1) {
9136         board[fromY][fromX] = EmptySquare;
9137         board[toY][toX] = king;
9138         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9139         board[fromY][BOARD_RGHT-1] = EmptySquare;
9140     } else if (board[fromY][fromX] == king
9141         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9142                && toY == fromY && toX < fromX-1) {
9143         board[fromY][fromX] = EmptySquare;
9144         board[toY][toX] = king;
9145         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9146         board[fromY][BOARD_LEFT] = EmptySquare;
9147     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9148                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9149                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9150                ) {
9151         /* white pawn promotion */
9152         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9153         if(gameInfo.variant==VariantBughouse ||
9154            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9155             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9156         board[fromY][fromX] = EmptySquare;
9157     } else if ((fromY >= BOARD_HEIGHT>>1)
9158                && (toX != fromX)
9159                && gameInfo.variant != VariantXiangqi
9160                && gameInfo.variant != VariantBerolina
9161                && (board[fromY][fromX] == WhitePawn)
9162                && (board[toY][toX] == EmptySquare)) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = WhitePawn;
9165         captured = board[toY - 1][toX];
9166         board[toY - 1][toX] = EmptySquare;
9167     } else if ((fromY == BOARD_HEIGHT-4)
9168                && (toX == fromX)
9169                && gameInfo.variant == VariantBerolina
9170                && (board[fromY][fromX] == WhitePawn)
9171                && (board[toY][toX] == EmptySquare)) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = WhitePawn;
9174         if(oldEP & EP_BEROLIN_A) {
9175                 captured = board[fromY][fromX-1];
9176                 board[fromY][fromX-1] = EmptySquare;
9177         }else{  captured = board[fromY][fromX+1];
9178                 board[fromY][fromX+1] = EmptySquare;
9179         }
9180     } else if (board[fromY][fromX] == king
9181         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9182                && toY == fromY && toX > fromX+1) {
9183         board[fromY][fromX] = EmptySquare;
9184         board[toY][toX] = king;
9185         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9186         board[fromY][BOARD_RGHT-1] = EmptySquare;
9187     } else if (board[fromY][fromX] == king
9188         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9189                && toY == fromY && toX < fromX-1) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = king;
9192         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9193         board[fromY][BOARD_LEFT] = EmptySquare;
9194     } else if (fromY == 7 && fromX == 3
9195                && board[fromY][fromX] == BlackKing
9196                && toY == 7 && toX == 5) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = BlackKing;
9199         board[fromY][7] = EmptySquare;
9200         board[toY][4] = BlackRook;
9201     } else if (fromY == 7 && fromX == 3
9202                && board[fromY][fromX] == BlackKing
9203                && toY == 7 && toX == 1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = BlackKing;
9206         board[fromY][0] = EmptySquare;
9207         board[toY][2] = BlackRook;
9208     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9209                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9210                && toY < promoRank && promoChar
9211                ) {
9212         /* black pawn promotion */
9213         board[toY][toX] = CharToPiece(ToLower(promoChar));
9214         if(gameInfo.variant==VariantBughouse ||
9215            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9216             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9217         board[fromY][fromX] = EmptySquare;
9218     } else if ((fromY < BOARD_HEIGHT>>1)
9219                && (toX != fromX)
9220                && gameInfo.variant != VariantXiangqi
9221                && gameInfo.variant != VariantBerolina
9222                && (board[fromY][fromX] == BlackPawn)
9223                && (board[toY][toX] == EmptySquare)) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackPawn;
9226         captured = board[toY + 1][toX];
9227         board[toY + 1][toX] = EmptySquare;
9228     } else if ((fromY == 3)
9229                && (toX == fromX)
9230                && gameInfo.variant == VariantBerolina
9231                && (board[fromY][fromX] == BlackPawn)
9232                && (board[toY][toX] == EmptySquare)) {
9233         board[fromY][fromX] = EmptySquare;
9234         board[toY][toX] = BlackPawn;
9235         if(oldEP & EP_BEROLIN_A) {
9236                 captured = board[fromY][fromX-1];
9237                 board[fromY][fromX-1] = EmptySquare;
9238         }else{  captured = board[fromY][fromX+1];
9239                 board[fromY][fromX+1] = EmptySquare;
9240         }
9241     } else {
9242         board[toY][toX] = board[fromY][fromX];
9243         board[fromY][fromX] = EmptySquare;
9244     }
9245   }
9246
9247     if (gameInfo.holdingsWidth != 0) {
9248
9249       /* !!A lot more code needs to be written to support holdings  */
9250       /* [HGM] OK, so I have written it. Holdings are stored in the */
9251       /* penultimate board files, so they are automaticlly stored   */
9252       /* in the game history.                                       */
9253       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9254                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9255         /* Delete from holdings, by decreasing count */
9256         /* and erasing image if necessary            */
9257         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9258         if(p < (int) BlackPawn) { /* white drop */
9259              p -= (int)WhitePawn;
9260                  p = PieceToNumber((ChessSquare)p);
9261              if(p >= gameInfo.holdingsSize) p = 0;
9262              if(--board[p][BOARD_WIDTH-2] <= 0)
9263                   board[p][BOARD_WIDTH-1] = EmptySquare;
9264              if((int)board[p][BOARD_WIDTH-2] < 0)
9265                         board[p][BOARD_WIDTH-2] = 0;
9266         } else {                  /* black drop */
9267              p -= (int)BlackPawn;
9268                  p = PieceToNumber((ChessSquare)p);
9269              if(p >= gameInfo.holdingsSize) p = 0;
9270              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9271                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9272              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9273                         board[BOARD_HEIGHT-1-p][1] = 0;
9274         }
9275       }
9276       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9277           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9278         /* [HGM] holdings: Add to holdings, if holdings exist */
9279         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9280                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9281                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9282         }
9283         p = (int) captured;
9284         if (p >= (int) BlackPawn) {
9285           p -= (int)BlackPawn;
9286           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9287                   /* in Shogi restore piece to its original  first */
9288                   captured = (ChessSquare) (DEMOTED captured);
9289                   p = DEMOTED p;
9290           }
9291           p = PieceToNumber((ChessSquare)p);
9292           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9293           board[p][BOARD_WIDTH-2]++;
9294           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9295         } else {
9296           p -= (int)WhitePawn;
9297           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9298                   captured = (ChessSquare) (DEMOTED captured);
9299                   p = DEMOTED p;
9300           }
9301           p = PieceToNumber((ChessSquare)p);
9302           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9303           board[BOARD_HEIGHT-1-p][1]++;
9304           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9305         }
9306       }
9307     } else if (gameInfo.variant == VariantAtomic) {
9308       if (captured != EmptySquare) {
9309         int y, x;
9310         for (y = toY-1; y <= toY+1; y++) {
9311           for (x = toX-1; x <= toX+1; x++) {
9312             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9313                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9314               board[y][x] = EmptySquare;
9315             }
9316           }
9317         }
9318         board[toY][toX] = EmptySquare;
9319       }
9320     }
9321     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9322         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9323     } else
9324     if(promoChar == '+') {
9325         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9326         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9327     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9328         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9329     }
9330     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9331                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9332         // [HGM] superchess: take promotion piece out of holdings
9333         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9334         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9335             if(!--board[k][BOARD_WIDTH-2])
9336                 board[k][BOARD_WIDTH-1] = EmptySquare;
9337         } else {
9338             if(!--board[BOARD_HEIGHT-1-k][1])
9339                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9340         }
9341     }
9342
9343 }
9344
9345 /* Updates forwardMostMove */
9346 void
9347 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9348 {
9349 //    forwardMostMove++; // [HGM] bare: moved downstream
9350
9351     (void) CoordsToAlgebraic(boards[forwardMostMove],
9352                              PosFlags(forwardMostMove),
9353                              fromY, fromX, toY, toX, promoChar,
9354                              parseList[forwardMostMove]);
9355
9356     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9357         int timeLeft; static int lastLoadFlag=0; int king, piece;
9358         piece = boards[forwardMostMove][fromY][fromX];
9359         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9360         if(gameInfo.variant == VariantKnightmate)
9361             king += (int) WhiteUnicorn - (int) WhiteKing;
9362         if(forwardMostMove == 0) {
9363             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9364                 fprintf(serverMoves, "%s;", UserName());
9365             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9366                 fprintf(serverMoves, "%s;", second.tidy);
9367             fprintf(serverMoves, "%s;", first.tidy);
9368             if(gameMode == MachinePlaysWhite)
9369                 fprintf(serverMoves, "%s;", UserName());
9370             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9371                 fprintf(serverMoves, "%s;", second.tidy);
9372         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9373         lastLoadFlag = loadFlag;
9374         // print base move
9375         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9376         // print castling suffix
9377         if( toY == fromY && piece == king ) {
9378             if(toX-fromX > 1)
9379                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9380             if(fromX-toX >1)
9381                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9382         }
9383         // e.p. suffix
9384         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9385              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9386              boards[forwardMostMove][toY][toX] == EmptySquare
9387              && fromX != toX && fromY != toY)
9388                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9389         // promotion suffix
9390         if(promoChar != NULLCHAR)
9391                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9392         if(!loadFlag) {
9393                 char buf[MOVE_LEN*2], *p; int len;
9394             fprintf(serverMoves, "/%d/%d",
9395                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9396             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9397             else                      timeLeft = blackTimeRemaining/1000;
9398             fprintf(serverMoves, "/%d", timeLeft);
9399                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9400                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9401                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9402             fprintf(serverMoves, "/%s", buf);
9403         }
9404         fflush(serverMoves);
9405     }
9406
9407     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9408         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9409       return;
9410     }
9411     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9412     if (commentList[forwardMostMove+1] != NULL) {
9413         free(commentList[forwardMostMove+1]);
9414         commentList[forwardMostMove+1] = NULL;
9415     }
9416     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9417     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9418     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9419     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9420     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9421     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9422     adjustedClock = FALSE;
9423     gameInfo.result = GameUnfinished;
9424     if (gameInfo.resultDetails != NULL) {
9425         free(gameInfo.resultDetails);
9426         gameInfo.resultDetails = NULL;
9427     }
9428     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9429                               moveList[forwardMostMove - 1]);
9430     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9431       case MT_NONE:
9432       case MT_STALEMATE:
9433       default:
9434         break;
9435       case MT_CHECK:
9436         if(gameInfo.variant != VariantShogi)
9437             strcat(parseList[forwardMostMove - 1], "+");
9438         break;
9439       case MT_CHECKMATE:
9440       case MT_STAINMATE:
9441         strcat(parseList[forwardMostMove - 1], "#");
9442         break;
9443     }
9444
9445 }
9446
9447 /* Updates currentMove if not pausing */
9448 void
9449 ShowMove (int fromX, int fromY, int toX, int toY)
9450 {
9451     int instant = (gameMode == PlayFromGameFile) ?
9452         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9453     if(appData.noGUI) return;
9454     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9455         if (!instant) {
9456             if (forwardMostMove == currentMove + 1) {
9457                 AnimateMove(boards[forwardMostMove - 1],
9458                             fromX, fromY, toX, toY);
9459             }
9460             if (appData.highlightLastMove) {
9461                 SetHighlights(fromX, fromY, toX, toY);
9462             }
9463         }
9464         currentMove = forwardMostMove;
9465     }
9466
9467     if (instant) return;
9468
9469     DisplayMove(currentMove - 1);
9470     DrawPosition(FALSE, boards[currentMove]);
9471     DisplayBothClocks();
9472     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9473 }
9474
9475 void
9476 SendEgtPath (ChessProgramState *cps)
9477 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9478         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9479
9480         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9481
9482         while(*p) {
9483             char c, *q = name+1, *r, *s;
9484
9485             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9486             while(*p && *p != ',') *q++ = *p++;
9487             *q++ = ':'; *q = 0;
9488             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9489                 strcmp(name, ",nalimov:") == 0 ) {
9490                 // take nalimov path from the menu-changeable option first, if it is defined
9491               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9492                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9493             } else
9494             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9495                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9496                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9497                 s = r = StrStr(s, ":") + 1; // beginning of path info
9498                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9499                 c = *r; *r = 0;             // temporarily null-terminate path info
9500                     *--q = 0;               // strip of trailig ':' from name
9501                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9502                 *r = c;
9503                 SendToProgram(buf,cps);     // send egtbpath command for this format
9504             }
9505             if(*p == ',') p++; // read away comma to position for next format name
9506         }
9507 }
9508
9509 void
9510 InitChessProgram (ChessProgramState *cps, int setup)
9511 /* setup needed to setup FRC opening position */
9512 {
9513     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9514     if (appData.noChessProgram) return;
9515     hintRequested = FALSE;
9516     bookRequested = FALSE;
9517
9518     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9519     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9520     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9521     if(cps->memSize) { /* [HGM] memory */
9522       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9523         SendToProgram(buf, cps);
9524     }
9525     SendEgtPath(cps); /* [HGM] EGT */
9526     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9527       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9528         SendToProgram(buf, cps);
9529     }
9530
9531     SendToProgram(cps->initString, cps);
9532     if (gameInfo.variant != VariantNormal &&
9533         gameInfo.variant != VariantLoadable
9534         /* [HGM] also send variant if board size non-standard */
9535         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9536                                             ) {
9537       char *v = VariantName(gameInfo.variant);
9538       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9539         /* [HGM] in protocol 1 we have to assume all variants valid */
9540         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9541         DisplayFatalError(buf, 0, 1);
9542         return;
9543       }
9544
9545       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9546       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantXiangqi )
9548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9549       if( gameInfo.variant == VariantShogi )
9550            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9551       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9553       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9554           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9555            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantCourier )
9557            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9558       if( gameInfo.variant == VariantSuper )
9559            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9560       if( gameInfo.variant == VariantGreat )
9561            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9562       if( gameInfo.variant == VariantSChess )
9563            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9564       if( gameInfo.variant == VariantGrand )
9565            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9566
9567       if(overruled) {
9568         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9569                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9570            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9571            if(StrStr(cps->variants, b) == NULL) {
9572                // specific sized variant not known, check if general sizing allowed
9573                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9574                    if(StrStr(cps->variants, "boardsize") == NULL) {
9575                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9576                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9577                        DisplayFatalError(buf, 0, 1);
9578                        return;
9579                    }
9580                    /* [HGM] here we really should compare with the maximum supported board size */
9581                }
9582            }
9583       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9584       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9585       SendToProgram(buf, cps);
9586     }
9587     currentlyInitializedVariant = gameInfo.variant;
9588
9589     /* [HGM] send opening position in FRC to first engine */
9590     if(setup) {
9591           SendToProgram("force\n", cps);
9592           SendBoard(cps, 0);
9593           /* engine is now in force mode! Set flag to wake it up after first move. */
9594           setboardSpoiledMachineBlack = 1;
9595     }
9596
9597     if (cps->sendICS) {
9598       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9599       SendToProgram(buf, cps);
9600     }
9601     cps->maybeThinking = FALSE;
9602     cps->offeredDraw = 0;
9603     if (!appData.icsActive) {
9604         SendTimeControl(cps, movesPerSession, timeControl,
9605                         timeIncrement, appData.searchDepth,
9606                         searchTime);
9607     }
9608     if (appData.showThinking
9609         // [HGM] thinking: four options require thinking output to be sent
9610         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9611                                 ) {
9612         SendToProgram("post\n", cps);
9613     }
9614     SendToProgram("hard\n", cps);
9615     if (!appData.ponderNextMove) {
9616         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9617            it without being sure what state we are in first.  "hard"
9618            is not a toggle, so that one is OK.
9619          */
9620         SendToProgram("easy\n", cps);
9621     }
9622     if (cps->usePing) {
9623       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9624       SendToProgram(buf, cps);
9625     }
9626     cps->initDone = TRUE;
9627     ClearEngineOutputPane(cps == &second);
9628 }
9629
9630
9631 void
9632 StartChessProgram (ChessProgramState *cps)
9633 {
9634     char buf[MSG_SIZ];
9635     int err;
9636
9637     if (appData.noChessProgram) return;
9638     cps->initDone = FALSE;
9639
9640     if (strcmp(cps->host, "localhost") == 0) {
9641         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9642     } else if (*appData.remoteShell == NULLCHAR) {
9643         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9644     } else {
9645         if (*appData.remoteUser == NULLCHAR) {
9646           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9647                     cps->program);
9648         } else {
9649           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9650                     cps->host, appData.remoteUser, cps->program);
9651         }
9652         err = StartChildProcess(buf, "", &cps->pr);
9653     }
9654
9655     if (err != 0) {
9656       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9657         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9658         if(cps != &first) return;
9659         appData.noChessProgram = TRUE;
9660         ThawUI();
9661         SetNCPMode();
9662 //      DisplayFatalError(buf, err, 1);
9663 //      cps->pr = NoProc;
9664 //      cps->isr = NULL;
9665         return;
9666     }
9667
9668     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9669     if (cps->protocolVersion > 1) {
9670       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9671       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9672       cps->comboCnt = 0;  //                and values of combo boxes
9673       SendToProgram(buf, cps);
9674     } else {
9675       SendToProgram("xboard\n", cps);
9676     }
9677 }
9678
9679 void
9680 TwoMachinesEventIfReady P((void))
9681 {
9682   static int curMess = 0;
9683   if (first.lastPing != first.lastPong) {
9684     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9686     return;
9687   }
9688   if (second.lastPing != second.lastPong) {
9689     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9690     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9691     return;
9692   }
9693   DisplayMessage("", ""); curMess = 0;
9694   ThawUI();
9695   TwoMachinesEvent();
9696 }
9697
9698 char *
9699 MakeName (char *template)
9700 {
9701     time_t clock;
9702     struct tm *tm;
9703     static char buf[MSG_SIZ];
9704     char *p = buf;
9705     int i;
9706
9707     clock = time((time_t *)NULL);
9708     tm = localtime(&clock);
9709
9710     while(*p++ = *template++) if(p[-1] == '%') {
9711         switch(*template++) {
9712           case 0:   *p = 0; return buf;
9713           case 'Y': i = tm->tm_year+1900; break;
9714           case 'y': i = tm->tm_year-100; break;
9715           case 'M': i = tm->tm_mon+1; break;
9716           case 'd': i = tm->tm_mday; break;
9717           case 'h': i = tm->tm_hour; break;
9718           case 'm': i = tm->tm_min; break;
9719           case 's': i = tm->tm_sec; break;
9720           default:  i = 0;
9721         }
9722         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9723     }
9724     return buf;
9725 }
9726
9727 int
9728 CountPlayers (char *p)
9729 {
9730     int n = 0;
9731     while(p = strchr(p, '\n')) p++, n++; // count participants
9732     return n;
9733 }
9734
9735 FILE *
9736 WriteTourneyFile (char *results, FILE *f)
9737 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9738     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9739     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9740         // create a file with tournament description
9741         fprintf(f, "-participants {%s}\n", appData.participants);
9742         fprintf(f, "-seedBase %d\n", appData.seedBase);
9743         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9744         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9745         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9746         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9747         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9748         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9749         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9750         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9751         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9752         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9753         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9754         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9755         if(searchTime > 0)
9756                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9757         else {
9758                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9759                 fprintf(f, "-tc %s\n", appData.timeControl);
9760                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9761         }
9762         fprintf(f, "-results \"%s\"\n", results);
9763     }
9764     return f;
9765 }
9766
9767 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9768
9769 void
9770 Substitute (char *participants, int expunge)
9771 {
9772     int i, changed, changes=0, nPlayers=0;
9773     char *p, *q, *r, buf[MSG_SIZ];
9774     if(participants == NULL) return;
9775     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9776     r = p = participants; q = appData.participants;
9777     while(*p && *p == *q) {
9778         if(*p == '\n') r = p+1, nPlayers++;
9779         p++; q++;
9780     }
9781     if(*p) { // difference
9782         while(*p && *p++ != '\n');
9783         while(*q && *q++ != '\n');
9784       changed = nPlayers;
9785         changes = 1 + (strcmp(p, q) != 0);
9786     }
9787     if(changes == 1) { // a single engine mnemonic was changed
9788         q = r; while(*q) nPlayers += (*q++ == '\n');
9789         p = buf; while(*r && (*p = *r++) != '\n') p++;
9790         *p = NULLCHAR;
9791         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9792         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9793         if(mnemonic[i]) { // The substitute is valid
9794             FILE *f;
9795             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9796                 flock(fileno(f), LOCK_EX);
9797                 ParseArgsFromFile(f);
9798                 fseek(f, 0, SEEK_SET);
9799                 FREE(appData.participants); appData.participants = participants;
9800                 if(expunge) { // erase results of replaced engine
9801                     int len = strlen(appData.results), w, b, dummy;
9802                     for(i=0; i<len; i++) {
9803                         Pairing(i, nPlayers, &w, &b, &dummy);
9804                         if((w == changed || b == changed) && appData.results[i] == '*') {
9805                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9806                             fclose(f);
9807                             return;
9808                         }
9809                     }
9810                     for(i=0; i<len; i++) {
9811                         Pairing(i, nPlayers, &w, &b, &dummy);
9812                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9813                     }
9814                 }
9815                 WriteTourneyFile(appData.results, f);
9816                 fclose(f); // release lock
9817                 return;
9818             }
9819         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9820     }
9821     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9822     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9823     free(participants);
9824     return;
9825 }
9826
9827 int
9828 CreateTourney (char *name)
9829 {
9830         FILE *f;
9831         if(matchMode && strcmp(name, appData.tourneyFile)) {
9832              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9833         }
9834         if(name[0] == NULLCHAR) {
9835             if(appData.participants[0])
9836                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9837             return 0;
9838         }
9839         f = fopen(name, "r");
9840         if(f) { // file exists
9841             ASSIGN(appData.tourneyFile, name);
9842             ParseArgsFromFile(f); // parse it
9843         } else {
9844             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9845             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9846                 DisplayError(_("Not enough participants"), 0);
9847                 return 0;
9848             }
9849             ASSIGN(appData.tourneyFile, name);
9850             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9851             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9852         }
9853         fclose(f);
9854         appData.noChessProgram = FALSE;
9855         appData.clockMode = TRUE;
9856         SetGNUMode();
9857         return 1;
9858 }
9859
9860 int
9861 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9862 {
9863     char buf[MSG_SIZ], *p, *q;
9864     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9865     skip = !all && group[0]; // if group requested, we start in skip mode
9866     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9867         p = names; q = buf; header = 0;
9868         while(*p && *p != '\n') *q++ = *p++;
9869         *q = 0;
9870         if(*p == '\n') p++;
9871         if(buf[0] == '#') {
9872             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9873             depth++; // we must be entering a new group
9874             if(all) continue; // suppress printing group headers when complete list requested
9875             header = 1;
9876             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9877         }
9878         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9879         if(engineList[i]) free(engineList[i]);
9880         engineList[i] = strdup(buf);
9881         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9882         if(engineMnemonic[i]) free(engineMnemonic[i]);
9883         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9884             strcat(buf, " (");
9885             sscanf(q + 8, "%s", buf + strlen(buf));
9886             strcat(buf, ")");
9887         }
9888         engineMnemonic[i] = strdup(buf);
9889         i++;
9890     }
9891     engineList[i] = engineMnemonic[i] = NULL;
9892     return i;
9893 }
9894
9895 // following implemented as macro to avoid type limitations
9896 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9897
9898 void
9899 SwapEngines (int n)
9900 {   // swap settings for first engine and other engine (so far only some selected options)
9901     int h;
9902     char *p;
9903     if(n == 0) return;
9904     SWAP(directory, p)
9905     SWAP(chessProgram, p)
9906     SWAP(isUCI, h)
9907     SWAP(hasOwnBookUCI, h)
9908     SWAP(protocolVersion, h)
9909     SWAP(reuse, h)
9910     SWAP(scoreIsAbsolute, h)
9911     SWAP(timeOdds, h)
9912     SWAP(logo, p)
9913     SWAP(pgnName, p)
9914     SWAP(pvSAN, h)
9915     SWAP(engOptions, p)
9916 }
9917
9918 int
9919 SetPlayer (int player, char *p)
9920 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9921     int i;
9922     char buf[MSG_SIZ], *engineName;
9923     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9924     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9925     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9926     if(mnemonic[i]) {
9927         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9928         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9929         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9930         ParseArgsFromString(buf);
9931     }
9932     free(engineName);
9933     return i;
9934 }
9935
9936 char *recentEngines;
9937
9938 void
9939 RecentEngineEvent (int nr)
9940 {
9941     int n;
9942 //    SwapEngines(1); // bump first to second
9943 //    ReplaceEngine(&second, 1); // and load it there
9944     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9945     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9946     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9947         ReplaceEngine(&first, 0);
9948         FloatToFront(&appData.recentEngineList, command[n]);
9949     }
9950 }
9951
9952 int
9953 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9954 {   // determine players from game number
9955     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9956
9957     if(appData.tourneyType == 0) {
9958         roundsPerCycle = (nPlayers - 1) | 1;
9959         pairingsPerRound = nPlayers / 2;
9960     } else if(appData.tourneyType > 0) {
9961         roundsPerCycle = nPlayers - appData.tourneyType;
9962         pairingsPerRound = appData.tourneyType;
9963     }
9964     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9965     gamesPerCycle = gamesPerRound * roundsPerCycle;
9966     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9967     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9968     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9969     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9970     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9971     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9972
9973     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9974     if(appData.roundSync) *syncInterval = gamesPerRound;
9975
9976     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9977
9978     if(appData.tourneyType == 0) {
9979         if(curPairing == (nPlayers-1)/2 ) {
9980             *whitePlayer = curRound;
9981             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9982         } else {
9983             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9984             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9985             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9986             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9987         }
9988     } else if(appData.tourneyType > 1) {
9989         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9990         *whitePlayer = curRound + appData.tourneyType;
9991     } else if(appData.tourneyType > 0) {
9992         *whitePlayer = curPairing;
9993         *blackPlayer = curRound + appData.tourneyType;
9994     }
9995
9996     // take care of white/black alternation per round. 
9997     // For cycles and games this is already taken care of by default, derived from matchGame!
9998     return curRound & 1;
9999 }
10000
10001 int
10002 NextTourneyGame (int nr, int *swapColors)
10003 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10004     char *p, *q;
10005     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10006     FILE *tf;
10007     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10008     tf = fopen(appData.tourneyFile, "r");
10009     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10010     ParseArgsFromFile(tf); fclose(tf);
10011     InitTimeControls(); // TC might be altered from tourney file
10012
10013     nPlayers = CountPlayers(appData.participants); // count participants
10014     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10015     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10016
10017     if(syncInterval) {
10018         p = q = appData.results;
10019         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10020         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10021             DisplayMessage(_("Waiting for other game(s)"),"");
10022             waitingForGame = TRUE;
10023             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10024             return 0;
10025         }
10026         waitingForGame = FALSE;
10027     }
10028
10029     if(appData.tourneyType < 0) {
10030         if(nr>=0 && !pairingReceived) {
10031             char buf[1<<16];
10032             if(pairing.pr == NoProc) {
10033                 if(!appData.pairingEngine[0]) {
10034                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10035                     return 0;
10036                 }
10037                 StartChessProgram(&pairing); // starts the pairing engine
10038             }
10039             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10040             SendToProgram(buf, &pairing);
10041             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10042             SendToProgram(buf, &pairing);
10043             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10044         }
10045         pairingReceived = 0;                              // ... so we continue here 
10046         *swapColors = 0;
10047         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10048         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10049         matchGame = 1; roundNr = nr / syncInterval + 1;
10050     }
10051
10052     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10053
10054     // redefine engines, engine dir, etc.
10055     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10056     if(first.pr == NoProc) {
10057       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10058       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10059     }
10060     if(second.pr == NoProc) {
10061       SwapEngines(1);
10062       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10063       SwapEngines(1);         // and make that valid for second engine by swapping
10064       InitEngine(&second, 1);
10065     }
10066     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10067     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10068     return 1;
10069 }
10070
10071 void
10072 NextMatchGame ()
10073 {   // performs game initialization that does not invoke engines, and then tries to start the game
10074     int res, firstWhite, swapColors = 0;
10075     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10076     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
10077         char buf[MSG_SIZ];
10078         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10079         if(strcmp(buf, currentDebugFile)) { // name has changed
10080             FILE *f = fopen(buf, "w");
10081             if(f) { // if opening the new file failed, just keep using the old one
10082                 ASSIGN(currentDebugFile, buf);
10083                 fclose(debugFP);
10084                 debugFP = f;
10085             }
10086             if(appData.serverFileName) {
10087                 if(serverFP) fclose(serverFP);
10088                 serverFP = fopen(appData.serverFileName, "w");
10089                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10090                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10091             }
10092         }
10093     }
10094     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10095     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10096     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10097     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10098     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10099     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10100     Reset(FALSE, first.pr != NoProc);
10101     res = LoadGameOrPosition(matchGame); // setup game
10102     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10103     if(!res) return; // abort when bad game/pos file
10104     TwoMachinesEvent();
10105 }
10106
10107 void
10108 UserAdjudicationEvent (int result)
10109 {
10110     ChessMove gameResult = GameIsDrawn;
10111
10112     if( result > 0 ) {
10113         gameResult = WhiteWins;
10114     }
10115     else if( result < 0 ) {
10116         gameResult = BlackWins;
10117     }
10118
10119     if( gameMode == TwoMachinesPlay ) {
10120         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10121     }
10122 }
10123
10124
10125 // [HGM] save: calculate checksum of game to make games easily identifiable
10126 int
10127 StringCheckSum (char *s)
10128 {
10129         int i = 0;
10130         if(s==NULL) return 0;
10131         while(*s) i = i*259 + *s++;
10132         return i;
10133 }
10134
10135 int
10136 GameCheckSum ()
10137 {
10138         int i, sum=0;
10139         for(i=backwardMostMove; i<forwardMostMove; i++) {
10140                 sum += pvInfoList[i].depth;
10141                 sum += StringCheckSum(parseList[i]);
10142                 sum += StringCheckSum(commentList[i]);
10143                 sum *= 261;
10144         }
10145         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10146         return sum + StringCheckSum(commentList[i]);
10147 } // end of save patch
10148
10149 void
10150 GameEnds (ChessMove result, char *resultDetails, int whosays)
10151 {
10152     GameMode nextGameMode;
10153     int isIcsGame;
10154     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10155
10156     if(endingGame) return; /* [HGM] crash: forbid recursion */
10157     endingGame = 1;
10158     if(twoBoards) { // [HGM] dual: switch back to one board
10159         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10160         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10161     }
10162     if (appData.debugMode) {
10163       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10164               result, resultDetails ? resultDetails : "(null)", whosays);
10165     }
10166
10167     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10168
10169     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10170         /* If we are playing on ICS, the server decides when the
10171            game is over, but the engine can offer to draw, claim
10172            a draw, or resign.
10173          */
10174 #if ZIPPY
10175         if (appData.zippyPlay && first.initDone) {
10176             if (result == GameIsDrawn) {
10177                 /* In case draw still needs to be claimed */
10178                 SendToICS(ics_prefix);
10179                 SendToICS("draw\n");
10180             } else if (StrCaseStr(resultDetails, "resign")) {
10181                 SendToICS(ics_prefix);
10182                 SendToICS("resign\n");
10183             }
10184         }
10185 #endif
10186         endingGame = 0; /* [HGM] crash */
10187         return;
10188     }
10189
10190     /* If we're loading the game from a file, stop */
10191     if (whosays == GE_FILE) {
10192       (void) StopLoadGameTimer();
10193       gameFileFP = NULL;
10194     }
10195
10196     /* Cancel draw offers */
10197     first.offeredDraw = second.offeredDraw = 0;
10198
10199     /* If this is an ICS game, only ICS can really say it's done;
10200        if not, anyone can. */
10201     isIcsGame = (gameMode == IcsPlayingWhite ||
10202                  gameMode == IcsPlayingBlack ||
10203                  gameMode == IcsObserving    ||
10204                  gameMode == IcsExamining);
10205
10206     if (!isIcsGame || whosays == GE_ICS) {
10207         /* OK -- not an ICS game, or ICS said it was done */
10208         StopClocks();
10209         if (!isIcsGame && !appData.noChessProgram)
10210           SetUserThinkingEnables();
10211
10212         /* [HGM] if a machine claims the game end we verify this claim */
10213         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10214             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10215                 char claimer;
10216                 ChessMove trueResult = (ChessMove) -1;
10217
10218                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10219                                             first.twoMachinesColor[0] :
10220                                             second.twoMachinesColor[0] ;
10221
10222                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10223                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10224                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10225                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10226                 } else
10227                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10228                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10229                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10230                 } else
10231                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10232                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10233                 }
10234
10235                 // now verify win claims, but not in drop games, as we don't understand those yet
10236                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10237                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10238                     (result == WhiteWins && claimer == 'w' ||
10239                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10240                       if (appData.debugMode) {
10241                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10242                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10243                       }
10244                       if(result != trueResult) {
10245                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10246                               result = claimer == 'w' ? BlackWins : WhiteWins;
10247                               resultDetails = buf;
10248                       }
10249                 } else
10250                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10251                     && (forwardMostMove <= backwardMostMove ||
10252                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10253                         (claimer=='b')==(forwardMostMove&1))
10254                                                                                   ) {
10255                       /* [HGM] verify: draws that were not flagged are false claims */
10256                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10257                       result = claimer == 'w' ? BlackWins : WhiteWins;
10258                       resultDetails = buf;
10259                 }
10260                 /* (Claiming a loss is accepted no questions asked!) */
10261             }
10262             /* [HGM] bare: don't allow bare King to win */
10263             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10264                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10265                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10266                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10267                && result != GameIsDrawn)
10268             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10269                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10270                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10271                         if(p >= 0 && p <= (int)WhiteKing) k++;
10272                 }
10273                 if (appData.debugMode) {
10274                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10275                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10276                 }
10277                 if(k <= 1) {
10278                         result = GameIsDrawn;
10279                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10280                         resultDetails = buf;
10281                 }
10282             }
10283         }
10284
10285
10286         if(serverMoves != NULL && !loadFlag) { char c = '=';
10287             if(result==WhiteWins) c = '+';
10288             if(result==BlackWins) c = '-';
10289             if(resultDetails != NULL)
10290                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10291         }
10292         if (resultDetails != NULL) {
10293             gameInfo.result = result;
10294             gameInfo.resultDetails = StrSave(resultDetails);
10295
10296             /* display last move only if game was not loaded from file */
10297             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10298                 DisplayMove(currentMove - 1);
10299
10300             if (forwardMostMove != 0) {
10301                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10302                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10303                                                                 ) {
10304                     if (*appData.saveGameFile != NULLCHAR) {
10305                         SaveGameToFile(appData.saveGameFile, TRUE);
10306                     } else if (appData.autoSaveGames) {
10307                         AutoSaveGame();
10308                     }
10309                     if (*appData.savePositionFile != NULLCHAR) {
10310                         SavePositionToFile(appData.savePositionFile);
10311                     }
10312                 }
10313             }
10314
10315             /* Tell program how game ended in case it is learning */
10316             /* [HGM] Moved this to after saving the PGN, just in case */
10317             /* engine died and we got here through time loss. In that */
10318             /* case we will get a fatal error writing the pipe, which */
10319             /* would otherwise lose us the PGN.                       */
10320             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10321             /* output during GameEnds should never be fatal anymore   */
10322             if (gameMode == MachinePlaysWhite ||
10323                 gameMode == MachinePlaysBlack ||
10324                 gameMode == TwoMachinesPlay ||
10325                 gameMode == IcsPlayingWhite ||
10326                 gameMode == IcsPlayingBlack ||
10327                 gameMode == BeginningOfGame) {
10328                 char buf[MSG_SIZ];
10329                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10330                         resultDetails);
10331                 if (first.pr != NoProc) {
10332                     SendToProgram(buf, &first);
10333                 }
10334                 if (second.pr != NoProc &&
10335                     gameMode == TwoMachinesPlay) {
10336                     SendToProgram(buf, &second);
10337                 }
10338             }
10339         }
10340
10341         if (appData.icsActive) {
10342             if (appData.quietPlay &&
10343                 (gameMode == IcsPlayingWhite ||
10344                  gameMode == IcsPlayingBlack)) {
10345                 SendToICS(ics_prefix);
10346                 SendToICS("set shout 1\n");
10347             }
10348             nextGameMode = IcsIdle;
10349             ics_user_moved = FALSE;
10350             /* clean up premove.  It's ugly when the game has ended and the
10351              * premove highlights are still on the board.
10352              */
10353             if (gotPremove) {
10354               gotPremove = FALSE;
10355               ClearPremoveHighlights();
10356               DrawPosition(FALSE, boards[currentMove]);
10357             }
10358             if (whosays == GE_ICS) {
10359                 switch (result) {
10360                 case WhiteWins:
10361                     if (gameMode == IcsPlayingWhite)
10362                         PlayIcsWinSound();
10363                     else if(gameMode == IcsPlayingBlack)
10364                         PlayIcsLossSound();
10365                     break;
10366                 case BlackWins:
10367                     if (gameMode == IcsPlayingBlack)
10368                         PlayIcsWinSound();
10369                     else if(gameMode == IcsPlayingWhite)
10370                         PlayIcsLossSound();
10371                     break;
10372                 case GameIsDrawn:
10373                     PlayIcsDrawSound();
10374                     break;
10375                 default:
10376                     PlayIcsUnfinishedSound();
10377                 }
10378             }
10379         } else if (gameMode == EditGame ||
10380                    gameMode == PlayFromGameFile ||
10381                    gameMode == AnalyzeMode ||
10382                    gameMode == AnalyzeFile) {
10383             nextGameMode = gameMode;
10384         } else {
10385             nextGameMode = EndOfGame;
10386         }
10387         pausing = FALSE;
10388         ModeHighlight();
10389     } else {
10390         nextGameMode = gameMode;
10391     }
10392
10393     if (appData.noChessProgram) {
10394         gameMode = nextGameMode;
10395         ModeHighlight();
10396         endingGame = 0; /* [HGM] crash */
10397         return;
10398     }
10399
10400     if (first.reuse) {
10401         /* Put first chess program into idle state */
10402         if (first.pr != NoProc &&
10403             (gameMode == MachinePlaysWhite ||
10404              gameMode == MachinePlaysBlack ||
10405              gameMode == TwoMachinesPlay ||
10406              gameMode == IcsPlayingWhite ||
10407              gameMode == IcsPlayingBlack ||
10408              gameMode == BeginningOfGame)) {
10409             SendToProgram("force\n", &first);
10410             if (first.usePing) {
10411               char buf[MSG_SIZ];
10412               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10413               SendToProgram(buf, &first);
10414             }
10415         }
10416     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10417         /* Kill off first chess program */
10418         if (first.isr != NULL)
10419           RemoveInputSource(first.isr);
10420         first.isr = NULL;
10421
10422         if (first.pr != NoProc) {
10423             ExitAnalyzeMode();
10424             DoSleep( appData.delayBeforeQuit );
10425             SendToProgram("quit\n", &first);
10426             DoSleep( appData.delayAfterQuit );
10427             DestroyChildProcess(first.pr, first.useSigterm);
10428         }
10429         first.pr = NoProc;
10430     }
10431     if (second.reuse) {
10432         /* Put second chess program into idle state */
10433         if (second.pr != NoProc &&
10434             gameMode == TwoMachinesPlay) {
10435             SendToProgram("force\n", &second);
10436             if (second.usePing) {
10437               char buf[MSG_SIZ];
10438               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10439               SendToProgram(buf, &second);
10440             }
10441         }
10442     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10443         /* Kill off second chess program */
10444         if (second.isr != NULL)
10445           RemoveInputSource(second.isr);
10446         second.isr = NULL;
10447
10448         if (second.pr != NoProc) {
10449             DoSleep( appData.delayBeforeQuit );
10450             SendToProgram("quit\n", &second);
10451             DoSleep( appData.delayAfterQuit );
10452             DestroyChildProcess(second.pr, second.useSigterm);
10453         }
10454         second.pr = NoProc;
10455     }
10456
10457     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10458         char resChar = '=';
10459         switch (result) {
10460         case WhiteWins:
10461           resChar = '+';
10462           if (first.twoMachinesColor[0] == 'w') {
10463             first.matchWins++;
10464           } else {
10465             second.matchWins++;
10466           }
10467           break;
10468         case BlackWins:
10469           resChar = '-';
10470           if (first.twoMachinesColor[0] == 'b') {
10471             first.matchWins++;
10472           } else {
10473             second.matchWins++;
10474           }
10475           break;
10476         case GameUnfinished:
10477           resChar = ' ';
10478         default:
10479           break;
10480         }
10481
10482         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10483         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10484             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10485             ReserveGame(nextGame, resChar); // sets nextGame
10486             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10487             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10488         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10489
10490         if (nextGame <= appData.matchGames && !abortMatch) {
10491             gameMode = nextGameMode;
10492             matchGame = nextGame; // this will be overruled in tourney mode!
10493             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10494             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10495             endingGame = 0; /* [HGM] crash */
10496             return;
10497         } else {
10498             gameMode = nextGameMode;
10499             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10500                      first.tidy, second.tidy,
10501                      first.matchWins, second.matchWins,
10502                      appData.matchGames - (first.matchWins + second.matchWins));
10503             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10504             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10505             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10506             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10507                 first.twoMachinesColor = "black\n";
10508                 second.twoMachinesColor = "white\n";
10509             } else {
10510                 first.twoMachinesColor = "white\n";
10511                 second.twoMachinesColor = "black\n";
10512             }
10513         }
10514     }
10515     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10516         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10517       ExitAnalyzeMode();
10518     gameMode = nextGameMode;
10519     ModeHighlight();
10520     endingGame = 0;  /* [HGM] crash */
10521     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10522         if(matchMode == TRUE) { // match through command line: exit with or without popup
10523             if(ranking) {
10524                 ToNrEvent(forwardMostMove);
10525                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10526                 else ExitEvent(0);
10527             } else DisplayFatalError(buf, 0, 0);
10528         } else { // match through menu; just stop, with or without popup
10529             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10530             ModeHighlight();
10531             if(ranking){
10532                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10533             } else DisplayNote(buf);
10534       }
10535       if(ranking) free(ranking);
10536     }
10537 }
10538
10539 /* Assumes program was just initialized (initString sent).
10540    Leaves program in force mode. */
10541 void
10542 FeedMovesToProgram (ChessProgramState *cps, int upto)
10543 {
10544     int i;
10545
10546     if (appData.debugMode)
10547       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10548               startedFromSetupPosition ? "position and " : "",
10549               backwardMostMove, upto, cps->which);
10550     if(currentlyInitializedVariant != gameInfo.variant) {
10551       char buf[MSG_SIZ];
10552         // [HGM] variantswitch: make engine aware of new variant
10553         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10554                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10555         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10556         SendToProgram(buf, cps);
10557         currentlyInitializedVariant = gameInfo.variant;
10558     }
10559     SendToProgram("force\n", cps);
10560     if (startedFromSetupPosition) {
10561         SendBoard(cps, backwardMostMove);
10562     if (appData.debugMode) {
10563         fprintf(debugFP, "feedMoves\n");
10564     }
10565     }
10566     for (i = backwardMostMove; i < upto; i++) {
10567         SendMoveToProgram(i, cps);
10568     }
10569 }
10570
10571
10572 int
10573 ResurrectChessProgram ()
10574 {
10575      /* The chess program may have exited.
10576         If so, restart it and feed it all the moves made so far. */
10577     static int doInit = 0;
10578
10579     if (appData.noChessProgram) return 1;
10580
10581     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10582         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10583         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10584         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10585     } else {
10586         if (first.pr != NoProc) return 1;
10587         StartChessProgram(&first);
10588     }
10589     InitChessProgram(&first, FALSE);
10590     FeedMovesToProgram(&first, currentMove);
10591
10592     if (!first.sendTime) {
10593         /* can't tell gnuchess what its clock should read,
10594            so we bow to its notion. */
10595         ResetClocks();
10596         timeRemaining[0][currentMove] = whiteTimeRemaining;
10597         timeRemaining[1][currentMove] = blackTimeRemaining;
10598     }
10599
10600     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10601                 appData.icsEngineAnalyze) && first.analysisSupport) {
10602       SendToProgram("analyze\n", &first);
10603       first.analyzing = TRUE;
10604     }
10605     return 1;
10606 }
10607
10608 /*
10609  * Button procedures
10610  */
10611 void
10612 Reset (int redraw, int init)
10613 {
10614     int i;
10615
10616     if (appData.debugMode) {
10617         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10618                 redraw, init, gameMode);
10619     }
10620     CleanupTail(); // [HGM] vari: delete any stored variations
10621     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10622     pausing = pauseExamInvalid = FALSE;
10623     startedFromSetupPosition = blackPlaysFirst = FALSE;
10624     firstMove = TRUE;
10625     whiteFlag = blackFlag = FALSE;
10626     userOfferedDraw = FALSE;
10627     hintRequested = bookRequested = FALSE;
10628     first.maybeThinking = FALSE;
10629     second.maybeThinking = FALSE;
10630     first.bookSuspend = FALSE; // [HGM] book
10631     second.bookSuspend = FALSE;
10632     thinkOutput[0] = NULLCHAR;
10633     lastHint[0] = NULLCHAR;
10634     ClearGameInfo(&gameInfo);
10635     gameInfo.variant = StringToVariant(appData.variant);
10636     ics_user_moved = ics_clock_paused = FALSE;
10637     ics_getting_history = H_FALSE;
10638     ics_gamenum = -1;
10639     white_holding[0] = black_holding[0] = NULLCHAR;
10640     ClearProgramStats();
10641     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10642
10643     ResetFrontEnd();
10644     ClearHighlights();
10645     flipView = appData.flipView;
10646     ClearPremoveHighlights();
10647     gotPremove = FALSE;
10648     alarmSounded = FALSE;
10649
10650     GameEnds(EndOfFile, NULL, GE_PLAYER);
10651     if(appData.serverMovesName != NULL) {
10652         /* [HGM] prepare to make moves file for broadcasting */
10653         clock_t t = clock();
10654         if(serverMoves != NULL) fclose(serverMoves);
10655         serverMoves = fopen(appData.serverMovesName, "r");
10656         if(serverMoves != NULL) {
10657             fclose(serverMoves);
10658             /* delay 15 sec before overwriting, so all clients can see end */
10659             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10660         }
10661         serverMoves = fopen(appData.serverMovesName, "w");
10662     }
10663
10664     ExitAnalyzeMode();
10665     gameMode = BeginningOfGame;
10666     ModeHighlight();
10667     if(appData.icsActive) gameInfo.variant = VariantNormal;
10668     currentMove = forwardMostMove = backwardMostMove = 0;
10669     MarkTargetSquares(1);
10670     InitPosition(redraw);
10671     for (i = 0; i < MAX_MOVES; i++) {
10672         if (commentList[i] != NULL) {
10673             free(commentList[i]);
10674             commentList[i] = NULL;
10675         }
10676     }
10677     ResetClocks();
10678     timeRemaining[0][0] = whiteTimeRemaining;
10679     timeRemaining[1][0] = blackTimeRemaining;
10680
10681     if (first.pr == NoProc) {
10682         StartChessProgram(&first);
10683     }
10684     if (init) {
10685             InitChessProgram(&first, startedFromSetupPosition);
10686     }
10687     DisplayTitle("");
10688     DisplayMessage("", "");
10689     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10690     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10691 }
10692
10693 void
10694 AutoPlayGameLoop ()
10695 {
10696     for (;;) {
10697         if (!AutoPlayOneMove())
10698           return;
10699         if (matchMode || appData.timeDelay == 0)
10700           continue;
10701         if (appData.timeDelay < 0)
10702           return;
10703         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10704         break;
10705     }
10706 }
10707
10708
10709 int
10710 AutoPlayOneMove ()
10711 {
10712     int fromX, fromY, toX, toY;
10713
10714     if (appData.debugMode) {
10715       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10716     }
10717
10718     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10719       return FALSE;
10720
10721     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10722       pvInfoList[currentMove].depth = programStats.depth;
10723       pvInfoList[currentMove].score = programStats.score;
10724       pvInfoList[currentMove].time  = 0;
10725       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10726     }
10727
10728     if (currentMove >= forwardMostMove) {
10729       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10730 //      gameMode = EndOfGame;
10731 //      ModeHighlight();
10732
10733       /* [AS] Clear current move marker at the end of a game */
10734       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10735
10736       return FALSE;
10737     }
10738
10739     toX = moveList[currentMove][2] - AAA;
10740     toY = moveList[currentMove][3] - ONE;
10741
10742     if (moveList[currentMove][1] == '@') {
10743         if (appData.highlightLastMove) {
10744             SetHighlights(-1, -1, toX, toY);
10745         }
10746     } else {
10747         fromX = moveList[currentMove][0] - AAA;
10748         fromY = moveList[currentMove][1] - ONE;
10749
10750         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10751
10752         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10753
10754         if (appData.highlightLastMove) {
10755             SetHighlights(fromX, fromY, toX, toY);
10756         }
10757     }
10758     DisplayMove(currentMove);
10759     SendMoveToProgram(currentMove++, &first);
10760     DisplayBothClocks();
10761     DrawPosition(FALSE, boards[currentMove]);
10762     // [HGM] PV info: always display, routine tests if empty
10763     DisplayComment(currentMove - 1, commentList[currentMove]);
10764     return TRUE;
10765 }
10766
10767
10768 int
10769 LoadGameOneMove (ChessMove readAhead)
10770 {
10771     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10772     char promoChar = NULLCHAR;
10773     ChessMove moveType;
10774     char move[MSG_SIZ];
10775     char *p, *q;
10776
10777     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10778         gameMode != AnalyzeMode && gameMode != Training) {
10779         gameFileFP = NULL;
10780         return FALSE;
10781     }
10782
10783     yyboardindex = forwardMostMove;
10784     if (readAhead != EndOfFile) {
10785       moveType = readAhead;
10786     } else {
10787       if (gameFileFP == NULL)
10788           return FALSE;
10789       moveType = (ChessMove) Myylex();
10790     }
10791
10792     done = FALSE;
10793     switch (moveType) {
10794       case Comment:
10795         if (appData.debugMode)
10796           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10797         p = yy_text;
10798
10799         /* append the comment but don't display it */
10800         AppendComment(currentMove, p, FALSE);
10801         return TRUE;
10802
10803       case WhiteCapturesEnPassant:
10804       case BlackCapturesEnPassant:
10805       case WhitePromotion:
10806       case BlackPromotion:
10807       case WhiteNonPromotion:
10808       case BlackNonPromotion:
10809       case NormalMove:
10810       case WhiteKingSideCastle:
10811       case WhiteQueenSideCastle:
10812       case BlackKingSideCastle:
10813       case BlackQueenSideCastle:
10814       case WhiteKingSideCastleWild:
10815       case WhiteQueenSideCastleWild:
10816       case BlackKingSideCastleWild:
10817       case BlackQueenSideCastleWild:
10818       /* PUSH Fabien */
10819       case WhiteHSideCastleFR:
10820       case WhiteASideCastleFR:
10821       case BlackHSideCastleFR:
10822       case BlackASideCastleFR:
10823       /* POP Fabien */
10824         if (appData.debugMode)
10825           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10826         fromX = currentMoveString[0] - AAA;
10827         fromY = currentMoveString[1] - ONE;
10828         toX = currentMoveString[2] - AAA;
10829         toY = currentMoveString[3] - ONE;
10830         promoChar = currentMoveString[4];
10831         break;
10832
10833       case WhiteDrop:
10834       case BlackDrop:
10835         if (appData.debugMode)
10836           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10837         fromX = moveType == WhiteDrop ?
10838           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10839         (int) CharToPiece(ToLower(currentMoveString[0]));
10840         fromY = DROP_RANK;
10841         toX = currentMoveString[2] - AAA;
10842         toY = currentMoveString[3] - ONE;
10843         break;
10844
10845       case WhiteWins:
10846       case BlackWins:
10847       case GameIsDrawn:
10848       case GameUnfinished:
10849         if (appData.debugMode)
10850           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10851         p = strchr(yy_text, '{');
10852         if (p == NULL) p = strchr(yy_text, '(');
10853         if (p == NULL) {
10854             p = yy_text;
10855             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10856         } else {
10857             q = strchr(p, *p == '{' ? '}' : ')');
10858             if (q != NULL) *q = NULLCHAR;
10859             p++;
10860         }
10861         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10862         GameEnds(moveType, p, GE_FILE);
10863         done = TRUE;
10864         if (cmailMsgLoaded) {
10865             ClearHighlights();
10866             flipView = WhiteOnMove(currentMove);
10867             if (moveType == GameUnfinished) flipView = !flipView;
10868             if (appData.debugMode)
10869               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10870         }
10871         break;
10872
10873       case EndOfFile:
10874         if (appData.debugMode)
10875           fprintf(debugFP, "Parser hit end of file\n");
10876         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10877           case MT_NONE:
10878           case MT_CHECK:
10879             break;
10880           case MT_CHECKMATE:
10881           case MT_STAINMATE:
10882             if (WhiteOnMove(currentMove)) {
10883                 GameEnds(BlackWins, "Black mates", GE_FILE);
10884             } else {
10885                 GameEnds(WhiteWins, "White mates", GE_FILE);
10886             }
10887             break;
10888           case MT_STALEMATE:
10889             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10890             break;
10891         }
10892         done = TRUE;
10893         break;
10894
10895       case MoveNumberOne:
10896         if (lastLoadGameStart == GNUChessGame) {
10897             /* GNUChessGames have numbers, but they aren't move numbers */
10898             if (appData.debugMode)
10899               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10900                       yy_text, (int) moveType);
10901             return LoadGameOneMove(EndOfFile); /* tail recursion */
10902         }
10903         /* else fall thru */
10904
10905       case XBoardGame:
10906       case GNUChessGame:
10907       case PGNTag:
10908         /* Reached start of next game in file */
10909         if (appData.debugMode)
10910           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10911         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10912           case MT_NONE:
10913           case MT_CHECK:
10914             break;
10915           case MT_CHECKMATE:
10916           case MT_STAINMATE:
10917             if (WhiteOnMove(currentMove)) {
10918                 GameEnds(BlackWins, "Black mates", GE_FILE);
10919             } else {
10920                 GameEnds(WhiteWins, "White mates", GE_FILE);
10921             }
10922             break;
10923           case MT_STALEMATE:
10924             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10925             break;
10926         }
10927         done = TRUE;
10928         break;
10929
10930       case PositionDiagram:     /* should not happen; ignore */
10931       case ElapsedTime:         /* ignore */
10932       case NAG:                 /* ignore */
10933         if (appData.debugMode)
10934           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10935                   yy_text, (int) moveType);
10936         return LoadGameOneMove(EndOfFile); /* tail recursion */
10937
10938       case IllegalMove:
10939         if (appData.testLegality) {
10940             if (appData.debugMode)
10941               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10942             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10943                     (forwardMostMove / 2) + 1,
10944                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10945             DisplayError(move, 0);
10946             done = TRUE;
10947         } else {
10948             if (appData.debugMode)
10949               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10950                       yy_text, currentMoveString);
10951             fromX = currentMoveString[0] - AAA;
10952             fromY = currentMoveString[1] - ONE;
10953             toX = currentMoveString[2] - AAA;
10954             toY = currentMoveString[3] - ONE;
10955             promoChar = currentMoveString[4];
10956         }
10957         break;
10958
10959       case AmbiguousMove:
10960         if (appData.debugMode)
10961           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10962         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10963                 (forwardMostMove / 2) + 1,
10964                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10965         DisplayError(move, 0);
10966         done = TRUE;
10967         break;
10968
10969       default:
10970       case ImpossibleMove:
10971         if (appData.debugMode)
10972           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10973         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10974                 (forwardMostMove / 2) + 1,
10975                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10976         DisplayError(move, 0);
10977         done = TRUE;
10978         break;
10979     }
10980
10981     if (done) {
10982         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10983             DrawPosition(FALSE, boards[currentMove]);
10984             DisplayBothClocks();
10985             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10986               DisplayComment(currentMove - 1, commentList[currentMove]);
10987         }
10988         (void) StopLoadGameTimer();
10989         gameFileFP = NULL;
10990         cmailOldMove = forwardMostMove;
10991         return FALSE;
10992     } else {
10993         /* currentMoveString is set as a side-effect of yylex */
10994
10995         thinkOutput[0] = NULLCHAR;
10996         MakeMove(fromX, fromY, toX, toY, promoChar);
10997         currentMove = forwardMostMove;
10998         return TRUE;
10999     }
11000 }
11001
11002 /* Load the nth game from the given file */
11003 int
11004 LoadGameFromFile (char *filename, int n, char *title, int useList)
11005 {
11006     FILE *f;
11007     char buf[MSG_SIZ];
11008
11009     if (strcmp(filename, "-") == 0) {
11010         f = stdin;
11011         title = "stdin";
11012     } else {
11013         f = fopen(filename, "rb");
11014         if (f == NULL) {
11015           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11016             DisplayError(buf, errno);
11017             return FALSE;
11018         }
11019     }
11020     if (fseek(f, 0, 0) == -1) {
11021         /* f is not seekable; probably a pipe */
11022         useList = FALSE;
11023     }
11024     if (useList && n == 0) {
11025         int error = GameListBuild(f);
11026         if (error) {
11027             DisplayError(_("Cannot build game list"), error);
11028         } else if (!ListEmpty(&gameList) &&
11029                    ((ListGame *) gameList.tailPred)->number > 1) {
11030             GameListPopUp(f, title);
11031             return TRUE;
11032         }
11033         GameListDestroy();
11034         n = 1;
11035     }
11036     if (n == 0) n = 1;
11037     return LoadGame(f, n, title, FALSE);
11038 }
11039
11040
11041 void
11042 MakeRegisteredMove ()
11043 {
11044     int fromX, fromY, toX, toY;
11045     char promoChar;
11046     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11047         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11048           case CMAIL_MOVE:
11049           case CMAIL_DRAW:
11050             if (appData.debugMode)
11051               fprintf(debugFP, "Restoring %s for game %d\n",
11052                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11053
11054             thinkOutput[0] = NULLCHAR;
11055             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11056             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11057             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11058             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11059             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11060             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11061             MakeMove(fromX, fromY, toX, toY, promoChar);
11062             ShowMove(fromX, fromY, toX, toY);
11063
11064             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11065               case MT_NONE:
11066               case MT_CHECK:
11067                 break;
11068
11069               case MT_CHECKMATE:
11070               case MT_STAINMATE:
11071                 if (WhiteOnMove(currentMove)) {
11072                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11073                 } else {
11074                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11075                 }
11076                 break;
11077
11078               case MT_STALEMATE:
11079                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11080                 break;
11081             }
11082
11083             break;
11084
11085           case CMAIL_RESIGN:
11086             if (WhiteOnMove(currentMove)) {
11087                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11088             } else {
11089                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11090             }
11091             break;
11092
11093           case CMAIL_ACCEPT:
11094             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11095             break;
11096
11097           default:
11098             break;
11099         }
11100     }
11101
11102     return;
11103 }
11104
11105 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11106 int
11107 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11108 {
11109     int retVal;
11110
11111     if (gameNumber > nCmailGames) {
11112         DisplayError(_("No more games in this message"), 0);
11113         return FALSE;
11114     }
11115     if (f == lastLoadGameFP) {
11116         int offset = gameNumber - lastLoadGameNumber;
11117         if (offset == 0) {
11118             cmailMsg[0] = NULLCHAR;
11119             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11120                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11121                 nCmailMovesRegistered--;
11122             }
11123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11124             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11125                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11126             }
11127         } else {
11128             if (! RegisterMove()) return FALSE;
11129         }
11130     }
11131
11132     retVal = LoadGame(f, gameNumber, title, useList);
11133
11134     /* Make move registered during previous look at this game, if any */
11135     MakeRegisteredMove();
11136
11137     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11138         commentList[currentMove]
11139           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11140         DisplayComment(currentMove - 1, commentList[currentMove]);
11141     }
11142
11143     return retVal;
11144 }
11145
11146 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11147 int
11148 ReloadGame (int offset)
11149 {
11150     int gameNumber = lastLoadGameNumber + offset;
11151     if (lastLoadGameFP == NULL) {
11152         DisplayError(_("No game has been loaded yet"), 0);
11153         return FALSE;
11154     }
11155     if (gameNumber <= 0) {
11156         DisplayError(_("Can't back up any further"), 0);
11157         return FALSE;
11158     }
11159     if (cmailMsgLoaded) {
11160         return CmailLoadGame(lastLoadGameFP, gameNumber,
11161                              lastLoadGameTitle, lastLoadGameUseList);
11162     } else {
11163         return LoadGame(lastLoadGameFP, gameNumber,
11164                         lastLoadGameTitle, lastLoadGameUseList);
11165     }
11166 }
11167
11168 int keys[EmptySquare+1];
11169
11170 int
11171 PositionMatches (Board b1, Board b2)
11172 {
11173     int r, f, sum=0;
11174     switch(appData.searchMode) {
11175         case 1: return CompareWithRights(b1, b2);
11176         case 2:
11177             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11178                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11179             }
11180             return TRUE;
11181         case 3:
11182             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11183               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11184                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11185             }
11186             return sum==0;
11187         case 4:
11188             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11189                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11190             }
11191             return sum==0;
11192     }
11193     return TRUE;
11194 }
11195
11196 #define Q_PROMO  4
11197 #define Q_EP     3
11198 #define Q_BCASTL 2
11199 #define Q_WCASTL 1
11200
11201 int pieceList[256], quickBoard[256];
11202 ChessSquare pieceType[256] = { EmptySquare };
11203 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11204 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11205 int soughtTotal, turn;
11206 Boolean epOK, flipSearch;
11207
11208 typedef struct {
11209     unsigned char piece, to;
11210 } Move;
11211
11212 #define DSIZE (250000)
11213
11214 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11215 Move *moveDatabase = initialSpace;
11216 unsigned int movePtr, dataSize = DSIZE;
11217
11218 int
11219 MakePieceList (Board board, int *counts)
11220 {
11221     int r, f, n=Q_PROMO, total=0;
11222     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11223     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11224         int sq = f + (r<<4);
11225         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11226             quickBoard[sq] = ++n;
11227             pieceList[n] = sq;
11228             pieceType[n] = board[r][f];
11229             counts[board[r][f]]++;
11230             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11231             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11232             total++;
11233         }
11234     }
11235     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11236     return total;
11237 }
11238
11239 void
11240 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11241 {
11242     int sq = fromX + (fromY<<4);
11243     int piece = quickBoard[sq];
11244     quickBoard[sq] = 0;
11245     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11246     if(piece == pieceList[1] && 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;
11248         moveDatabase[movePtr++].piece = Q_WCASTL;
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(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11254         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11255         moveDatabase[movePtr++].piece = Q_BCASTL;
11256         quickBoard[sq] = piece;
11257         piece = quickBoard[from]; quickBoard[from] = 0;
11258         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11259     } else
11260     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11261         quickBoard[(fromY<<4)+toX] = 0;
11262         moveDatabase[movePtr].piece = Q_EP;
11263         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11264         moveDatabase[movePtr].to = sq;
11265     } else
11266     if(promoPiece != pieceType[piece]) {
11267         moveDatabase[movePtr++].piece = Q_PROMO;
11268         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11269     }
11270     moveDatabase[movePtr].piece = piece;
11271     quickBoard[sq] = piece;
11272     movePtr++;
11273 }
11274
11275 int
11276 PackGame (Board board)
11277 {
11278     Move *newSpace = NULL;
11279     moveDatabase[movePtr].piece = 0; // terminate previous game
11280     if(movePtr > dataSize) {
11281         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11282         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11283         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11284         if(newSpace) {
11285             int i;
11286             Move *p = moveDatabase, *q = newSpace;
11287             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11288             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11289             moveDatabase = newSpace;
11290         } else { // calloc failed, we must be out of memory. Too bad...
11291             dataSize = 0; // prevent calloc events for all subsequent games
11292             return 0;     // and signal this one isn't cached
11293         }
11294     }
11295     movePtr++;
11296     MakePieceList(board, counts);
11297     return movePtr;
11298 }
11299
11300 int
11301 QuickCompare (Board board, int *minCounts, int *maxCounts)
11302 {   // compare according to search mode
11303     int r, f;
11304     switch(appData.searchMode)
11305     {
11306       case 1: // exact position match
11307         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11308         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11309             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11310         }
11311         break;
11312       case 2: // can have extra material on empty squares
11313         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11314             if(board[r][f] == EmptySquare) continue;
11315             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11316         }
11317         break;
11318       case 3: // material with exact Pawn structure
11319         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11320             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11321             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11322         } // fall through to material comparison
11323       case 4: // exact material
11324         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11325         break;
11326       case 6: // material range with given imbalance
11327         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11328         // fall through to range comparison
11329       case 5: // material range
11330         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11331     }
11332     return TRUE;
11333 }
11334
11335 int
11336 QuickScan (Board board, Move *move)
11337 {   // reconstruct game,and compare all positions in it
11338     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11339     do {
11340         int piece = move->piece;
11341         int to = move->to, from = pieceList[piece];
11342         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11343           if(!piece) return -1;
11344           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11345             piece = (++move)->piece;
11346             from = pieceList[piece];
11347             counts[pieceType[piece]]--;
11348             pieceType[piece] = (ChessSquare) move->to;
11349             counts[move->to]++;
11350           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11351             counts[pieceType[quickBoard[to]]]--;
11352             quickBoard[to] = 0; total--;
11353             move++;
11354             continue;
11355           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11356             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11357             from  = pieceList[piece]; // so this must be King
11358             quickBoard[from] = 0;
11359             quickBoard[to] = piece;
11360             pieceList[piece] = to;
11361             move++;
11362             continue;
11363           }
11364         }
11365         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11366         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11367         quickBoard[from] = 0;
11368         quickBoard[to] = piece;
11369         pieceList[piece] = to;
11370         cnt++; turn ^= 3;
11371         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11372            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11373            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11374                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11375           ) {
11376             static int lastCounts[EmptySquare+1];
11377             int i;
11378             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11379             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11380         } else stretch = 0;
11381         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11382         move++;
11383     } while(1);
11384 }
11385
11386 void
11387 InitSearch ()
11388 {
11389     int r, f;
11390     flipSearch = FALSE;
11391     CopyBoard(soughtBoard, boards[currentMove]);
11392     soughtTotal = MakePieceList(soughtBoard, maxSought);
11393     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11394     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11395     CopyBoard(reverseBoard, boards[currentMove]);
11396     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11397         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11398         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11399         reverseBoard[r][f] = piece;
11400     }
11401     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11402     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11403     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11404                  || (boards[currentMove][CASTLING][2] == NoRights || 
11405                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11406                  && (boards[currentMove][CASTLING][5] == NoRights || 
11407                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11408       ) {
11409         flipSearch = TRUE;
11410         CopyBoard(flipBoard, soughtBoard);
11411         CopyBoard(rotateBoard, reverseBoard);
11412         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11413             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11414             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11415         }
11416     }
11417     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11418     if(appData.searchMode >= 5) {
11419         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11420         MakePieceList(soughtBoard, minSought);
11421         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11422     }
11423     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11424         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11425 }
11426
11427 GameInfo dummyInfo;
11428
11429 int
11430 GameContainsPosition (FILE *f, ListGame *lg)
11431 {
11432     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11433     int fromX, fromY, toX, toY;
11434     char promoChar;
11435     static int initDone=FALSE;
11436
11437     // weed out games based on numerical tag comparison
11438     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11439     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11440     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11441     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11442     if(!initDone) {
11443         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11444         initDone = TRUE;
11445     }
11446     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11447     else CopyBoard(boards[scratch], initialPosition); // default start position
11448     if(lg->moves) {
11449         turn = btm + 1;
11450         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11451         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11452     }
11453     if(btm) plyNr++;
11454     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11455     fseek(f, lg->offset, 0);
11456     yynewfile(f);
11457     while(1) {
11458         yyboardindex = scratch;
11459         quickFlag = plyNr+1;
11460         next = Myylex();
11461         quickFlag = 0;
11462         switch(next) {
11463             case PGNTag:
11464                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11465             default:
11466                 continue;
11467
11468             case XBoardGame:
11469             case GNUChessGame:
11470                 if(plyNr) return -1; // after we have seen moves, this is for new game
11471               continue;
11472
11473             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11474             case ImpossibleMove:
11475             case WhiteWins: // game ends here with these four
11476             case BlackWins:
11477             case GameIsDrawn:
11478             case GameUnfinished:
11479                 return -1;
11480
11481             case IllegalMove:
11482                 if(appData.testLegality) return -1;
11483             case WhiteCapturesEnPassant:
11484             case BlackCapturesEnPassant:
11485             case WhitePromotion:
11486             case BlackPromotion:
11487             case WhiteNonPromotion:
11488             case BlackNonPromotion:
11489             case NormalMove:
11490             case WhiteKingSideCastle:
11491             case WhiteQueenSideCastle:
11492             case BlackKingSideCastle:
11493             case BlackQueenSideCastle:
11494             case WhiteKingSideCastleWild:
11495             case WhiteQueenSideCastleWild:
11496             case BlackKingSideCastleWild:
11497             case BlackQueenSideCastleWild:
11498             case WhiteHSideCastleFR:
11499             case WhiteASideCastleFR:
11500             case BlackHSideCastleFR:
11501             case BlackASideCastleFR:
11502                 fromX = currentMoveString[0] - AAA;
11503                 fromY = currentMoveString[1] - ONE;
11504                 toX = currentMoveString[2] - AAA;
11505                 toY = currentMoveString[3] - ONE;
11506                 promoChar = currentMoveString[4];
11507                 break;
11508             case WhiteDrop:
11509             case BlackDrop:
11510                 fromX = next == WhiteDrop ?
11511                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11512                   (int) CharToPiece(ToLower(currentMoveString[0]));
11513                 fromY = DROP_RANK;
11514                 toX = currentMoveString[2] - AAA;
11515                 toY = currentMoveString[3] - ONE;
11516                 promoChar = 0;
11517                 break;
11518         }
11519         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11520         plyNr++;
11521         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11522         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11523         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11524         if(appData.findMirror) {
11525             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11526             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11527         }
11528     }
11529 }
11530
11531 /* Load the nth game from open file f */
11532 int
11533 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11534 {
11535     ChessMove cm;
11536     char buf[MSG_SIZ];
11537     int gn = gameNumber;
11538     ListGame *lg = NULL;
11539     int numPGNTags = 0;
11540     int err, pos = -1;
11541     GameMode oldGameMode;
11542     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11543
11544     if (appData.debugMode)
11545         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11546
11547     if (gameMode == Training )
11548         SetTrainingModeOff();
11549
11550     oldGameMode = gameMode;
11551     if (gameMode != BeginningOfGame) {
11552       Reset(FALSE, TRUE);
11553     }
11554
11555     gameFileFP = f;
11556     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11557         fclose(lastLoadGameFP);
11558     }
11559
11560     if (useList) {
11561         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11562
11563         if (lg) {
11564             fseek(f, lg->offset, 0);
11565             GameListHighlight(gameNumber);
11566             pos = lg->position;
11567             gn = 1;
11568         }
11569         else {
11570             DisplayError(_("Game number out of range"), 0);
11571             return FALSE;
11572         }
11573     } else {
11574         GameListDestroy();
11575         if (fseek(f, 0, 0) == -1) {
11576             if (f == lastLoadGameFP ?
11577                 gameNumber == lastLoadGameNumber + 1 :
11578                 gameNumber == 1) {
11579                 gn = 1;
11580             } else {
11581                 DisplayError(_("Can't seek on game file"), 0);
11582                 return FALSE;
11583             }
11584         }
11585     }
11586     lastLoadGameFP = f;
11587     lastLoadGameNumber = gameNumber;
11588     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11589     lastLoadGameUseList = useList;
11590
11591     yynewfile(f);
11592
11593     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11594       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11595                 lg->gameInfo.black);
11596             DisplayTitle(buf);
11597     } else if (*title != NULLCHAR) {
11598         if (gameNumber > 1) {
11599           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11600             DisplayTitle(buf);
11601         } else {
11602             DisplayTitle(title);
11603         }
11604     }
11605
11606     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11607         gameMode = PlayFromGameFile;
11608         ModeHighlight();
11609     }
11610
11611     currentMove = forwardMostMove = backwardMostMove = 0;
11612     CopyBoard(boards[0], initialPosition);
11613     StopClocks();
11614
11615     /*
11616      * Skip the first gn-1 games in the file.
11617      * Also skip over anything that precedes an identifiable
11618      * start of game marker, to avoid being confused by
11619      * garbage at the start of the file.  Currently
11620      * recognized start of game markers are the move number "1",
11621      * the pattern "gnuchess .* game", the pattern
11622      * "^[#;%] [^ ]* game file", and a PGN tag block.
11623      * A game that starts with one of the latter two patterns
11624      * will also have a move number 1, possibly
11625      * following a position diagram.
11626      * 5-4-02: Let's try being more lenient and allowing a game to
11627      * start with an unnumbered move.  Does that break anything?
11628      */
11629     cm = lastLoadGameStart = EndOfFile;
11630     while (gn > 0) {
11631         yyboardindex = forwardMostMove;
11632         cm = (ChessMove) Myylex();
11633         switch (cm) {
11634           case EndOfFile:
11635             if (cmailMsgLoaded) {
11636                 nCmailGames = CMAIL_MAX_GAMES - gn;
11637             } else {
11638                 Reset(TRUE, TRUE);
11639                 DisplayError(_("Game not found in file"), 0);
11640             }
11641             return FALSE;
11642
11643           case GNUChessGame:
11644           case XBoardGame:
11645             gn--;
11646             lastLoadGameStart = cm;
11647             break;
11648
11649           case MoveNumberOne:
11650             switch (lastLoadGameStart) {
11651               case GNUChessGame:
11652               case XBoardGame:
11653               case PGNTag:
11654                 break;
11655               case MoveNumberOne:
11656               case EndOfFile:
11657                 gn--;           /* count this game */
11658                 lastLoadGameStart = cm;
11659                 break;
11660               default:
11661                 /* impossible */
11662                 break;
11663             }
11664             break;
11665
11666           case PGNTag:
11667             switch (lastLoadGameStart) {
11668               case GNUChessGame:
11669               case PGNTag:
11670               case MoveNumberOne:
11671               case EndOfFile:
11672                 gn--;           /* count this game */
11673                 lastLoadGameStart = cm;
11674                 break;
11675               case XBoardGame:
11676                 lastLoadGameStart = cm; /* game counted already */
11677                 break;
11678               default:
11679                 /* impossible */
11680                 break;
11681             }
11682             if (gn > 0) {
11683                 do {
11684                     yyboardindex = forwardMostMove;
11685                     cm = (ChessMove) Myylex();
11686                 } while (cm == PGNTag || cm == Comment);
11687             }
11688             break;
11689
11690           case WhiteWins:
11691           case BlackWins:
11692           case GameIsDrawn:
11693             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11694                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11695                     != CMAIL_OLD_RESULT) {
11696                     nCmailResults ++ ;
11697                     cmailResult[  CMAIL_MAX_GAMES
11698                                 - gn - 1] = CMAIL_OLD_RESULT;
11699                 }
11700             }
11701             break;
11702
11703           case NormalMove:
11704             /* Only a NormalMove can be at the start of a game
11705              * without a position diagram. */
11706             if (lastLoadGameStart == EndOfFile ) {
11707               gn--;
11708               lastLoadGameStart = MoveNumberOne;
11709             }
11710             break;
11711
11712           default:
11713             break;
11714         }
11715     }
11716
11717     if (appData.debugMode)
11718       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11719
11720     if (cm == XBoardGame) {
11721         /* Skip any header junk before position diagram and/or move 1 */
11722         for (;;) {
11723             yyboardindex = forwardMostMove;
11724             cm = (ChessMove) Myylex();
11725
11726             if (cm == EndOfFile ||
11727                 cm == GNUChessGame || cm == XBoardGame) {
11728                 /* Empty game; pretend end-of-file and handle later */
11729                 cm = EndOfFile;
11730                 break;
11731             }
11732
11733             if (cm == MoveNumberOne || cm == PositionDiagram ||
11734                 cm == PGNTag || cm == Comment)
11735               break;
11736         }
11737     } else if (cm == GNUChessGame) {
11738         if (gameInfo.event != NULL) {
11739             free(gameInfo.event);
11740         }
11741         gameInfo.event = StrSave(yy_text);
11742     }
11743
11744     startedFromSetupPosition = FALSE;
11745     while (cm == PGNTag) {
11746         if (appData.debugMode)
11747           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11748         err = ParsePGNTag(yy_text, &gameInfo);
11749         if (!err) numPGNTags++;
11750
11751         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11752         if(gameInfo.variant != oldVariant) {
11753             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11754             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11755             InitPosition(TRUE);
11756             oldVariant = gameInfo.variant;
11757             if (appData.debugMode)
11758               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11759         }
11760
11761
11762         if (gameInfo.fen != NULL) {
11763           Board initial_position;
11764           startedFromSetupPosition = TRUE;
11765           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11766             Reset(TRUE, TRUE);
11767             DisplayError(_("Bad FEN position in file"), 0);
11768             return FALSE;
11769           }
11770           CopyBoard(boards[0], initial_position);
11771           if (blackPlaysFirst) {
11772             currentMove = forwardMostMove = backwardMostMove = 1;
11773             CopyBoard(boards[1], initial_position);
11774             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11775             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11776             timeRemaining[0][1] = whiteTimeRemaining;
11777             timeRemaining[1][1] = blackTimeRemaining;
11778             if (commentList[0] != NULL) {
11779               commentList[1] = commentList[0];
11780               commentList[0] = NULL;
11781             }
11782           } else {
11783             currentMove = forwardMostMove = backwardMostMove = 0;
11784           }
11785           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11786           {   int i;
11787               initialRulePlies = FENrulePlies;
11788               for( i=0; i< nrCastlingRights; i++ )
11789                   initialRights[i] = initial_position[CASTLING][i];
11790           }
11791           yyboardindex = forwardMostMove;
11792           free(gameInfo.fen);
11793           gameInfo.fen = NULL;
11794         }
11795
11796         yyboardindex = forwardMostMove;
11797         cm = (ChessMove) Myylex();
11798
11799         /* Handle comments interspersed among the tags */
11800         while (cm == Comment) {
11801             char *p;
11802             if (appData.debugMode)
11803               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11804             p = yy_text;
11805             AppendComment(currentMove, p, FALSE);
11806             yyboardindex = forwardMostMove;
11807             cm = (ChessMove) Myylex();
11808         }
11809     }
11810
11811     /* don't rely on existence of Event tag since if game was
11812      * pasted from clipboard the Event tag may not exist
11813      */
11814     if (numPGNTags > 0){
11815         char *tags;
11816         if (gameInfo.variant == VariantNormal) {
11817           VariantClass v = StringToVariant(gameInfo.event);
11818           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11819           if(v < VariantShogi) gameInfo.variant = v;
11820         }
11821         if (!matchMode) {
11822           if( appData.autoDisplayTags ) {
11823             tags = PGNTags(&gameInfo);
11824             TagsPopUp(tags, CmailMsg());
11825             free(tags);
11826           }
11827         }
11828     } else {
11829         /* Make something up, but don't display it now */
11830         SetGameInfo();
11831         TagsPopDown();
11832     }
11833
11834     if (cm == PositionDiagram) {
11835         int i, j;
11836         char *p;
11837         Board initial_position;
11838
11839         if (appData.debugMode)
11840           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11841
11842         if (!startedFromSetupPosition) {
11843             p = yy_text;
11844             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11845               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11846                 switch (*p) {
11847                   case '{':
11848                   case '[':
11849                   case '-':
11850                   case ' ':
11851                   case '\t':
11852                   case '\n':
11853                   case '\r':
11854                     break;
11855                   default:
11856                     initial_position[i][j++] = CharToPiece(*p);
11857                     break;
11858                 }
11859             while (*p == ' ' || *p == '\t' ||
11860                    *p == '\n' || *p == '\r') p++;
11861
11862             if (strncmp(p, "black", strlen("black"))==0)
11863               blackPlaysFirst = TRUE;
11864             else
11865               blackPlaysFirst = FALSE;
11866             startedFromSetupPosition = TRUE;
11867
11868             CopyBoard(boards[0], initial_position);
11869             if (blackPlaysFirst) {
11870                 currentMove = forwardMostMove = backwardMostMove = 1;
11871                 CopyBoard(boards[1], initial_position);
11872                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11873                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11874                 timeRemaining[0][1] = whiteTimeRemaining;
11875                 timeRemaining[1][1] = blackTimeRemaining;
11876                 if (commentList[0] != NULL) {
11877                     commentList[1] = commentList[0];
11878                     commentList[0] = NULL;
11879                 }
11880             } else {
11881                 currentMove = forwardMostMove = backwardMostMove = 0;
11882             }
11883         }
11884         yyboardindex = forwardMostMove;
11885         cm = (ChessMove) Myylex();
11886     }
11887
11888     if (first.pr == NoProc) {
11889         StartChessProgram(&first);
11890     }
11891     InitChessProgram(&first, FALSE);
11892     SendToProgram("force\n", &first);
11893     if (startedFromSetupPosition) {
11894         SendBoard(&first, forwardMostMove);
11895     if (appData.debugMode) {
11896         fprintf(debugFP, "Load Game\n");
11897     }
11898         DisplayBothClocks();
11899     }
11900
11901     /* [HGM] server: flag to write setup moves in broadcast file as one */
11902     loadFlag = appData.suppressLoadMoves;
11903
11904     while (cm == Comment) {
11905         char *p;
11906         if (appData.debugMode)
11907           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11908         p = yy_text;
11909         AppendComment(currentMove, p, FALSE);
11910         yyboardindex = forwardMostMove;
11911         cm = (ChessMove) Myylex();
11912     }
11913
11914     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11915         cm == WhiteWins || cm == BlackWins ||
11916         cm == GameIsDrawn || cm == GameUnfinished) {
11917         DisplayMessage("", _("No moves in game"));
11918         if (cmailMsgLoaded) {
11919             if (appData.debugMode)
11920               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11921             ClearHighlights();
11922             flipView = FALSE;
11923         }
11924         DrawPosition(FALSE, boards[currentMove]);
11925         DisplayBothClocks();
11926         gameMode = EditGame;
11927         ModeHighlight();
11928         gameFileFP = NULL;
11929         cmailOldMove = 0;
11930         return TRUE;
11931     }
11932
11933     // [HGM] PV info: routine tests if comment empty
11934     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11935         DisplayComment(currentMove - 1, commentList[currentMove]);
11936     }
11937     if (!matchMode && appData.timeDelay != 0)
11938       DrawPosition(FALSE, boards[currentMove]);
11939
11940     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11941       programStats.ok_to_send = 1;
11942     }
11943
11944     /* if the first token after the PGN tags is a move
11945      * and not move number 1, retrieve it from the parser
11946      */
11947     if (cm != MoveNumberOne)
11948         LoadGameOneMove(cm);
11949
11950     /* load the remaining moves from the file */
11951     while (LoadGameOneMove(EndOfFile)) {
11952       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11953       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11954     }
11955
11956     /* rewind to the start of the game */
11957     currentMove = backwardMostMove;
11958
11959     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11960
11961     if (oldGameMode == AnalyzeFile ||
11962         oldGameMode == AnalyzeMode) {
11963       AnalyzeFileEvent();
11964     }
11965
11966     if (!matchMode && pos >= 0) {
11967         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11968     } else
11969     if (matchMode || appData.timeDelay == 0) {
11970       ToEndEvent();
11971     } else if (appData.timeDelay > 0) {
11972       AutoPlayGameLoop();
11973     }
11974
11975     if (appData.debugMode)
11976         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11977
11978     loadFlag = 0; /* [HGM] true game starts */
11979     return TRUE;
11980 }
11981
11982 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11983 int
11984 ReloadPosition (int offset)
11985 {
11986     int positionNumber = lastLoadPositionNumber + offset;
11987     if (lastLoadPositionFP == NULL) {
11988         DisplayError(_("No position has been loaded yet"), 0);
11989         return FALSE;
11990     }
11991     if (positionNumber <= 0) {
11992         DisplayError(_("Can't back up any further"), 0);
11993         return FALSE;
11994     }
11995     return LoadPosition(lastLoadPositionFP, positionNumber,
11996                         lastLoadPositionTitle);
11997 }
11998
11999 /* Load the nth position from the given file */
12000 int
12001 LoadPositionFromFile (char *filename, int n, char *title)
12002 {
12003     FILE *f;
12004     char buf[MSG_SIZ];
12005
12006     if (strcmp(filename, "-") == 0) {
12007         return LoadPosition(stdin, n, "stdin");
12008     } else {
12009         f = fopen(filename, "rb");
12010         if (f == NULL) {
12011             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12012             DisplayError(buf, errno);
12013             return FALSE;
12014         } else {
12015             return LoadPosition(f, n, title);
12016         }
12017     }
12018 }
12019
12020 /* Load the nth position from the given open file, and close it */
12021 int
12022 LoadPosition (FILE *f, int positionNumber, char *title)
12023 {
12024     char *p, line[MSG_SIZ];
12025     Board initial_position;
12026     int i, j, fenMode, pn;
12027
12028     if (gameMode == Training )
12029         SetTrainingModeOff();
12030
12031     if (gameMode != BeginningOfGame) {
12032         Reset(FALSE, TRUE);
12033     }
12034     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12035         fclose(lastLoadPositionFP);
12036     }
12037     if (positionNumber == 0) positionNumber = 1;
12038     lastLoadPositionFP = f;
12039     lastLoadPositionNumber = positionNumber;
12040     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12041     if (first.pr == NoProc && !appData.noChessProgram) {
12042       StartChessProgram(&first);
12043       InitChessProgram(&first, FALSE);
12044     }
12045     pn = positionNumber;
12046     if (positionNumber < 0) {
12047         /* Negative position number means to seek to that byte offset */
12048         if (fseek(f, -positionNumber, 0) == -1) {
12049             DisplayError(_("Can't seek on position file"), 0);
12050             return FALSE;
12051         };
12052         pn = 1;
12053     } else {
12054         if (fseek(f, 0, 0) == -1) {
12055             if (f == lastLoadPositionFP ?
12056                 positionNumber == lastLoadPositionNumber + 1 :
12057                 positionNumber == 1) {
12058                 pn = 1;
12059             } else {
12060                 DisplayError(_("Can't seek on position file"), 0);
12061                 return FALSE;
12062             }
12063         }
12064     }
12065     /* See if this file is FEN or old-style xboard */
12066     if (fgets(line, MSG_SIZ, f) == NULL) {
12067         DisplayError(_("Position not found in file"), 0);
12068         return FALSE;
12069     }
12070     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12071     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12072
12073     if (pn >= 2) {
12074         if (fenMode || line[0] == '#') pn--;
12075         while (pn > 0) {
12076             /* skip positions before number pn */
12077             if (fgets(line, MSG_SIZ, f) == NULL) {
12078                 Reset(TRUE, TRUE);
12079                 DisplayError(_("Position not found in file"), 0);
12080                 return FALSE;
12081             }
12082             if (fenMode || line[0] == '#') pn--;
12083         }
12084     }
12085
12086     if (fenMode) {
12087         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12088             DisplayError(_("Bad FEN position in file"), 0);
12089             return FALSE;
12090         }
12091     } else {
12092         (void) fgets(line, MSG_SIZ, f);
12093         (void) fgets(line, MSG_SIZ, f);
12094
12095         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12096             (void) fgets(line, MSG_SIZ, f);
12097             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12098                 if (*p == ' ')
12099                   continue;
12100                 initial_position[i][j++] = CharToPiece(*p);
12101             }
12102         }
12103
12104         blackPlaysFirst = FALSE;
12105         if (!feof(f)) {
12106             (void) fgets(line, MSG_SIZ, f);
12107             if (strncmp(line, "black", strlen("black"))==0)
12108               blackPlaysFirst = TRUE;
12109         }
12110     }
12111     startedFromSetupPosition = TRUE;
12112
12113     CopyBoard(boards[0], initial_position);
12114     if (blackPlaysFirst) {
12115         currentMove = forwardMostMove = backwardMostMove = 1;
12116         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12117         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12118         CopyBoard(boards[1], initial_position);
12119         DisplayMessage("", _("Black to play"));
12120     } else {
12121         currentMove = forwardMostMove = backwardMostMove = 0;
12122         DisplayMessage("", _("White to play"));
12123     }
12124     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12125     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12126         SendToProgram("force\n", &first);
12127         SendBoard(&first, forwardMostMove);
12128     }
12129     if (appData.debugMode) {
12130 int i, j;
12131   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12132   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12133         fprintf(debugFP, "Load Position\n");
12134     }
12135
12136     if (positionNumber > 1) {
12137       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12138         DisplayTitle(line);
12139     } else {
12140         DisplayTitle(title);
12141     }
12142     gameMode = EditGame;
12143     ModeHighlight();
12144     ResetClocks();
12145     timeRemaining[0][1] = whiteTimeRemaining;
12146     timeRemaining[1][1] = blackTimeRemaining;
12147     DrawPosition(FALSE, boards[currentMove]);
12148
12149     return TRUE;
12150 }
12151
12152
12153 void
12154 CopyPlayerNameIntoFileName (char **dest, char *src)
12155 {
12156     while (*src != NULLCHAR && *src != ',') {
12157         if (*src == ' ') {
12158             *(*dest)++ = '_';
12159             src++;
12160         } else {
12161             *(*dest)++ = *src++;
12162         }
12163     }
12164 }
12165
12166 char *
12167 DefaultFileName (char *ext)
12168 {
12169     static char def[MSG_SIZ];
12170     char *p;
12171
12172     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12173         p = def;
12174         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12175         *p++ = '-';
12176         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12177         *p++ = '.';
12178         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12179     } else {
12180         def[0] = NULLCHAR;
12181     }
12182     return def;
12183 }
12184
12185 /* Save the current game to the given file */
12186 int
12187 SaveGameToFile (char *filename, int append)
12188 {
12189     FILE *f;
12190     char buf[MSG_SIZ];
12191     int result, i, t,tot=0;
12192
12193     if (strcmp(filename, "-") == 0) {
12194         return SaveGame(stdout, 0, NULL);
12195     } else {
12196         for(i=0; i<10; i++) { // upto 10 tries
12197              f = fopen(filename, append ? "a" : "w");
12198              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12199              if(f || errno != 13) break;
12200              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12201              tot += t;
12202         }
12203         if (f == NULL) {
12204             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12205             DisplayError(buf, errno);
12206             return FALSE;
12207         } else {
12208             safeStrCpy(buf, lastMsg, MSG_SIZ);
12209             DisplayMessage(_("Waiting for access to save file"), "");
12210             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12211             DisplayMessage(_("Saving game"), "");
12212             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12213             result = SaveGame(f, 0, NULL);
12214             DisplayMessage(buf, "");
12215             return result;
12216         }
12217     }
12218 }
12219
12220 char *
12221 SavePart (char *str)
12222 {
12223     static char buf[MSG_SIZ];
12224     char *p;
12225
12226     p = strchr(str, ' ');
12227     if (p == NULL) return str;
12228     strncpy(buf, str, p - str);
12229     buf[p - str] = NULLCHAR;
12230     return buf;
12231 }
12232
12233 #define PGN_MAX_LINE 75
12234
12235 #define PGN_SIDE_WHITE  0
12236 #define PGN_SIDE_BLACK  1
12237
12238 static int
12239 FindFirstMoveOutOfBook (int side)
12240 {
12241     int result = -1;
12242
12243     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12244         int index = backwardMostMove;
12245         int has_book_hit = 0;
12246
12247         if( (index % 2) != side ) {
12248             index++;
12249         }
12250
12251         while( index < forwardMostMove ) {
12252             /* Check to see if engine is in book */
12253             int depth = pvInfoList[index].depth;
12254             int score = pvInfoList[index].score;
12255             int in_book = 0;
12256
12257             if( depth <= 2 ) {
12258                 in_book = 1;
12259             }
12260             else if( score == 0 && depth == 63 ) {
12261                 in_book = 1; /* Zappa */
12262             }
12263             else if( score == 2 && depth == 99 ) {
12264                 in_book = 1; /* Abrok */
12265             }
12266
12267             has_book_hit += in_book;
12268
12269             if( ! in_book ) {
12270                 result = index;
12271
12272                 break;
12273             }
12274
12275             index += 2;
12276         }
12277     }
12278
12279     return result;
12280 }
12281
12282 void
12283 GetOutOfBookInfo (char * buf)
12284 {
12285     int oob[2];
12286     int i;
12287     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12288
12289     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12290     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12291
12292     *buf = '\0';
12293
12294     if( oob[0] >= 0 || oob[1] >= 0 ) {
12295         for( i=0; i<2; i++ ) {
12296             int idx = oob[i];
12297
12298             if( idx >= 0 ) {
12299                 if( i > 0 && oob[0] >= 0 ) {
12300                     strcat( buf, "   " );
12301                 }
12302
12303                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12304                 sprintf( buf+strlen(buf), "%s%.2f",
12305                     pvInfoList[idx].score >= 0 ? "+" : "",
12306                     pvInfoList[idx].score / 100.0 );
12307             }
12308         }
12309     }
12310 }
12311
12312 /* Save game in PGN style and close the file */
12313 int
12314 SaveGamePGN (FILE *f)
12315 {
12316     int i, offset, linelen, newblock;
12317     time_t tm;
12318 //    char *movetext;
12319     char numtext[32];
12320     int movelen, numlen, blank;
12321     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12322
12323     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12324
12325     tm = time((time_t *) NULL);
12326
12327     PrintPGNTags(f, &gameInfo);
12328
12329     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12330
12331     if (backwardMostMove > 0 || startedFromSetupPosition) {
12332         char *fen = PositionToFEN(backwardMostMove, NULL);
12333         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12334         fprintf(f, "\n{--------------\n");
12335         PrintPosition(f, backwardMostMove);
12336         fprintf(f, "--------------}\n");
12337         free(fen);
12338     }
12339     else {
12340         /* [AS] Out of book annotation */
12341         if( appData.saveOutOfBookInfo ) {
12342             char buf[64];
12343
12344             GetOutOfBookInfo( buf );
12345
12346             if( buf[0] != '\0' ) {
12347                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12348             }
12349         }
12350
12351         fprintf(f, "\n");
12352     }
12353
12354     i = backwardMostMove;
12355     linelen = 0;
12356     newblock = TRUE;
12357
12358     while (i < forwardMostMove) {
12359         /* Print comments preceding this move */
12360         if (commentList[i] != NULL) {
12361             if (linelen > 0) fprintf(f, "\n");
12362             fprintf(f, "%s", commentList[i]);
12363             linelen = 0;
12364             newblock = TRUE;
12365         }
12366
12367         /* Format move number */
12368         if ((i % 2) == 0)
12369           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12370         else
12371           if (newblock)
12372             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12373           else
12374             numtext[0] = NULLCHAR;
12375
12376         numlen = strlen(numtext);
12377         newblock = FALSE;
12378
12379         /* Print move number */
12380         blank = linelen > 0 && numlen > 0;
12381         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12382             fprintf(f, "\n");
12383             linelen = 0;
12384             blank = 0;
12385         }
12386         if (blank) {
12387             fprintf(f, " ");
12388             linelen++;
12389         }
12390         fprintf(f, "%s", numtext);
12391         linelen += numlen;
12392
12393         /* Get move */
12394         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12395         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12396
12397         /* Print move */
12398         blank = linelen > 0 && movelen > 0;
12399         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12400             fprintf(f, "\n");
12401             linelen = 0;
12402             blank = 0;
12403         }
12404         if (blank) {
12405             fprintf(f, " ");
12406             linelen++;
12407         }
12408         fprintf(f, "%s", move_buffer);
12409         linelen += movelen;
12410
12411         /* [AS] Add PV info if present */
12412         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12413             /* [HGM] add time */
12414             char buf[MSG_SIZ]; int seconds;
12415
12416             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12417
12418             if( seconds <= 0)
12419               buf[0] = 0;
12420             else
12421               if( seconds < 30 )
12422                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12423               else
12424                 {
12425                   seconds = (seconds + 4)/10; // round to full seconds
12426                   if( seconds < 60 )
12427                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12428                   else
12429                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12430                 }
12431
12432             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12433                       pvInfoList[i].score >= 0 ? "+" : "",
12434                       pvInfoList[i].score / 100.0,
12435                       pvInfoList[i].depth,
12436                       buf );
12437
12438             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12439
12440             /* Print score/depth */
12441             blank = linelen > 0 && movelen > 0;
12442             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12443                 fprintf(f, "\n");
12444                 linelen = 0;
12445                 blank = 0;
12446             }
12447             if (blank) {
12448                 fprintf(f, " ");
12449                 linelen++;
12450             }
12451             fprintf(f, "%s", move_buffer);
12452             linelen += movelen;
12453         }
12454
12455         i++;
12456     }
12457
12458     /* Start a new line */
12459     if (linelen > 0) fprintf(f, "\n");
12460
12461     /* Print comments after last move */
12462     if (commentList[i] != NULL) {
12463         fprintf(f, "%s\n", commentList[i]);
12464     }
12465
12466     /* Print result */
12467     if (gameInfo.resultDetails != NULL &&
12468         gameInfo.resultDetails[0] != NULLCHAR) {
12469         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12470                 PGNResult(gameInfo.result));
12471     } else {
12472         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12473     }
12474
12475     fclose(f);
12476     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12477     return TRUE;
12478 }
12479
12480 /* Save game in old style and close the file */
12481 int
12482 SaveGameOldStyle (FILE *f)
12483 {
12484     int i, offset;
12485     time_t tm;
12486
12487     tm = time((time_t *) NULL);
12488
12489     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12490     PrintOpponents(f);
12491
12492     if (backwardMostMove > 0 || startedFromSetupPosition) {
12493         fprintf(f, "\n[--------------\n");
12494         PrintPosition(f, backwardMostMove);
12495         fprintf(f, "--------------]\n");
12496     } else {
12497         fprintf(f, "\n");
12498     }
12499
12500     i = backwardMostMove;
12501     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12502
12503     while (i < forwardMostMove) {
12504         if (commentList[i] != NULL) {
12505             fprintf(f, "[%s]\n", commentList[i]);
12506         }
12507
12508         if ((i % 2) == 1) {
12509             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12510             i++;
12511         } else {
12512             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12513             i++;
12514             if (commentList[i] != NULL) {
12515                 fprintf(f, "\n");
12516                 continue;
12517             }
12518             if (i >= forwardMostMove) {
12519                 fprintf(f, "\n");
12520                 break;
12521             }
12522             fprintf(f, "%s\n", parseList[i]);
12523             i++;
12524         }
12525     }
12526
12527     if (commentList[i] != NULL) {
12528         fprintf(f, "[%s]\n", commentList[i]);
12529     }
12530
12531     /* This isn't really the old style, but it's close enough */
12532     if (gameInfo.resultDetails != NULL &&
12533         gameInfo.resultDetails[0] != NULLCHAR) {
12534         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12535                 gameInfo.resultDetails);
12536     } else {
12537         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12538     }
12539
12540     fclose(f);
12541     return TRUE;
12542 }
12543
12544 /* Save the current game to open file f and close the file */
12545 int
12546 SaveGame (FILE *f, int dummy, char *dummy2)
12547 {
12548     if (gameMode == EditPosition) EditPositionDone(TRUE);
12549     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12550     if (appData.oldSaveStyle)
12551       return SaveGameOldStyle(f);
12552     else
12553       return SaveGamePGN(f);
12554 }
12555
12556 /* Save the current position to the given file */
12557 int
12558 SavePositionToFile (char *filename)
12559 {
12560     FILE *f;
12561     char buf[MSG_SIZ];
12562
12563     if (strcmp(filename, "-") == 0) {
12564         return SavePosition(stdout, 0, NULL);
12565     } else {
12566         f = fopen(filename, "a");
12567         if (f == NULL) {
12568             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12569             DisplayError(buf, errno);
12570             return FALSE;
12571         } else {
12572             safeStrCpy(buf, lastMsg, MSG_SIZ);
12573             DisplayMessage(_("Waiting for access to save file"), "");
12574             flock(fileno(f), LOCK_EX); // [HGM] lock
12575             DisplayMessage(_("Saving position"), "");
12576             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12577             SavePosition(f, 0, NULL);
12578             DisplayMessage(buf, "");
12579             return TRUE;
12580         }
12581     }
12582 }
12583
12584 /* Save the current position to the given open file and close the file */
12585 int
12586 SavePosition (FILE *f, int dummy, char *dummy2)
12587 {
12588     time_t tm;
12589     char *fen;
12590
12591     if (gameMode == EditPosition) EditPositionDone(TRUE);
12592     if (appData.oldSaveStyle) {
12593         tm = time((time_t *) NULL);
12594
12595         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12596         PrintOpponents(f);
12597         fprintf(f, "[--------------\n");
12598         PrintPosition(f, currentMove);
12599         fprintf(f, "--------------]\n");
12600     } else {
12601         fen = PositionToFEN(currentMove, NULL);
12602         fprintf(f, "%s\n", fen);
12603         free(fen);
12604     }
12605     fclose(f);
12606     return TRUE;
12607 }
12608
12609 void
12610 ReloadCmailMsgEvent (int unregister)
12611 {
12612 #if !WIN32
12613     static char *inFilename = NULL;
12614     static char *outFilename;
12615     int i;
12616     struct stat inbuf, outbuf;
12617     int status;
12618
12619     /* Any registered moves are unregistered if unregister is set, */
12620     /* i.e. invoked by the signal handler */
12621     if (unregister) {
12622         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12623             cmailMoveRegistered[i] = FALSE;
12624             if (cmailCommentList[i] != NULL) {
12625                 free(cmailCommentList[i]);
12626                 cmailCommentList[i] = NULL;
12627             }
12628         }
12629         nCmailMovesRegistered = 0;
12630     }
12631
12632     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12633         cmailResult[i] = CMAIL_NOT_RESULT;
12634     }
12635     nCmailResults = 0;
12636
12637     if (inFilename == NULL) {
12638         /* Because the filenames are static they only get malloced once  */
12639         /* and they never get freed                                      */
12640         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12641         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12642
12643         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12644         sprintf(outFilename, "%s.out", appData.cmailGameName);
12645     }
12646
12647     status = stat(outFilename, &outbuf);
12648     if (status < 0) {
12649         cmailMailedMove = FALSE;
12650     } else {
12651         status = stat(inFilename, &inbuf);
12652         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12653     }
12654
12655     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12656        counts the games, notes how each one terminated, etc.
12657
12658        It would be nice to remove this kludge and instead gather all
12659        the information while building the game list.  (And to keep it
12660        in the game list nodes instead of having a bunch of fixed-size
12661        parallel arrays.)  Note this will require getting each game's
12662        termination from the PGN tags, as the game list builder does
12663        not process the game moves.  --mann
12664        */
12665     cmailMsgLoaded = TRUE;
12666     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12667
12668     /* Load first game in the file or popup game menu */
12669     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12670
12671 #endif /* !WIN32 */
12672     return;
12673 }
12674
12675 int
12676 RegisterMove ()
12677 {
12678     FILE *f;
12679     char string[MSG_SIZ];
12680
12681     if (   cmailMailedMove
12682         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12683         return TRUE;            /* Allow free viewing  */
12684     }
12685
12686     /* Unregister move to ensure that we don't leave RegisterMove        */
12687     /* with the move registered when the conditions for registering no   */
12688     /* longer hold                                                       */
12689     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12691         nCmailMovesRegistered --;
12692
12693         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12694           {
12695               free(cmailCommentList[lastLoadGameNumber - 1]);
12696               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12697           }
12698     }
12699
12700     if (cmailOldMove == -1) {
12701         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12702         return FALSE;
12703     }
12704
12705     if (currentMove > cmailOldMove + 1) {
12706         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12707         return FALSE;
12708     }
12709
12710     if (currentMove < cmailOldMove) {
12711         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12712         return FALSE;
12713     }
12714
12715     if (forwardMostMove > currentMove) {
12716         /* Silently truncate extra moves */
12717         TruncateGame();
12718     }
12719
12720     if (   (currentMove == cmailOldMove + 1)
12721         || (   (currentMove == cmailOldMove)
12722             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12723                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12724         if (gameInfo.result != GameUnfinished) {
12725             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12726         }
12727
12728         if (commentList[currentMove] != NULL) {
12729             cmailCommentList[lastLoadGameNumber - 1]
12730               = StrSave(commentList[currentMove]);
12731         }
12732         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12733
12734         if (appData.debugMode)
12735           fprintf(debugFP, "Saving %s for game %d\n",
12736                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12737
12738         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12739
12740         f = fopen(string, "w");
12741         if (appData.oldSaveStyle) {
12742             SaveGameOldStyle(f); /* also closes the file */
12743
12744             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12745             f = fopen(string, "w");
12746             SavePosition(f, 0, NULL); /* also closes the file */
12747         } else {
12748             fprintf(f, "{--------------\n");
12749             PrintPosition(f, currentMove);
12750             fprintf(f, "--------------}\n\n");
12751
12752             SaveGame(f, 0, NULL); /* also closes the file*/
12753         }
12754
12755         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12756         nCmailMovesRegistered ++;
12757     } else if (nCmailGames == 1) {
12758         DisplayError(_("You have not made a move yet"), 0);
12759         return FALSE;
12760     }
12761
12762     return TRUE;
12763 }
12764
12765 void
12766 MailMoveEvent ()
12767 {
12768 #if !WIN32
12769     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12770     FILE *commandOutput;
12771     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12772     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12773     int nBuffers;
12774     int i;
12775     int archived;
12776     char *arcDir;
12777
12778     if (! cmailMsgLoaded) {
12779         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12780         return;
12781     }
12782
12783     if (nCmailGames == nCmailResults) {
12784         DisplayError(_("No unfinished games"), 0);
12785         return;
12786     }
12787
12788 #if CMAIL_PROHIBIT_REMAIL
12789     if (cmailMailedMove) {
12790       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);
12791         DisplayError(msg, 0);
12792         return;
12793     }
12794 #endif
12795
12796     if (! (cmailMailedMove || RegisterMove())) return;
12797
12798     if (   cmailMailedMove
12799         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12800       snprintf(string, MSG_SIZ, partCommandString,
12801                appData.debugMode ? " -v" : "", appData.cmailGameName);
12802         commandOutput = popen(string, "r");
12803
12804         if (commandOutput == NULL) {
12805             DisplayError(_("Failed to invoke cmail"), 0);
12806         } else {
12807             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12808                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12809             }
12810             if (nBuffers > 1) {
12811                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12812                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12813                 nBytes = MSG_SIZ - 1;
12814             } else {
12815                 (void) memcpy(msg, buffer, nBytes);
12816             }
12817             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12818
12819             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12820                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12821
12822                 archived = TRUE;
12823                 for (i = 0; i < nCmailGames; i ++) {
12824                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12825                         archived = FALSE;
12826                     }
12827                 }
12828                 if (   archived
12829                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12830                         != NULL)) {
12831                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12832                            arcDir,
12833                            appData.cmailGameName,
12834                            gameInfo.date);
12835                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12836                     cmailMsgLoaded = FALSE;
12837                 }
12838             }
12839
12840             DisplayInformation(msg);
12841             pclose(commandOutput);
12842         }
12843     } else {
12844         if ((*cmailMsg) != '\0') {
12845             DisplayInformation(cmailMsg);
12846         }
12847     }
12848
12849     return;
12850 #endif /* !WIN32 */
12851 }
12852
12853 char *
12854 CmailMsg ()
12855 {
12856 #if WIN32
12857     return NULL;
12858 #else
12859     int  prependComma = 0;
12860     char number[5];
12861     char string[MSG_SIZ];       /* Space for game-list */
12862     int  i;
12863
12864     if (!cmailMsgLoaded) return "";
12865
12866     if (cmailMailedMove) {
12867       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12868     } else {
12869         /* Create a list of games left */
12870       snprintf(string, MSG_SIZ, "[");
12871         for (i = 0; i < nCmailGames; i ++) {
12872             if (! (   cmailMoveRegistered[i]
12873                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12874                 if (prependComma) {
12875                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12876                 } else {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12878                     prependComma = 1;
12879                 }
12880
12881                 strcat(string, number);
12882             }
12883         }
12884         strcat(string, "]");
12885
12886         if (nCmailMovesRegistered + nCmailResults == 0) {
12887             switch (nCmailGames) {
12888               case 1:
12889                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12890                 break;
12891
12892               case 2:
12893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12894                 break;
12895
12896               default:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12898                          nCmailGames);
12899                 break;
12900             }
12901         } else {
12902             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12903               case 1:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12905                          string);
12906                 break;
12907
12908               case 0:
12909                 if (nCmailResults == nCmailGames) {
12910                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12911                 } else {
12912                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12913                 }
12914                 break;
12915
12916               default:
12917                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12918                          string);
12919             }
12920         }
12921     }
12922     return cmailMsg;
12923 #endif /* WIN32 */
12924 }
12925
12926 void
12927 ResetGameEvent ()
12928 {
12929     if (gameMode == Training)
12930       SetTrainingModeOff();
12931
12932     Reset(TRUE, TRUE);
12933     cmailMsgLoaded = FALSE;
12934     if (appData.icsActive) {
12935       SendToICS(ics_prefix);
12936       SendToICS("refresh\n");
12937     }
12938 }
12939
12940 void
12941 ExitEvent (int status)
12942 {
12943     exiting++;
12944     if (exiting > 2) {
12945       /* Give up on clean exit */
12946       exit(status);
12947     }
12948     if (exiting > 1) {
12949       /* Keep trying for clean exit */
12950       return;
12951     }
12952
12953     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12954
12955     if (telnetISR != NULL) {
12956       RemoveInputSource(telnetISR);
12957     }
12958     if (icsPR != NoProc) {
12959       DestroyChildProcess(icsPR, TRUE);
12960     }
12961
12962     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12963     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12964
12965     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12966     /* make sure this other one finishes before killing it!                  */
12967     if(endingGame) { int count = 0;
12968         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12969         while(endingGame && count++ < 10) DoSleep(1);
12970         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12971     }
12972
12973     /* Kill off chess programs */
12974     if (first.pr != NoProc) {
12975         ExitAnalyzeMode();
12976
12977         DoSleep( appData.delayBeforeQuit );
12978         SendToProgram("quit\n", &first);
12979         DoSleep( appData.delayAfterQuit );
12980         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12981     }
12982     if (second.pr != NoProc) {
12983         DoSleep( appData.delayBeforeQuit );
12984         SendToProgram("quit\n", &second);
12985         DoSleep( appData.delayAfterQuit );
12986         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12987     }
12988     if (first.isr != NULL) {
12989         RemoveInputSource(first.isr);
12990     }
12991     if (second.isr != NULL) {
12992         RemoveInputSource(second.isr);
12993     }
12994
12995     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12996     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12997
12998     ShutDownFrontEnd();
12999     exit(status);
13000 }
13001
13002 void
13003 PauseEvent ()
13004 {
13005     if (appData.debugMode)
13006         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13007     if (pausing) {
13008         pausing = FALSE;
13009         ModeHighlight();
13010         if (gameMode == MachinePlaysWhite ||
13011             gameMode == MachinePlaysBlack) {
13012             StartClocks();
13013         } else {
13014             DisplayBothClocks();
13015         }
13016         if (gameMode == PlayFromGameFile) {
13017             if (appData.timeDelay >= 0)
13018                 AutoPlayGameLoop();
13019         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13020             Reset(FALSE, TRUE);
13021             SendToICS(ics_prefix);
13022             SendToICS("refresh\n");
13023         } else if (currentMove < forwardMostMove) {
13024             ForwardInner(forwardMostMove);
13025         }
13026         pauseExamInvalid = FALSE;
13027     } else {
13028         switch (gameMode) {
13029           default:
13030             return;
13031           case IcsExamining:
13032             pauseExamForwardMostMove = forwardMostMove;
13033             pauseExamInvalid = FALSE;
13034             /* fall through */
13035           case IcsObserving:
13036           case IcsPlayingWhite:
13037           case IcsPlayingBlack:
13038             pausing = TRUE;
13039             ModeHighlight();
13040             return;
13041           case PlayFromGameFile:
13042             (void) StopLoadGameTimer();
13043             pausing = TRUE;
13044             ModeHighlight();
13045             break;
13046           case BeginningOfGame:
13047             if (appData.icsActive) return;
13048             /* else fall through */
13049           case MachinePlaysWhite:
13050           case MachinePlaysBlack:
13051           case TwoMachinesPlay:
13052             if (forwardMostMove == 0)
13053               return;           /* don't pause if no one has moved */
13054             if ((gameMode == MachinePlaysWhite &&
13055                  !WhiteOnMove(forwardMostMove)) ||
13056                 (gameMode == MachinePlaysBlack &&
13057                  WhiteOnMove(forwardMostMove))) {
13058                 StopClocks();
13059             }
13060             pausing = TRUE;
13061             ModeHighlight();
13062             break;
13063         }
13064     }
13065 }
13066
13067 void
13068 EditCommentEvent ()
13069 {
13070     char title[MSG_SIZ];
13071
13072     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13073       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13074     } else {
13075       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13076                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13077                parseList[currentMove - 1]);
13078     }
13079
13080     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13081 }
13082
13083
13084 void
13085 EditTagsEvent ()
13086 {
13087     char *tags = PGNTags(&gameInfo);
13088     bookUp = FALSE;
13089     EditTagsPopUp(tags, NULL);
13090     free(tags);
13091 }
13092
13093 void
13094 AnalyzeModeEvent ()
13095 {
13096     if (appData.noChessProgram || gameMode == AnalyzeMode)
13097       return;
13098
13099     if (gameMode != AnalyzeFile) {
13100         if (!appData.icsEngineAnalyze) {
13101                EditGameEvent();
13102                if (gameMode != EditGame) return;
13103         }
13104         ResurrectChessProgram();
13105         SendToProgram("analyze\n", &first);
13106         first.analyzing = TRUE;
13107         /*first.maybeThinking = TRUE;*/
13108         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13109         EngineOutputPopUp();
13110     }
13111     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13112     pausing = FALSE;
13113     ModeHighlight();
13114     SetGameInfo();
13115
13116     StartAnalysisClock();
13117     GetTimeMark(&lastNodeCountTime);
13118     lastNodeCount = 0;
13119 }
13120
13121 void
13122 AnalyzeFileEvent ()
13123 {
13124     if (appData.noChessProgram || gameMode == AnalyzeFile)
13125       return;
13126
13127     if (gameMode != AnalyzeMode) {
13128         EditGameEvent();
13129         if (gameMode != EditGame) return;
13130         ResurrectChessProgram();
13131         SendToProgram("analyze\n", &first);
13132         first.analyzing = TRUE;
13133         /*first.maybeThinking = TRUE;*/
13134         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13135         EngineOutputPopUp();
13136     }
13137     gameMode = AnalyzeFile;
13138     pausing = FALSE;
13139     ModeHighlight();
13140     SetGameInfo();
13141
13142     StartAnalysisClock();
13143     GetTimeMark(&lastNodeCountTime);
13144     lastNodeCount = 0;
13145     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13146 }
13147
13148 void
13149 MachineWhiteEvent ()
13150 {
13151     char buf[MSG_SIZ];
13152     char *bookHit = NULL;
13153
13154     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13155       return;
13156
13157
13158     if (gameMode == PlayFromGameFile ||
13159         gameMode == TwoMachinesPlay  ||
13160         gameMode == Training         ||
13161         gameMode == AnalyzeMode      ||
13162         gameMode == EndOfGame)
13163         EditGameEvent();
13164
13165     if (gameMode == EditPosition)
13166         EditPositionDone(TRUE);
13167
13168     if (!WhiteOnMove(currentMove)) {
13169         DisplayError(_("It is not White's turn"), 0);
13170         return;
13171     }
13172
13173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13174       ExitAnalyzeMode();
13175
13176     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13177         gameMode == AnalyzeFile)
13178         TruncateGame();
13179
13180     ResurrectChessProgram();    /* in case it isn't running */
13181     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13182         gameMode = MachinePlaysWhite;
13183         ResetClocks();
13184     } else
13185     gameMode = MachinePlaysWhite;
13186     pausing = FALSE;
13187     ModeHighlight();
13188     SetGameInfo();
13189     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13190     DisplayTitle(buf);
13191     if (first.sendName) {
13192       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13193       SendToProgram(buf, &first);
13194     }
13195     if (first.sendTime) {
13196       if (first.useColors) {
13197         SendToProgram("black\n", &first); /*gnu kludge*/
13198       }
13199       SendTimeRemaining(&first, TRUE);
13200     }
13201     if (first.useColors) {
13202       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13203     }
13204     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13205     SetMachineThinkingEnables();
13206     first.maybeThinking = TRUE;
13207     StartClocks();
13208     firstMove = FALSE;
13209
13210     if (appData.autoFlipView && !flipView) {
13211       flipView = !flipView;
13212       DrawPosition(FALSE, NULL);
13213       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13214     }
13215
13216     if(bookHit) { // [HGM] book: simulate book reply
13217         static char bookMove[MSG_SIZ]; // a bit generous?
13218
13219         programStats.nodes = programStats.depth = programStats.time =
13220         programStats.score = programStats.got_only_move = 0;
13221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13222
13223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13224         strcat(bookMove, bookHit);
13225         HandleMachineMove(bookMove, &first);
13226     }
13227 }
13228
13229 void
13230 MachineBlackEvent ()
13231 {
13232   char buf[MSG_SIZ];
13233   char *bookHit = NULL;
13234
13235     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13236         return;
13237
13238
13239     if (gameMode == PlayFromGameFile ||
13240         gameMode == TwoMachinesPlay  ||
13241         gameMode == Training         ||
13242         gameMode == AnalyzeMode      ||
13243         gameMode == EndOfGame)
13244         EditGameEvent();
13245
13246     if (gameMode == EditPosition)
13247         EditPositionDone(TRUE);
13248
13249     if (WhiteOnMove(currentMove)) {
13250         DisplayError(_("It is not Black's turn"), 0);
13251         return;
13252     }
13253
13254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13255       ExitAnalyzeMode();
13256
13257     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13258         gameMode == AnalyzeFile)
13259         TruncateGame();
13260
13261     ResurrectChessProgram();    /* in case it isn't running */
13262     gameMode = MachinePlaysBlack;
13263     pausing = FALSE;
13264     ModeHighlight();
13265     SetGameInfo();
13266     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13267     DisplayTitle(buf);
13268     if (first.sendName) {
13269       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13270       SendToProgram(buf, &first);
13271     }
13272     if (first.sendTime) {
13273       if (first.useColors) {
13274         SendToProgram("white\n", &first); /*gnu kludge*/
13275       }
13276       SendTimeRemaining(&first, FALSE);
13277     }
13278     if (first.useColors) {
13279       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13280     }
13281     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13282     SetMachineThinkingEnables();
13283     first.maybeThinking = TRUE;
13284     StartClocks();
13285
13286     if (appData.autoFlipView && flipView) {
13287       flipView = !flipView;
13288       DrawPosition(FALSE, NULL);
13289       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13290     }
13291     if(bookHit) { // [HGM] book: simulate book reply
13292         static char bookMove[MSG_SIZ]; // a bit generous?
13293
13294         programStats.nodes = programStats.depth = programStats.time =
13295         programStats.score = programStats.got_only_move = 0;
13296         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13297
13298         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13299         strcat(bookMove, bookHit);
13300         HandleMachineMove(bookMove, &first);
13301     }
13302 }
13303
13304
13305 void
13306 DisplayTwoMachinesTitle ()
13307 {
13308     char buf[MSG_SIZ];
13309     if (appData.matchGames > 0) {
13310         if(appData.tourneyFile[0]) {
13311           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13312                    gameInfo.white, _("vs."), gameInfo.black,
13313                    nextGame+1, appData.matchGames+1,
13314                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13315         } else 
13316         if (first.twoMachinesColor[0] == 'w') {
13317           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13318                    gameInfo.white, _("vs."),  gameInfo.black,
13319                    first.matchWins, second.matchWins,
13320                    matchGame - 1 - (first.matchWins + second.matchWins));
13321         } else {
13322           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13323                    gameInfo.white, _("vs."), gameInfo.black,
13324                    second.matchWins, first.matchWins,
13325                    matchGame - 1 - (first.matchWins + second.matchWins));
13326         }
13327     } else {
13328       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13329     }
13330     DisplayTitle(buf);
13331 }
13332
13333 void
13334 SettingsMenuIfReady ()
13335 {
13336   if (second.lastPing != second.lastPong) {
13337     DisplayMessage("", _("Waiting for second chess program"));
13338     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13339     return;
13340   }
13341   ThawUI();
13342   DisplayMessage("", "");
13343   SettingsPopUp(&second);
13344 }
13345
13346 int
13347 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13348 {
13349     char buf[MSG_SIZ];
13350     if (cps->pr == NoProc) {
13351         StartChessProgram(cps);
13352         if (cps->protocolVersion == 1) {
13353           retry();
13354         } else {
13355           /* kludge: allow timeout for initial "feature" command */
13356           FreezeUI();
13357           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13358           DisplayMessage("", buf);
13359           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13360         }
13361         return 1;
13362     }
13363     return 0;
13364 }
13365
13366 void
13367 TwoMachinesEvent P((void))
13368 {
13369     int i;
13370     char buf[MSG_SIZ];
13371     ChessProgramState *onmove;
13372     char *bookHit = NULL;
13373     static int stalling = 0;
13374     TimeMark now;
13375     long wait;
13376
13377     if (appData.noChessProgram) return;
13378
13379     switch (gameMode) {
13380       case TwoMachinesPlay:
13381         return;
13382       case MachinePlaysWhite:
13383       case MachinePlaysBlack:
13384         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13385             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13386             return;
13387         }
13388         /* fall through */
13389       case BeginningOfGame:
13390       case PlayFromGameFile:
13391       case EndOfGame:
13392         EditGameEvent();
13393         if (gameMode != EditGame) return;
13394         break;
13395       case EditPosition:
13396         EditPositionDone(TRUE);
13397         break;
13398       case AnalyzeMode:
13399       case AnalyzeFile:
13400         ExitAnalyzeMode();
13401         break;
13402       case EditGame:
13403       default:
13404         break;
13405     }
13406
13407 //    forwardMostMove = currentMove;
13408     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13409
13410     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13411
13412     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13413     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13414       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13415       return;
13416     }
13417     if(!stalling) {
13418       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13419       SendToProgram("force\n", &second);
13420       stalling = 1;
13421       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13422       return;
13423     }
13424     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13425     if(appData.matchPause>10000 || appData.matchPause<10)
13426                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13427     wait = SubtractTimeMarks(&now, &pauseStart);
13428     if(wait < appData.matchPause) {
13429         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13430         return;
13431     }
13432     // we are now committed to starting the game
13433     stalling = 0;
13434     DisplayMessage("", "");
13435     if (startedFromSetupPosition) {
13436         SendBoard(&second, backwardMostMove);
13437     if (appData.debugMode) {
13438         fprintf(debugFP, "Two Machines\n");
13439     }
13440     }
13441     for (i = backwardMostMove; i < forwardMostMove; i++) {
13442         SendMoveToProgram(i, &second);
13443     }
13444
13445     gameMode = TwoMachinesPlay;
13446     pausing = FALSE;
13447     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13448     SetGameInfo();
13449     DisplayTwoMachinesTitle();
13450     firstMove = TRUE;
13451     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13452         onmove = &first;
13453     } else {
13454         onmove = &second;
13455     }
13456     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13457     SendToProgram(first.computerString, &first);
13458     if (first.sendName) {
13459       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13460       SendToProgram(buf, &first);
13461     }
13462     SendToProgram(second.computerString, &second);
13463     if (second.sendName) {
13464       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13465       SendToProgram(buf, &second);
13466     }
13467
13468     ResetClocks();
13469     if (!first.sendTime || !second.sendTime) {
13470         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13471         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13472     }
13473     if (onmove->sendTime) {
13474       if (onmove->useColors) {
13475         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13476       }
13477       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13478     }
13479     if (onmove->useColors) {
13480       SendToProgram(onmove->twoMachinesColor, onmove);
13481     }
13482     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13483 //    SendToProgram("go\n", onmove);
13484     onmove->maybeThinking = TRUE;
13485     SetMachineThinkingEnables();
13486
13487     StartClocks();
13488
13489     if(bookHit) { // [HGM] book: simulate book reply
13490         static char bookMove[MSG_SIZ]; // a bit generous?
13491
13492         programStats.nodes = programStats.depth = programStats.time =
13493         programStats.score = programStats.got_only_move = 0;
13494         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13495
13496         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13497         strcat(bookMove, bookHit);
13498         savedMessage = bookMove; // args for deferred call
13499         savedState = onmove;
13500         ScheduleDelayedEvent(DeferredBookMove, 1);
13501     }
13502 }
13503
13504 void
13505 TrainingEvent ()
13506 {
13507     if (gameMode == Training) {
13508       SetTrainingModeOff();
13509       gameMode = PlayFromGameFile;
13510       DisplayMessage("", _("Training mode off"));
13511     } else {
13512       gameMode = Training;
13513       animateTraining = appData.animate;
13514
13515       /* make sure we are not already at the end of the game */
13516       if (currentMove < forwardMostMove) {
13517         SetTrainingModeOn();
13518         DisplayMessage("", _("Training mode on"));
13519       } else {
13520         gameMode = PlayFromGameFile;
13521         DisplayError(_("Already at end of game"), 0);
13522       }
13523     }
13524     ModeHighlight();
13525 }
13526
13527 void
13528 IcsClientEvent ()
13529 {
13530     if (!appData.icsActive) return;
13531     switch (gameMode) {
13532       case IcsPlayingWhite:
13533       case IcsPlayingBlack:
13534       case IcsObserving:
13535       case IcsIdle:
13536       case BeginningOfGame:
13537       case IcsExamining:
13538         return;
13539
13540       case EditGame:
13541         break;
13542
13543       case EditPosition:
13544         EditPositionDone(TRUE);
13545         break;
13546
13547       case AnalyzeMode:
13548       case AnalyzeFile:
13549         ExitAnalyzeMode();
13550         break;
13551
13552       default:
13553         EditGameEvent();
13554         break;
13555     }
13556
13557     gameMode = IcsIdle;
13558     ModeHighlight();
13559     return;
13560 }
13561
13562 void
13563 EditGameEvent ()
13564 {
13565     int i;
13566
13567     switch (gameMode) {
13568       case Training:
13569         SetTrainingModeOff();
13570         break;
13571       case MachinePlaysWhite:
13572       case MachinePlaysBlack:
13573       case BeginningOfGame:
13574         SendToProgram("force\n", &first);
13575         SetUserThinkingEnables();
13576         break;
13577       case PlayFromGameFile:
13578         (void) StopLoadGameTimer();
13579         if (gameFileFP != NULL) {
13580             gameFileFP = NULL;
13581         }
13582         break;
13583       case EditPosition:
13584         EditPositionDone(TRUE);
13585         break;
13586       case AnalyzeMode:
13587       case AnalyzeFile:
13588         ExitAnalyzeMode();
13589         SendToProgram("force\n", &first);
13590         break;
13591       case TwoMachinesPlay:
13592         GameEnds(EndOfFile, NULL, GE_PLAYER);
13593         ResurrectChessProgram();
13594         SetUserThinkingEnables();
13595         break;
13596       case EndOfGame:
13597         ResurrectChessProgram();
13598         break;
13599       case IcsPlayingBlack:
13600       case IcsPlayingWhite:
13601         DisplayError(_("Warning: You are still playing a game"), 0);
13602         break;
13603       case IcsObserving:
13604         DisplayError(_("Warning: You are still observing a game"), 0);
13605         break;
13606       case IcsExamining:
13607         DisplayError(_("Warning: You are still examining a game"), 0);
13608         break;
13609       case IcsIdle:
13610         break;
13611       case EditGame:
13612       default:
13613         return;
13614     }
13615
13616     pausing = FALSE;
13617     StopClocks();
13618     first.offeredDraw = second.offeredDraw = 0;
13619
13620     if (gameMode == PlayFromGameFile) {
13621         whiteTimeRemaining = timeRemaining[0][currentMove];
13622         blackTimeRemaining = timeRemaining[1][currentMove];
13623         DisplayTitle("");
13624     }
13625
13626     if (gameMode == MachinePlaysWhite ||
13627         gameMode == MachinePlaysBlack ||
13628         gameMode == TwoMachinesPlay ||
13629         gameMode == EndOfGame) {
13630         i = forwardMostMove;
13631         while (i > currentMove) {
13632             SendToProgram("undo\n", &first);
13633             i--;
13634         }
13635         if(!adjustedClock) {
13636         whiteTimeRemaining = timeRemaining[0][currentMove];
13637         blackTimeRemaining = timeRemaining[1][currentMove];
13638         DisplayBothClocks();
13639         }
13640         if (whiteFlag || blackFlag) {
13641             whiteFlag = blackFlag = 0;
13642         }
13643         DisplayTitle("");
13644     }
13645
13646     gameMode = EditGame;
13647     ModeHighlight();
13648     SetGameInfo();
13649 }
13650
13651
13652 void
13653 EditPositionEvent ()
13654 {
13655     if (gameMode == EditPosition) {
13656         EditGameEvent();
13657         return;
13658     }
13659
13660     EditGameEvent();
13661     if (gameMode != EditGame) return;
13662
13663     gameMode = EditPosition;
13664     ModeHighlight();
13665     SetGameInfo();
13666     if (currentMove > 0)
13667       CopyBoard(boards[0], boards[currentMove]);
13668
13669     blackPlaysFirst = !WhiteOnMove(currentMove);
13670     ResetClocks();
13671     currentMove = forwardMostMove = backwardMostMove = 0;
13672     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13673     DisplayMove(-1);
13674 }
13675
13676 void
13677 ExitAnalyzeMode ()
13678 {
13679     /* [DM] icsEngineAnalyze - possible call from other functions */
13680     if (appData.icsEngineAnalyze) {
13681         appData.icsEngineAnalyze = FALSE;
13682
13683         DisplayMessage("",_("Close ICS engine analyze..."));
13684     }
13685     if (first.analysisSupport && first.analyzing) {
13686       SendToProgram("exit\n", &first);
13687       first.analyzing = FALSE;
13688     }
13689     thinkOutput[0] = NULLCHAR;
13690 }
13691
13692 void
13693 EditPositionDone (Boolean fakeRights)
13694 {
13695     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13696
13697     startedFromSetupPosition = TRUE;
13698     InitChessProgram(&first, FALSE);
13699     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13700       boards[0][EP_STATUS] = EP_NONE;
13701       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13702     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13703         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13704         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13705       } else boards[0][CASTLING][2] = NoRights;
13706     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13707         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13708         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13709       } else boards[0][CASTLING][5] = NoRights;
13710     }
13711     SendToProgram("force\n", &first);
13712     if (blackPlaysFirst) {
13713         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13714         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13715         currentMove = forwardMostMove = backwardMostMove = 1;
13716         CopyBoard(boards[1], boards[0]);
13717     } else {
13718         currentMove = forwardMostMove = backwardMostMove = 0;
13719     }
13720     SendBoard(&first, forwardMostMove);
13721     if (appData.debugMode) {
13722         fprintf(debugFP, "EditPosDone\n");
13723     }
13724     DisplayTitle("");
13725     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13726     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13727     gameMode = EditGame;
13728     ModeHighlight();
13729     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13730     ClearHighlights(); /* [AS] */
13731 }
13732
13733 /* Pause for `ms' milliseconds */
13734 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13735 void
13736 TimeDelay (long ms)
13737 {
13738     TimeMark m1, m2;
13739
13740     GetTimeMark(&m1);
13741     do {
13742         GetTimeMark(&m2);
13743     } while (SubtractTimeMarks(&m2, &m1) < ms);
13744 }
13745
13746 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13747 void
13748 SendMultiLineToICS (char *buf)
13749 {
13750     char temp[MSG_SIZ+1], *p;
13751     int len;
13752
13753     len = strlen(buf);
13754     if (len > MSG_SIZ)
13755       len = MSG_SIZ;
13756
13757     strncpy(temp, buf, len);
13758     temp[len] = 0;
13759
13760     p = temp;
13761     while (*p) {
13762         if (*p == '\n' || *p == '\r')
13763           *p = ' ';
13764         ++p;
13765     }
13766
13767     strcat(temp, "\n");
13768     SendToICS(temp);
13769     SendToPlayer(temp, strlen(temp));
13770 }
13771
13772 void
13773 SetWhiteToPlayEvent ()
13774 {
13775     if (gameMode == EditPosition) {
13776         blackPlaysFirst = FALSE;
13777         DisplayBothClocks();    /* works because currentMove is 0 */
13778     } else if (gameMode == IcsExamining) {
13779         SendToICS(ics_prefix);
13780         SendToICS("tomove white\n");
13781     }
13782 }
13783
13784 void
13785 SetBlackToPlayEvent ()
13786 {
13787     if (gameMode == EditPosition) {
13788         blackPlaysFirst = TRUE;
13789         currentMove = 1;        /* kludge */
13790         DisplayBothClocks();
13791         currentMove = 0;
13792     } else if (gameMode == IcsExamining) {
13793         SendToICS(ics_prefix);
13794         SendToICS("tomove black\n");
13795     }
13796 }
13797
13798 void
13799 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13800 {
13801     char buf[MSG_SIZ];
13802     ChessSquare piece = boards[0][y][x];
13803
13804     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13805
13806     switch (selection) {
13807       case ClearBoard:
13808         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13809             SendToICS(ics_prefix);
13810             SendToICS("bsetup clear\n");
13811         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13812             SendToICS(ics_prefix);
13813             SendToICS("clearboard\n");
13814         } else {
13815             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13816                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13817                 for (y = 0; y < BOARD_HEIGHT; y++) {
13818                     if (gameMode == IcsExamining) {
13819                         if (boards[currentMove][y][x] != EmptySquare) {
13820                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13821                                     AAA + x, ONE + y);
13822                             SendToICS(buf);
13823                         }
13824                     } else {
13825                         boards[0][y][x] = p;
13826                     }
13827                 }
13828             }
13829         }
13830         if (gameMode == EditPosition) {
13831             DrawPosition(FALSE, boards[0]);
13832         }
13833         break;
13834
13835       case WhitePlay:
13836         SetWhiteToPlayEvent();
13837         break;
13838
13839       case BlackPlay:
13840         SetBlackToPlayEvent();
13841         break;
13842
13843       case EmptySquare:
13844         if (gameMode == IcsExamining) {
13845             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13846             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13847             SendToICS(buf);
13848         } else {
13849             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13850                 if(x == BOARD_LEFT-2) {
13851                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13852                     boards[0][y][1] = 0;
13853                 } else
13854                 if(x == BOARD_RGHT+1) {
13855                     if(y >= gameInfo.holdingsSize) break;
13856                     boards[0][y][BOARD_WIDTH-2] = 0;
13857                 } else break;
13858             }
13859             boards[0][y][x] = EmptySquare;
13860             DrawPosition(FALSE, boards[0]);
13861         }
13862         break;
13863
13864       case PromotePiece:
13865         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13866            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13867             selection = (ChessSquare) (PROMOTED piece);
13868         } else if(piece == EmptySquare) selection = WhiteSilver;
13869         else selection = (ChessSquare)((int)piece - 1);
13870         goto defaultlabel;
13871
13872       case DemotePiece:
13873         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13874            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13875             selection = (ChessSquare) (DEMOTED piece);
13876         } else if(piece == EmptySquare) selection = BlackSilver;
13877         else selection = (ChessSquare)((int)piece + 1);
13878         goto defaultlabel;
13879
13880       case WhiteQueen:
13881       case BlackQueen:
13882         if(gameInfo.variant == VariantShatranj ||
13883            gameInfo.variant == VariantXiangqi  ||
13884            gameInfo.variant == VariantCourier  ||
13885            gameInfo.variant == VariantMakruk     )
13886             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13887         goto defaultlabel;
13888
13889       case WhiteKing:
13890       case BlackKing:
13891         if(gameInfo.variant == VariantXiangqi)
13892             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13893         if(gameInfo.variant == VariantKnightmate)
13894             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13895       default:
13896         defaultlabel:
13897         if (gameMode == IcsExamining) {
13898             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13899             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13900                      PieceToChar(selection), AAA + x, ONE + y);
13901             SendToICS(buf);
13902         } else {
13903             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13904                 int n;
13905                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13906                     n = PieceToNumber(selection - BlackPawn);
13907                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13908                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13909                     boards[0][BOARD_HEIGHT-1-n][1]++;
13910                 } else
13911                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13912                     n = PieceToNumber(selection);
13913                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13914                     boards[0][n][BOARD_WIDTH-1] = selection;
13915                     boards[0][n][BOARD_WIDTH-2]++;
13916                 }
13917             } else
13918             boards[0][y][x] = selection;
13919             DrawPosition(TRUE, boards[0]);
13920             ClearHighlights();
13921             fromX = fromY = -1;
13922         }
13923         break;
13924     }
13925 }
13926
13927
13928 void
13929 DropMenuEvent (ChessSquare selection, int x, int y)
13930 {
13931     ChessMove moveType;
13932
13933     switch (gameMode) {
13934       case IcsPlayingWhite:
13935       case MachinePlaysBlack:
13936         if (!WhiteOnMove(currentMove)) {
13937             DisplayMoveError(_("It is Black's turn"));
13938             return;
13939         }
13940         moveType = WhiteDrop;
13941         break;
13942       case IcsPlayingBlack:
13943       case MachinePlaysWhite:
13944         if (WhiteOnMove(currentMove)) {
13945             DisplayMoveError(_("It is White's turn"));
13946             return;
13947         }
13948         moveType = BlackDrop;
13949         break;
13950       case EditGame:
13951         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13952         break;
13953       default:
13954         return;
13955     }
13956
13957     if (moveType == BlackDrop && selection < BlackPawn) {
13958       selection = (ChessSquare) ((int) selection
13959                                  + (int) BlackPawn - (int) WhitePawn);
13960     }
13961     if (boards[currentMove][y][x] != EmptySquare) {
13962         DisplayMoveError(_("That square is occupied"));
13963         return;
13964     }
13965
13966     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13967 }
13968
13969 void
13970 AcceptEvent ()
13971 {
13972     /* Accept a pending offer of any kind from opponent */
13973
13974     if (appData.icsActive) {
13975         SendToICS(ics_prefix);
13976         SendToICS("accept\n");
13977     } else if (cmailMsgLoaded) {
13978         if (currentMove == cmailOldMove &&
13979             commentList[cmailOldMove] != NULL &&
13980             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13981                    "Black offers a draw" : "White offers a draw")) {
13982             TruncateGame();
13983             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13984             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13985         } else {
13986             DisplayError(_("There is no pending offer on this move"), 0);
13987             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13988         }
13989     } else {
13990         /* Not used for offers from chess program */
13991     }
13992 }
13993
13994 void
13995 DeclineEvent ()
13996 {
13997     /* Decline a pending offer of any kind from opponent */
13998
13999     if (appData.icsActive) {
14000         SendToICS(ics_prefix);
14001         SendToICS("decline\n");
14002     } else if (cmailMsgLoaded) {
14003         if (currentMove == cmailOldMove &&
14004             commentList[cmailOldMove] != NULL &&
14005             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14006                    "Black offers a draw" : "White offers a draw")) {
14007 #ifdef NOTDEF
14008             AppendComment(cmailOldMove, "Draw declined", TRUE);
14009             DisplayComment(cmailOldMove - 1, "Draw declined");
14010 #endif /*NOTDEF*/
14011         } else {
14012             DisplayError(_("There is no pending offer on this move"), 0);
14013         }
14014     } else {
14015         /* Not used for offers from chess program */
14016     }
14017 }
14018
14019 void
14020 RematchEvent ()
14021 {
14022     /* Issue ICS rematch command */
14023     if (appData.icsActive) {
14024         SendToICS(ics_prefix);
14025         SendToICS("rematch\n");
14026     }
14027 }
14028
14029 void
14030 CallFlagEvent ()
14031 {
14032     /* Call your opponent's flag (claim a win on time) */
14033     if (appData.icsActive) {
14034         SendToICS(ics_prefix);
14035         SendToICS("flag\n");
14036     } else {
14037         switch (gameMode) {
14038           default:
14039             return;
14040           case MachinePlaysWhite:
14041             if (whiteFlag) {
14042                 if (blackFlag)
14043                   GameEnds(GameIsDrawn, "Both players ran out of time",
14044                            GE_PLAYER);
14045                 else
14046                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14047             } else {
14048                 DisplayError(_("Your opponent is not out of time"), 0);
14049             }
14050             break;
14051           case MachinePlaysBlack:
14052             if (blackFlag) {
14053                 if (whiteFlag)
14054                   GameEnds(GameIsDrawn, "Both players ran out of time",
14055                            GE_PLAYER);
14056                 else
14057                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14058             } else {
14059                 DisplayError(_("Your opponent is not out of time"), 0);
14060             }
14061             break;
14062         }
14063     }
14064 }
14065
14066 void
14067 ClockClick (int which)
14068 {       // [HGM] code moved to back-end from winboard.c
14069         if(which) { // black clock
14070           if (gameMode == EditPosition || gameMode == IcsExamining) {
14071             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14072             SetBlackToPlayEvent();
14073           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14074           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14075           } else if (shiftKey) {
14076             AdjustClock(which, -1);
14077           } else if (gameMode == IcsPlayingWhite ||
14078                      gameMode == MachinePlaysBlack) {
14079             CallFlagEvent();
14080           }
14081         } else { // white clock
14082           if (gameMode == EditPosition || gameMode == IcsExamining) {
14083             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14084             SetWhiteToPlayEvent();
14085           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14086           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14087           } else if (shiftKey) {
14088             AdjustClock(which, -1);
14089           } else if (gameMode == IcsPlayingBlack ||
14090                    gameMode == MachinePlaysWhite) {
14091             CallFlagEvent();
14092           }
14093         }
14094 }
14095
14096 void
14097 DrawEvent ()
14098 {
14099     /* Offer draw or accept pending draw offer from opponent */
14100
14101     if (appData.icsActive) {
14102         /* Note: tournament rules require draw offers to be
14103            made after you make your move but before you punch
14104            your clock.  Currently ICS doesn't let you do that;
14105            instead, you immediately punch your clock after making
14106            a move, but you can offer a draw at any time. */
14107
14108         SendToICS(ics_prefix);
14109         SendToICS("draw\n");
14110         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14111     } else if (cmailMsgLoaded) {
14112         if (currentMove == cmailOldMove &&
14113             commentList[cmailOldMove] != NULL &&
14114             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14115                    "Black offers a draw" : "White offers a draw")) {
14116             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14117             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14118         } else if (currentMove == cmailOldMove + 1) {
14119             char *offer = WhiteOnMove(cmailOldMove) ?
14120               "White offers a draw" : "Black offers a draw";
14121             AppendComment(currentMove, offer, TRUE);
14122             DisplayComment(currentMove - 1, offer);
14123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14124         } else {
14125             DisplayError(_("You must make your move before offering a draw"), 0);
14126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14127         }
14128     } else if (first.offeredDraw) {
14129         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14130     } else {
14131         if (first.sendDrawOffers) {
14132             SendToProgram("draw\n", &first);
14133             userOfferedDraw = TRUE;
14134         }
14135     }
14136 }
14137
14138 void
14139 AdjournEvent ()
14140 {
14141     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14142
14143     if (appData.icsActive) {
14144         SendToICS(ics_prefix);
14145         SendToICS("adjourn\n");
14146     } else {
14147         /* Currently GNU Chess doesn't offer or accept Adjourns */
14148     }
14149 }
14150
14151
14152 void
14153 AbortEvent ()
14154 {
14155     /* Offer Abort or accept pending Abort offer from opponent */
14156
14157     if (appData.icsActive) {
14158         SendToICS(ics_prefix);
14159         SendToICS("abort\n");
14160     } else {
14161         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14162     }
14163 }
14164
14165 void
14166 ResignEvent ()
14167 {
14168     /* Resign.  You can do this even if it's not your turn. */
14169
14170     if (appData.icsActive) {
14171         SendToICS(ics_prefix);
14172         SendToICS("resign\n");
14173     } else {
14174         switch (gameMode) {
14175           case MachinePlaysWhite:
14176             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14177             break;
14178           case MachinePlaysBlack:
14179             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14180             break;
14181           case EditGame:
14182             if (cmailMsgLoaded) {
14183                 TruncateGame();
14184                 if (WhiteOnMove(cmailOldMove)) {
14185                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14186                 } else {
14187                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14188                 }
14189                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14190             }
14191             break;
14192           default:
14193             break;
14194         }
14195     }
14196 }
14197
14198
14199 void
14200 StopObservingEvent ()
14201 {
14202     /* Stop observing current games */
14203     SendToICS(ics_prefix);
14204     SendToICS("unobserve\n");
14205 }
14206
14207 void
14208 StopExaminingEvent ()
14209 {
14210     /* Stop observing current game */
14211     SendToICS(ics_prefix);
14212     SendToICS("unexamine\n");
14213 }
14214
14215 void
14216 ForwardInner (int target)
14217 {
14218     int limit; int oldSeekGraphUp = seekGraphUp;
14219
14220     if (appData.debugMode)
14221         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14222                 target, currentMove, forwardMostMove);
14223
14224     if (gameMode == EditPosition)
14225       return;
14226
14227     seekGraphUp = FALSE;
14228     MarkTargetSquares(1);
14229
14230     if (gameMode == PlayFromGameFile && !pausing)
14231       PauseEvent();
14232
14233     if (gameMode == IcsExamining && pausing)
14234       limit = pauseExamForwardMostMove;
14235     else
14236       limit = forwardMostMove;
14237
14238     if (target > limit) target = limit;
14239
14240     if (target > 0 && moveList[target - 1][0]) {
14241         int fromX, fromY, toX, toY;
14242         toX = moveList[target - 1][2] - AAA;
14243         toY = moveList[target - 1][3] - ONE;
14244         if (moveList[target - 1][1] == '@') {
14245             if (appData.highlightLastMove) {
14246                 SetHighlights(-1, -1, toX, toY);
14247             }
14248         } else {
14249             fromX = moveList[target - 1][0] - AAA;
14250             fromY = moveList[target - 1][1] - ONE;
14251             if (target == currentMove + 1) {
14252                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14253             }
14254             if (appData.highlightLastMove) {
14255                 SetHighlights(fromX, fromY, toX, toY);
14256             }
14257         }
14258     }
14259     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14260         gameMode == Training || gameMode == PlayFromGameFile ||
14261         gameMode == AnalyzeFile) {
14262         while (currentMove < target) {
14263             SendMoveToProgram(currentMove++, &first);
14264         }
14265     } else {
14266         currentMove = target;
14267     }
14268
14269     if (gameMode == EditGame || gameMode == EndOfGame) {
14270         whiteTimeRemaining = timeRemaining[0][currentMove];
14271         blackTimeRemaining = timeRemaining[1][currentMove];
14272     }
14273     DisplayBothClocks();
14274     DisplayMove(currentMove - 1);
14275     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14276     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14277     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14278         DisplayComment(currentMove - 1, commentList[currentMove]);
14279     }
14280 }
14281
14282
14283 void
14284 ForwardEvent ()
14285 {
14286     if (gameMode == IcsExamining && !pausing) {
14287         SendToICS(ics_prefix);
14288         SendToICS("forward\n");
14289     } else {
14290         ForwardInner(currentMove + 1);
14291     }
14292 }
14293
14294 void
14295 ToEndEvent ()
14296 {
14297     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14298         /* to optimze, we temporarily turn off analysis mode while we feed
14299          * the remaining moves to the engine. Otherwise we get analysis output
14300          * after each move.
14301          */
14302         if (first.analysisSupport) {
14303           SendToProgram("exit\nforce\n", &first);
14304           first.analyzing = FALSE;
14305         }
14306     }
14307
14308     if (gameMode == IcsExamining && !pausing) {
14309         SendToICS(ics_prefix);
14310         SendToICS("forward 999999\n");
14311     } else {
14312         ForwardInner(forwardMostMove);
14313     }
14314
14315     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14316         /* we have fed all the moves, so reactivate analysis mode */
14317         SendToProgram("analyze\n", &first);
14318         first.analyzing = TRUE;
14319         /*first.maybeThinking = TRUE;*/
14320         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14321     }
14322 }
14323
14324 void
14325 BackwardInner (int target)
14326 {
14327     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14328
14329     if (appData.debugMode)
14330         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14331                 target, currentMove, forwardMostMove);
14332
14333     if (gameMode == EditPosition) return;
14334     seekGraphUp = FALSE;
14335     MarkTargetSquares(1);
14336     if (currentMove <= backwardMostMove) {
14337         ClearHighlights();
14338         DrawPosition(full_redraw, boards[currentMove]);
14339         return;
14340     }
14341     if (gameMode == PlayFromGameFile && !pausing)
14342       PauseEvent();
14343
14344     if (moveList[target][0]) {
14345         int fromX, fromY, toX, toY;
14346         toX = moveList[target][2] - AAA;
14347         toY = moveList[target][3] - ONE;
14348         if (moveList[target][1] == '@') {
14349             if (appData.highlightLastMove) {
14350                 SetHighlights(-1, -1, toX, toY);
14351             }
14352         } else {
14353             fromX = moveList[target][0] - AAA;
14354             fromY = moveList[target][1] - ONE;
14355             if (target == currentMove - 1) {
14356                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14357             }
14358             if (appData.highlightLastMove) {
14359                 SetHighlights(fromX, fromY, toX, toY);
14360             }
14361         }
14362     }
14363     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14364         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14365         while (currentMove > target) {
14366             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14367                 // null move cannot be undone. Reload program with move history before it.
14368                 int i;
14369                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14370                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14371                 }
14372                 SendBoard(&first, i); 
14373                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14374                 break;
14375             }
14376             SendToProgram("undo\n", &first);
14377             currentMove--;
14378         }
14379     } else {
14380         currentMove = target;
14381     }
14382
14383     if (gameMode == EditGame || gameMode == EndOfGame) {
14384         whiteTimeRemaining = timeRemaining[0][currentMove];
14385         blackTimeRemaining = timeRemaining[1][currentMove];
14386     }
14387     DisplayBothClocks();
14388     DisplayMove(currentMove - 1);
14389     DrawPosition(full_redraw, boards[currentMove]);
14390     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14391     // [HGM] PV info: routine tests if comment empty
14392     DisplayComment(currentMove - 1, commentList[currentMove]);
14393 }
14394
14395 void
14396 BackwardEvent ()
14397 {
14398     if (gameMode == IcsExamining && !pausing) {
14399         SendToICS(ics_prefix);
14400         SendToICS("backward\n");
14401     } else {
14402         BackwardInner(currentMove - 1);
14403     }
14404 }
14405
14406 void
14407 ToStartEvent ()
14408 {
14409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14410         /* to optimize, we temporarily turn off analysis mode while we undo
14411          * all the moves. Otherwise we get analysis output after each undo.
14412          */
14413         if (first.analysisSupport) {
14414           SendToProgram("exit\nforce\n", &first);
14415           first.analyzing = FALSE;
14416         }
14417     }
14418
14419     if (gameMode == IcsExamining && !pausing) {
14420         SendToICS(ics_prefix);
14421         SendToICS("backward 999999\n");
14422     } else {
14423         BackwardInner(backwardMostMove);
14424     }
14425
14426     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14427         /* we have fed all the moves, so reactivate analysis mode */
14428         SendToProgram("analyze\n", &first);
14429         first.analyzing = TRUE;
14430         /*first.maybeThinking = TRUE;*/
14431         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14432     }
14433 }
14434
14435 void
14436 ToNrEvent (int to)
14437 {
14438   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14439   if (to >= forwardMostMove) to = forwardMostMove;
14440   if (to <= backwardMostMove) to = backwardMostMove;
14441   if (to < currentMove) {
14442     BackwardInner(to);
14443   } else {
14444     ForwardInner(to);
14445   }
14446 }
14447
14448 void
14449 RevertEvent (Boolean annotate)
14450 {
14451     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14452         return;
14453     }
14454     if (gameMode != IcsExamining) {
14455         DisplayError(_("You are not examining a game"), 0);
14456         return;
14457     }
14458     if (pausing) {
14459         DisplayError(_("You can't revert while pausing"), 0);
14460         return;
14461     }
14462     SendToICS(ics_prefix);
14463     SendToICS("revert\n");
14464 }
14465
14466 void
14467 RetractMoveEvent ()
14468 {
14469     switch (gameMode) {
14470       case MachinePlaysWhite:
14471       case MachinePlaysBlack:
14472         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14473             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14474             return;
14475         }
14476         if (forwardMostMove < 2) return;
14477         currentMove = forwardMostMove = forwardMostMove - 2;
14478         whiteTimeRemaining = timeRemaining[0][currentMove];
14479         blackTimeRemaining = timeRemaining[1][currentMove];
14480         DisplayBothClocks();
14481         DisplayMove(currentMove - 1);
14482         ClearHighlights();/*!! could figure this out*/
14483         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14484         SendToProgram("remove\n", &first);
14485         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14486         break;
14487
14488       case BeginningOfGame:
14489       default:
14490         break;
14491
14492       case IcsPlayingWhite:
14493       case IcsPlayingBlack:
14494         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14495             SendToICS(ics_prefix);
14496             SendToICS("takeback 2\n");
14497         } else {
14498             SendToICS(ics_prefix);
14499             SendToICS("takeback 1\n");
14500         }
14501         break;
14502     }
14503 }
14504
14505 void
14506 MoveNowEvent ()
14507 {
14508     ChessProgramState *cps;
14509
14510     switch (gameMode) {
14511       case MachinePlaysWhite:
14512         if (!WhiteOnMove(forwardMostMove)) {
14513             DisplayError(_("It is your turn"), 0);
14514             return;
14515         }
14516         cps = &first;
14517         break;
14518       case MachinePlaysBlack:
14519         if (WhiteOnMove(forwardMostMove)) {
14520             DisplayError(_("It is your turn"), 0);
14521             return;
14522         }
14523         cps = &first;
14524         break;
14525       case TwoMachinesPlay:
14526         if (WhiteOnMove(forwardMostMove) ==
14527             (first.twoMachinesColor[0] == 'w')) {
14528             cps = &first;
14529         } else {
14530             cps = &second;
14531         }
14532         break;
14533       case BeginningOfGame:
14534       default:
14535         return;
14536     }
14537     SendToProgram("?\n", cps);
14538 }
14539
14540 void
14541 TruncateGameEvent ()
14542 {
14543     EditGameEvent();
14544     if (gameMode != EditGame) return;
14545     TruncateGame();
14546 }
14547
14548 void
14549 TruncateGame ()
14550 {
14551     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14552     if (forwardMostMove > currentMove) {
14553         if (gameInfo.resultDetails != NULL) {
14554             free(gameInfo.resultDetails);
14555             gameInfo.resultDetails = NULL;
14556             gameInfo.result = GameUnfinished;
14557         }
14558         forwardMostMove = currentMove;
14559         HistorySet(parseList, backwardMostMove, forwardMostMove,
14560                    currentMove-1);
14561     }
14562 }
14563
14564 void
14565 HintEvent ()
14566 {
14567     if (appData.noChessProgram) return;
14568     switch (gameMode) {
14569       case MachinePlaysWhite:
14570         if (WhiteOnMove(forwardMostMove)) {
14571             DisplayError(_("Wait until your turn"), 0);
14572             return;
14573         }
14574         break;
14575       case BeginningOfGame:
14576       case MachinePlaysBlack:
14577         if (!WhiteOnMove(forwardMostMove)) {
14578             DisplayError(_("Wait until your turn"), 0);
14579             return;
14580         }
14581         break;
14582       default:
14583         DisplayError(_("No hint available"), 0);
14584         return;
14585     }
14586     SendToProgram("hint\n", &first);
14587     hintRequested = TRUE;
14588 }
14589
14590 void
14591 BookEvent ()
14592 {
14593     if (appData.noChessProgram) return;
14594     switch (gameMode) {
14595       case MachinePlaysWhite:
14596         if (WhiteOnMove(forwardMostMove)) {
14597             DisplayError(_("Wait until your turn"), 0);
14598             return;
14599         }
14600         break;
14601       case BeginningOfGame:
14602       case MachinePlaysBlack:
14603         if (!WhiteOnMove(forwardMostMove)) {
14604             DisplayError(_("Wait until your turn"), 0);
14605             return;
14606         }
14607         break;
14608       case EditPosition:
14609         EditPositionDone(TRUE);
14610         break;
14611       case TwoMachinesPlay:
14612         return;
14613       default:
14614         break;
14615     }
14616     SendToProgram("bk\n", &first);
14617     bookOutput[0] = NULLCHAR;
14618     bookRequested = TRUE;
14619 }
14620
14621 void
14622 AboutGameEvent ()
14623 {
14624     char *tags = PGNTags(&gameInfo);
14625     TagsPopUp(tags, CmailMsg());
14626     free(tags);
14627 }
14628
14629 /* end button procedures */
14630
14631 void
14632 PrintPosition (FILE *fp, int move)
14633 {
14634     int i, j;
14635
14636     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14637         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14638             char c = PieceToChar(boards[move][i][j]);
14639             fputc(c == 'x' ? '.' : c, fp);
14640             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14641         }
14642     }
14643     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14644       fprintf(fp, "white to play\n");
14645     else
14646       fprintf(fp, "black to play\n");
14647 }
14648
14649 void
14650 PrintOpponents (FILE *fp)
14651 {
14652     if (gameInfo.white != NULL) {
14653         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14654     } else {
14655         fprintf(fp, "\n");
14656     }
14657 }
14658
14659 /* Find last component of program's own name, using some heuristics */
14660 void
14661 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14662 {
14663     char *p, *q, c;
14664     int local = (strcmp(host, "localhost") == 0);
14665     while (!local && (p = strchr(prog, ';')) != NULL) {
14666         p++;
14667         while (*p == ' ') p++;
14668         prog = p;
14669     }
14670     if (*prog == '"' || *prog == '\'') {
14671         q = strchr(prog + 1, *prog);
14672     } else {
14673         q = strchr(prog, ' ');
14674     }
14675     if (q == NULL) q = prog + strlen(prog);
14676     p = q;
14677     while (p >= prog && *p != '/' && *p != '\\') p--;
14678     p++;
14679     if(p == prog && *p == '"') p++;
14680     c = *q; *q = 0;
14681     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14682     memcpy(buf, p, q - p);
14683     buf[q - p] = NULLCHAR;
14684     if (!local) {
14685         strcat(buf, "@");
14686         strcat(buf, host);
14687     }
14688 }
14689
14690 char *
14691 TimeControlTagValue ()
14692 {
14693     char buf[MSG_SIZ];
14694     if (!appData.clockMode) {
14695       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14696     } else if (movesPerSession > 0) {
14697       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14698     } else if (timeIncrement == 0) {
14699       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14700     } else {
14701       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14702     }
14703     return StrSave(buf);
14704 }
14705
14706 void
14707 SetGameInfo ()
14708 {
14709     /* This routine is used only for certain modes */
14710     VariantClass v = gameInfo.variant;
14711     ChessMove r = GameUnfinished;
14712     char *p = NULL;
14713
14714     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14715         r = gameInfo.result;
14716         p = gameInfo.resultDetails;
14717         gameInfo.resultDetails = NULL;
14718     }
14719     ClearGameInfo(&gameInfo);
14720     gameInfo.variant = v;
14721
14722     switch (gameMode) {
14723       case MachinePlaysWhite:
14724         gameInfo.event = StrSave( appData.pgnEventHeader );
14725         gameInfo.site = StrSave(HostName());
14726         gameInfo.date = PGNDate();
14727         gameInfo.round = StrSave("-");
14728         gameInfo.white = StrSave(first.tidy);
14729         gameInfo.black = StrSave(UserName());
14730         gameInfo.timeControl = TimeControlTagValue();
14731         break;
14732
14733       case MachinePlaysBlack:
14734         gameInfo.event = StrSave( appData.pgnEventHeader );
14735         gameInfo.site = StrSave(HostName());
14736         gameInfo.date = PGNDate();
14737         gameInfo.round = StrSave("-");
14738         gameInfo.white = StrSave(UserName());
14739         gameInfo.black = StrSave(first.tidy);
14740         gameInfo.timeControl = TimeControlTagValue();
14741         break;
14742
14743       case TwoMachinesPlay:
14744         gameInfo.event = StrSave( appData.pgnEventHeader );
14745         gameInfo.site = StrSave(HostName());
14746         gameInfo.date = PGNDate();
14747         if (roundNr > 0) {
14748             char buf[MSG_SIZ];
14749             snprintf(buf, MSG_SIZ, "%d", roundNr);
14750             gameInfo.round = StrSave(buf);
14751         } else {
14752             gameInfo.round = StrSave("-");
14753         }
14754         if (first.twoMachinesColor[0] == 'w') {
14755             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14756             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14757         } else {
14758             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14759             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14760         }
14761         gameInfo.timeControl = TimeControlTagValue();
14762         break;
14763
14764       case EditGame:
14765         gameInfo.event = StrSave("Edited game");
14766         gameInfo.site = StrSave(HostName());
14767         gameInfo.date = PGNDate();
14768         gameInfo.round = StrSave("-");
14769         gameInfo.white = StrSave("-");
14770         gameInfo.black = StrSave("-");
14771         gameInfo.result = r;
14772         gameInfo.resultDetails = p;
14773         break;
14774
14775       case EditPosition:
14776         gameInfo.event = StrSave("Edited position");
14777         gameInfo.site = StrSave(HostName());
14778         gameInfo.date = PGNDate();
14779         gameInfo.round = StrSave("-");
14780         gameInfo.white = StrSave("-");
14781         gameInfo.black = StrSave("-");
14782         break;
14783
14784       case IcsPlayingWhite:
14785       case IcsPlayingBlack:
14786       case IcsObserving:
14787       case IcsExamining:
14788         break;
14789
14790       case PlayFromGameFile:
14791         gameInfo.event = StrSave("Game from non-PGN file");
14792         gameInfo.site = StrSave(HostName());
14793         gameInfo.date = PGNDate();
14794         gameInfo.round = StrSave("-");
14795         gameInfo.white = StrSave("?");
14796         gameInfo.black = StrSave("?");
14797         break;
14798
14799       default:
14800         break;
14801     }
14802 }
14803
14804 void
14805 ReplaceComment (int index, char *text)
14806 {
14807     int len;
14808     char *p;
14809     float score;
14810
14811     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14812        pvInfoList[index-1].depth == len &&
14813        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14814        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14815     while (*text == '\n') text++;
14816     len = strlen(text);
14817     while (len > 0 && text[len - 1] == '\n') len--;
14818
14819     if (commentList[index] != NULL)
14820       free(commentList[index]);
14821
14822     if (len == 0) {
14823         commentList[index] = NULL;
14824         return;
14825     }
14826   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14827       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14828       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14829     commentList[index] = (char *) malloc(len + 2);
14830     strncpy(commentList[index], text, len);
14831     commentList[index][len] = '\n';
14832     commentList[index][len + 1] = NULLCHAR;
14833   } else {
14834     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14835     char *p;
14836     commentList[index] = (char *) malloc(len + 7);
14837     safeStrCpy(commentList[index], "{\n", 3);
14838     safeStrCpy(commentList[index]+2, text, len+1);
14839     commentList[index][len+2] = NULLCHAR;
14840     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14841     strcat(commentList[index], "\n}\n");
14842   }
14843 }
14844
14845 void
14846 CrushCRs (char *text)
14847 {
14848   char *p = text;
14849   char *q = text;
14850   char ch;
14851
14852   do {
14853     ch = *p++;
14854     if (ch == '\r') continue;
14855     *q++ = ch;
14856   } while (ch != '\0');
14857 }
14858
14859 void
14860 AppendComment (int index, char *text, Boolean addBraces)
14861 /* addBraces  tells if we should add {} */
14862 {
14863     int oldlen, len;
14864     char *old;
14865
14866 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14867     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14868
14869     CrushCRs(text);
14870     while (*text == '\n') text++;
14871     len = strlen(text);
14872     while (len > 0 && text[len - 1] == '\n') len--;
14873     text[len] = NULLCHAR;
14874
14875     if (len == 0) return;
14876
14877     if (commentList[index] != NULL) {
14878       Boolean addClosingBrace = addBraces;
14879         old = commentList[index];
14880         oldlen = strlen(old);
14881         while(commentList[index][oldlen-1] ==  '\n')
14882           commentList[index][--oldlen] = NULLCHAR;
14883         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14884         safeStrCpy(commentList[index], old, oldlen + len + 6);
14885         free(old);
14886         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14887         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14888           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14889           while (*text == '\n') { text++; len--; }
14890           commentList[index][--oldlen] = NULLCHAR;
14891       }
14892         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14893         else          strcat(commentList[index], "\n");
14894         strcat(commentList[index], text);
14895         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14896         else          strcat(commentList[index], "\n");
14897     } else {
14898         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14899         if(addBraces)
14900           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14901         else commentList[index][0] = NULLCHAR;
14902         strcat(commentList[index], text);
14903         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14904         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14905     }
14906 }
14907
14908 static char *
14909 FindStr (char * text, char * sub_text)
14910 {
14911     char * result = strstr( text, sub_text );
14912
14913     if( result != NULL ) {
14914         result += strlen( sub_text );
14915     }
14916
14917     return result;
14918 }
14919
14920 /* [AS] Try to extract PV info from PGN comment */
14921 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14922 char *
14923 GetInfoFromComment (int index, char * text)
14924 {
14925     char * sep = text, *p;
14926
14927     if( text != NULL && index > 0 ) {
14928         int score = 0;
14929         int depth = 0;
14930         int time = -1, sec = 0, deci;
14931         char * s_eval = FindStr( text, "[%eval " );
14932         char * s_emt = FindStr( text, "[%emt " );
14933
14934         if( s_eval != NULL || s_emt != NULL ) {
14935             /* New style */
14936             char delim;
14937
14938             if( s_eval != NULL ) {
14939                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14940                     return text;
14941                 }
14942
14943                 if( delim != ']' ) {
14944                     return text;
14945                 }
14946             }
14947
14948             if( s_emt != NULL ) {
14949             }
14950                 return text;
14951         }
14952         else {
14953             /* We expect something like: [+|-]nnn.nn/dd */
14954             int score_lo = 0;
14955
14956             if(*text != '{') return text; // [HGM] braces: must be normal comment
14957
14958             sep = strchr( text, '/' );
14959             if( sep == NULL || sep < (text+4) ) {
14960                 return text;
14961             }
14962
14963             p = text;
14964             if(p[1] == '(') { // comment starts with PV
14965                p = strchr(p, ')'); // locate end of PV
14966                if(p == NULL || sep < p+5) return text;
14967                // at this point we have something like "{(.*) +0.23/6 ..."
14968                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14969                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14970                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14971             }
14972             time = -1; sec = -1; deci = -1;
14973             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14974                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14975                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14976                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14977                 return text;
14978             }
14979
14980             if( score_lo < 0 || score_lo >= 100 ) {
14981                 return text;
14982             }
14983
14984             if(sec >= 0) time = 600*time + 10*sec; else
14985             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14986
14987             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14988
14989             /* [HGM] PV time: now locate end of PV info */
14990             while( *++sep >= '0' && *sep <= '9'); // strip depth
14991             if(time >= 0)
14992             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14993             if(sec >= 0)
14994             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14995             if(deci >= 0)
14996             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14997             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14998         }
14999
15000         if( depth <= 0 ) {
15001             return text;
15002         }
15003
15004         if( time < 0 ) {
15005             time = -1;
15006         }
15007
15008         pvInfoList[index-1].depth = depth;
15009         pvInfoList[index-1].score = score;
15010         pvInfoList[index-1].time  = 10*time; // centi-sec
15011         if(*sep == '}') *sep = 0; else *--sep = '{';
15012         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15013     }
15014     return sep;
15015 }
15016
15017 void
15018 SendToProgram (char *message, ChessProgramState *cps)
15019 {
15020     int count, outCount, error;
15021     char buf[MSG_SIZ];
15022
15023     if (cps->pr == NoProc) return;
15024     Attention(cps);
15025
15026     if (appData.debugMode) {
15027         TimeMark now;
15028         GetTimeMark(&now);
15029         fprintf(debugFP, "%ld >%-6s: %s",
15030                 SubtractTimeMarks(&now, &programStartTime),
15031                 cps->which, message);
15032         if(serverFP)
15033             fprintf(serverFP, "%ld >%-6s: %s",
15034                 SubtractTimeMarks(&now, &programStartTime),
15035                 cps->which, message), fflush(serverFP);
15036     }
15037
15038     count = strlen(message);
15039     outCount = OutputToProcess(cps->pr, message, count, &error);
15040     if (outCount < count && !exiting
15041                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15042       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15043       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15044         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15045             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15046                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15047                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15048                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15049             } else {
15050                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15051                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15052                 gameInfo.result = res;
15053             }
15054             gameInfo.resultDetails = StrSave(buf);
15055         }
15056         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15057         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15058     }
15059 }
15060
15061 void
15062 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15063 {
15064     char *end_str;
15065     char buf[MSG_SIZ];
15066     ChessProgramState *cps = (ChessProgramState *)closure;
15067
15068     if (isr != cps->isr) return; /* Killed intentionally */
15069     if (count <= 0) {
15070         if (count == 0) {
15071             RemoveInputSource(cps->isr);
15072             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15073             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15074                     _(cps->which), cps->program);
15075         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15076                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15077                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15078                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15079                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15080                 } else {
15081                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15082                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15083                     gameInfo.result = res;
15084                 }
15085                 gameInfo.resultDetails = StrSave(buf);
15086             }
15087             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15088             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15089         } else {
15090             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15091                     _(cps->which), cps->program);
15092             RemoveInputSource(cps->isr);
15093
15094             /* [AS] Program is misbehaving badly... kill it */
15095             if( count == -2 ) {
15096                 DestroyChildProcess( cps->pr, 9 );
15097                 cps->pr = NoProc;
15098             }
15099
15100             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15101         }
15102         return;
15103     }
15104
15105     if ((end_str = strchr(message, '\r')) != NULL)
15106       *end_str = NULLCHAR;
15107     if ((end_str = strchr(message, '\n')) != NULL)
15108       *end_str = NULLCHAR;
15109
15110     if (appData.debugMode) {
15111         TimeMark now; int print = 1;
15112         char *quote = ""; char c; int i;
15113
15114         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15115                 char start = message[0];
15116                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15117                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15118                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15119                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15120                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15121                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15122                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15123                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15124                    sscanf(message, "hint: %c", &c)!=1 && 
15125                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15126                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15127                     print = (appData.engineComments >= 2);
15128                 }
15129                 message[0] = start; // restore original message
15130         }
15131         if(print) {
15132                 GetTimeMark(&now);
15133                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15134                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15135                         quote,
15136                         message);
15137                 if(serverFP)
15138                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15139                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15140                         quote,
15141                         message), fflush(serverFP);
15142         }
15143     }
15144
15145     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15146     if (appData.icsEngineAnalyze) {
15147         if (strstr(message, "whisper") != NULL ||
15148              strstr(message, "kibitz") != NULL ||
15149             strstr(message, "tellics") != NULL) return;
15150     }
15151
15152     HandleMachineMove(message, cps);
15153 }
15154
15155
15156 void
15157 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15158 {
15159     char buf[MSG_SIZ];
15160     int seconds;
15161
15162     if( timeControl_2 > 0 ) {
15163         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15164             tc = timeControl_2;
15165         }
15166     }
15167     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15168     inc /= cps->timeOdds;
15169     st  /= cps->timeOdds;
15170
15171     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15172
15173     if (st > 0) {
15174       /* Set exact time per move, normally using st command */
15175       if (cps->stKludge) {
15176         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15177         seconds = st % 60;
15178         if (seconds == 0) {
15179           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15180         } else {
15181           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15182         }
15183       } else {
15184         snprintf(buf, MSG_SIZ, "st %d\n", st);
15185       }
15186     } else {
15187       /* Set conventional or incremental time control, using level command */
15188       if (seconds == 0) {
15189         /* Note old gnuchess bug -- minutes:seconds used to not work.
15190            Fixed in later versions, but still avoid :seconds
15191            when seconds is 0. */
15192         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15193       } else {
15194         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15195                  seconds, inc/1000.);
15196       }
15197     }
15198     SendToProgram(buf, cps);
15199
15200     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15201     /* Orthogonally, limit search to given depth */
15202     if (sd > 0) {
15203       if (cps->sdKludge) {
15204         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15205       } else {
15206         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15207       }
15208       SendToProgram(buf, cps);
15209     }
15210
15211     if(cps->nps >= 0) { /* [HGM] nps */
15212         if(cps->supportsNPS == FALSE)
15213           cps->nps = -1; // don't use if engine explicitly says not supported!
15214         else {
15215           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15216           SendToProgram(buf, cps);
15217         }
15218     }
15219 }
15220
15221 ChessProgramState *
15222 WhitePlayer ()
15223 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15224 {
15225     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15226        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15227         return &second;
15228     return &first;
15229 }
15230
15231 void
15232 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15233 {
15234     char message[MSG_SIZ];
15235     long time, otime;
15236
15237     /* Note: this routine must be called when the clocks are stopped
15238        or when they have *just* been set or switched; otherwise
15239        it will be off by the time since the current tick started.
15240     */
15241     if (machineWhite) {
15242         time = whiteTimeRemaining / 10;
15243         otime = blackTimeRemaining / 10;
15244     } else {
15245         time = blackTimeRemaining / 10;
15246         otime = whiteTimeRemaining / 10;
15247     }
15248     /* [HGM] translate opponent's time by time-odds factor */
15249     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15250
15251     if (time <= 0) time = 1;
15252     if (otime <= 0) otime = 1;
15253
15254     snprintf(message, MSG_SIZ, "time %ld\n", time);
15255     SendToProgram(message, cps);
15256
15257     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15258     SendToProgram(message, cps);
15259 }
15260
15261 int
15262 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15263 {
15264   char buf[MSG_SIZ];
15265   int len = strlen(name);
15266   int val;
15267
15268   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15269     (*p) += len + 1;
15270     sscanf(*p, "%d", &val);
15271     *loc = (val != 0);
15272     while (**p && **p != ' ')
15273       (*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 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15283 {
15284   char buf[MSG_SIZ];
15285   int len = strlen(name);
15286   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15287     (*p) += len + 1;
15288     sscanf(*p, "%d", loc);
15289     while (**p && **p != ' ') (*p)++;
15290     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15291     SendToProgram(buf, cps);
15292     return TRUE;
15293   }
15294   return FALSE;
15295 }
15296
15297 int
15298 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15299 {
15300   char buf[MSG_SIZ];
15301   int len = strlen(name);
15302   if (strncmp((*p), name, len) == 0
15303       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15304     (*p) += len + 2;
15305     sscanf(*p, "%[^\"]", loc);
15306     while (**p && **p != '\"') (*p)++;
15307     if (**p == '\"') (*p)++;
15308     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15309     SendToProgram(buf, cps);
15310     return TRUE;
15311   }
15312   return FALSE;
15313 }
15314
15315 int
15316 ParseOption (Option *opt, ChessProgramState *cps)
15317 // [HGM] options: process the string that defines an engine option, and determine
15318 // name, type, default value, and allowed value range
15319 {
15320         char *p, *q, buf[MSG_SIZ];
15321         int n, min = (-1)<<31, max = 1<<31, def;
15322
15323         if(p = strstr(opt->name, " -spin ")) {
15324             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15325             if(max < min) max = min; // enforce consistency
15326             if(def < min) def = min;
15327             if(def > max) def = max;
15328             opt->value = def;
15329             opt->min = min;
15330             opt->max = max;
15331             opt->type = Spin;
15332         } else if((p = strstr(opt->name, " -slider "))) {
15333             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15334             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15335             if(max < min) max = min; // enforce consistency
15336             if(def < min) def = min;
15337             if(def > max) def = max;
15338             opt->value = def;
15339             opt->min = min;
15340             opt->max = max;
15341             opt->type = Spin; // Slider;
15342         } else if((p = strstr(opt->name, " -string "))) {
15343             opt->textValue = p+9;
15344             opt->type = TextBox;
15345         } else if((p = strstr(opt->name, " -file "))) {
15346             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15347             opt->textValue = p+7;
15348             opt->type = FileName; // FileName;
15349         } else if((p = strstr(opt->name, " -path "))) {
15350             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15351             opt->textValue = p+7;
15352             opt->type = PathName; // PathName;
15353         } else if(p = strstr(opt->name, " -check ")) {
15354             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15355             opt->value = (def != 0);
15356             opt->type = CheckBox;
15357         } else if(p = strstr(opt->name, " -combo ")) {
15358             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15359             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15360             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15361             opt->value = n = 0;
15362             while(q = StrStr(q, " /// ")) {
15363                 n++; *q = 0;    // count choices, and null-terminate each of them
15364                 q += 5;
15365                 if(*q == '*') { // remember default, which is marked with * prefix
15366                     q++;
15367                     opt->value = n;
15368                 }
15369                 cps->comboList[cps->comboCnt++] = q;
15370             }
15371             cps->comboList[cps->comboCnt++] = NULL;
15372             opt->max = n + 1;
15373             opt->type = ComboBox;
15374         } else if(p = strstr(opt->name, " -button")) {
15375             opt->type = Button;
15376         } else if(p = strstr(opt->name, " -save")) {
15377             opt->type = SaveButton;
15378         } else return FALSE;
15379         *p = 0; // terminate option name
15380         // now look if the command-line options define a setting for this engine option.
15381         if(cps->optionSettings && cps->optionSettings[0])
15382             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15383         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15384           snprintf(buf, MSG_SIZ, "option %s", p);
15385                 if(p = strstr(buf, ",")) *p = 0;
15386                 if(q = strchr(buf, '=')) switch(opt->type) {
15387                     case ComboBox:
15388                         for(n=0; n<opt->max; n++)
15389                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15390                         break;
15391                     case TextBox:
15392                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15393                         break;
15394                     case Spin:
15395                     case CheckBox:
15396                         opt->value = atoi(q+1);
15397                     default:
15398                         break;
15399                 }
15400                 strcat(buf, "\n");
15401                 SendToProgram(buf, cps);
15402         }
15403         return TRUE;
15404 }
15405
15406 void
15407 FeatureDone (ChessProgramState *cps, int val)
15408 {
15409   DelayedEventCallback cb = GetDelayedEvent();
15410   if ((cb == InitBackEnd3 && cps == &first) ||
15411       (cb == SettingsMenuIfReady && cps == &second) ||
15412       (cb == LoadEngine) ||
15413       (cb == TwoMachinesEventIfReady)) {
15414     CancelDelayedEvent();
15415     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15416   }
15417   cps->initDone = val;
15418 }
15419
15420 /* Parse feature command from engine */
15421 void
15422 ParseFeatures (char *args, ChessProgramState *cps)
15423 {
15424   char *p = args;
15425   char *q;
15426   int val;
15427   char buf[MSG_SIZ];
15428
15429   for (;;) {
15430     while (*p == ' ') p++;
15431     if (*p == NULLCHAR) return;
15432
15433     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15434     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15435     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15436     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15437     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15438     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15439     if (BoolFeature(&p, "reuse", &val, cps)) {
15440       /* Engine can disable reuse, but can't enable it if user said no */
15441       if (!val) cps->reuse = FALSE;
15442       continue;
15443     }
15444     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15445     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15446       if (gameMode == TwoMachinesPlay) {
15447         DisplayTwoMachinesTitle();
15448       } else {
15449         DisplayTitle("");
15450       }
15451       continue;
15452     }
15453     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15454     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15455     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15456     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15457     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15458     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15459     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15460     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15461     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15462     if (IntFeature(&p, "done", &val, cps)) {
15463       FeatureDone(cps, val);
15464       continue;
15465     }
15466     /* Added by Tord: */
15467     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15468     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15469     /* End of additions by Tord */
15470
15471     /* [HGM] added features: */
15472     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15473     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15474     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15475     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15476     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15477     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15478     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15479         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15480           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15481             SendToProgram(buf, cps);
15482             continue;
15483         }
15484         if(cps->nrOptions >= MAX_OPTIONS) {
15485             cps->nrOptions--;
15486             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15487             DisplayError(buf, 0);
15488         }
15489         continue;
15490     }
15491     /* End of additions by HGM */
15492
15493     /* unknown feature: complain and skip */
15494     q = p;
15495     while (*q && *q != '=') q++;
15496     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15497     SendToProgram(buf, cps);
15498     p = q;
15499     if (*p == '=') {
15500       p++;
15501       if (*p == '\"') {
15502         p++;
15503         while (*p && *p != '\"') p++;
15504         if (*p == '\"') p++;
15505       } else {
15506         while (*p && *p != ' ') p++;
15507       }
15508     }
15509   }
15510
15511 }
15512
15513 void
15514 PeriodicUpdatesEvent (int newState)
15515 {
15516     if (newState == appData.periodicUpdates)
15517       return;
15518
15519     appData.periodicUpdates=newState;
15520
15521     /* Display type changes, so update it now */
15522 //    DisplayAnalysis();
15523
15524     /* Get the ball rolling again... */
15525     if (newState) {
15526         AnalysisPeriodicEvent(1);
15527         StartAnalysisClock();
15528     }
15529 }
15530
15531 void
15532 PonderNextMoveEvent (int newState)
15533 {
15534     if (newState == appData.ponderNextMove) return;
15535     if (gameMode == EditPosition) EditPositionDone(TRUE);
15536     if (newState) {
15537         SendToProgram("hard\n", &first);
15538         if (gameMode == TwoMachinesPlay) {
15539             SendToProgram("hard\n", &second);
15540         }
15541     } else {
15542         SendToProgram("easy\n", &first);
15543         thinkOutput[0] = NULLCHAR;
15544         if (gameMode == TwoMachinesPlay) {
15545             SendToProgram("easy\n", &second);
15546         }
15547     }
15548     appData.ponderNextMove = newState;
15549 }
15550
15551 void
15552 NewSettingEvent (int option, int *feature, char *command, int value)
15553 {
15554     char buf[MSG_SIZ];
15555
15556     if (gameMode == EditPosition) EditPositionDone(TRUE);
15557     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15558     if(feature == NULL || *feature) SendToProgram(buf, &first);
15559     if (gameMode == TwoMachinesPlay) {
15560         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15561     }
15562 }
15563
15564 void
15565 ShowThinkingEvent ()
15566 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15567 {
15568     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15569     int newState = appData.showThinking
15570         // [HGM] thinking: other features now need thinking output as well
15571         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15572
15573     if (oldState == newState) return;
15574     oldState = newState;
15575     if (gameMode == EditPosition) EditPositionDone(TRUE);
15576     if (oldState) {
15577         SendToProgram("post\n", &first);
15578         if (gameMode == TwoMachinesPlay) {
15579             SendToProgram("post\n", &second);
15580         }
15581     } else {
15582         SendToProgram("nopost\n", &first);
15583         thinkOutput[0] = NULLCHAR;
15584         if (gameMode == TwoMachinesPlay) {
15585             SendToProgram("nopost\n", &second);
15586         }
15587     }
15588 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15589 }
15590
15591 void
15592 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15593 {
15594   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15595   if (pr == NoProc) return;
15596   AskQuestion(title, question, replyPrefix, pr);
15597 }
15598
15599 void
15600 TypeInEvent (char firstChar)
15601 {
15602     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15603         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15604         gameMode == AnalyzeMode || gameMode == EditGame || 
15605         gameMode == EditPosition || gameMode == IcsExamining ||
15606         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15607         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15608                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15609                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15610         gameMode == Training) PopUpMoveDialog(firstChar);
15611 }
15612
15613 void
15614 TypeInDoneEvent (char *move)
15615 {
15616         Board board;
15617         int n, fromX, fromY, toX, toY;
15618         char promoChar;
15619         ChessMove moveType;
15620
15621         // [HGM] FENedit
15622         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15623                 EditPositionPasteFEN(move);
15624                 return;
15625         }
15626         // [HGM] movenum: allow move number to be typed in any mode
15627         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15628           ToNrEvent(2*n-1);
15629           return;
15630         }
15631         // undocumented kludge: allow command-line option to be typed in!
15632         // (potentially fatal, and does not implement the effect of the option.)
15633         // should only be used for options that are values on which future decisions will be made,
15634         // and definitely not on options that would be used during initialization.
15635         if(strstr(move, "!!! -") == move) {
15636             ParseArgsFromString(move+4);
15637             return;
15638         }
15639
15640       if (gameMode != EditGame && currentMove != forwardMostMove && 
15641         gameMode != Training) {
15642         DisplayMoveError(_("Displayed move is not current"));
15643       } else {
15644         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15645           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15646         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15647         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15648           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15649           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15650         } else {
15651           DisplayMoveError(_("Could not parse move"));
15652         }
15653       }
15654 }
15655
15656 void
15657 DisplayMove (int moveNumber)
15658 {
15659     char message[MSG_SIZ];
15660     char res[MSG_SIZ];
15661     char cpThinkOutput[MSG_SIZ];
15662
15663     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15664
15665     if (moveNumber == forwardMostMove - 1 ||
15666         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15667
15668         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15669
15670         if (strchr(cpThinkOutput, '\n')) {
15671             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15672         }
15673     } else {
15674         *cpThinkOutput = NULLCHAR;
15675     }
15676
15677     /* [AS] Hide thinking from human user */
15678     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15679         *cpThinkOutput = NULLCHAR;
15680         if( thinkOutput[0] != NULLCHAR ) {
15681             int i;
15682
15683             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15684                 cpThinkOutput[i] = '.';
15685             }
15686             cpThinkOutput[i] = NULLCHAR;
15687             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15688         }
15689     }
15690
15691     if (moveNumber == forwardMostMove - 1 &&
15692         gameInfo.resultDetails != NULL) {
15693         if (gameInfo.resultDetails[0] == NULLCHAR) {
15694           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15695         } else {
15696           snprintf(res, MSG_SIZ, " {%s} %s",
15697                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15698         }
15699     } else {
15700         res[0] = NULLCHAR;
15701     }
15702
15703     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15704         DisplayMessage(res, cpThinkOutput);
15705     } else {
15706       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15707                 WhiteOnMove(moveNumber) ? " " : ".. ",
15708                 parseList[moveNumber], res);
15709         DisplayMessage(message, cpThinkOutput);
15710     }
15711 }
15712
15713 void
15714 DisplayComment (int moveNumber, char *text)
15715 {
15716     char title[MSG_SIZ];
15717
15718     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15719       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15720     } else {
15721       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15722               WhiteOnMove(moveNumber) ? " " : ".. ",
15723               parseList[moveNumber]);
15724     }
15725     if (text != NULL && (appData.autoDisplayComment || commentUp))
15726         CommentPopUp(title, text);
15727 }
15728
15729 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15730  * might be busy thinking or pondering.  It can be omitted if your
15731  * gnuchess is configured to stop thinking immediately on any user
15732  * input.  However, that gnuchess feature depends on the FIONREAD
15733  * ioctl, which does not work properly on some flavors of Unix.
15734  */
15735 void
15736 Attention (ChessProgramState *cps)
15737 {
15738 #if ATTENTION
15739     if (!cps->useSigint) return;
15740     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15741     switch (gameMode) {
15742       case MachinePlaysWhite:
15743       case MachinePlaysBlack:
15744       case TwoMachinesPlay:
15745       case IcsPlayingWhite:
15746       case IcsPlayingBlack:
15747       case AnalyzeMode:
15748       case AnalyzeFile:
15749         /* Skip if we know it isn't thinking */
15750         if (!cps->maybeThinking) return;
15751         if (appData.debugMode)
15752           fprintf(debugFP, "Interrupting %s\n", cps->which);
15753         InterruptChildProcess(cps->pr);
15754         cps->maybeThinking = FALSE;
15755         break;
15756       default:
15757         break;
15758     }
15759 #endif /*ATTENTION*/
15760 }
15761
15762 int
15763 CheckFlags ()
15764 {
15765     if (whiteTimeRemaining <= 0) {
15766         if (!whiteFlag) {
15767             whiteFlag = TRUE;
15768             if (appData.icsActive) {
15769                 if (appData.autoCallFlag &&
15770                     gameMode == IcsPlayingBlack && !blackFlag) {
15771                   SendToICS(ics_prefix);
15772                   SendToICS("flag\n");
15773                 }
15774             } else {
15775                 if (blackFlag) {
15776                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15777                 } else {
15778                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15779                     if (appData.autoCallFlag) {
15780                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15781                         return TRUE;
15782                     }
15783                 }
15784             }
15785         }
15786     }
15787     if (blackTimeRemaining <= 0) {
15788         if (!blackFlag) {
15789             blackFlag = TRUE;
15790             if (appData.icsActive) {
15791                 if (appData.autoCallFlag &&
15792                     gameMode == IcsPlayingWhite && !whiteFlag) {
15793                   SendToICS(ics_prefix);
15794                   SendToICS("flag\n");
15795                 }
15796             } else {
15797                 if (whiteFlag) {
15798                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15799                 } else {
15800                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15801                     if (appData.autoCallFlag) {
15802                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15803                         return TRUE;
15804                     }
15805                 }
15806             }
15807         }
15808     }
15809     return FALSE;
15810 }
15811
15812 void
15813 CheckTimeControl ()
15814 {
15815     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15816         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15817
15818     /*
15819      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15820      */
15821     if ( !WhiteOnMove(forwardMostMove) ) {
15822         /* White made time control */
15823         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15824         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15825         /* [HGM] time odds: correct new time quota for time odds! */
15826                                             / WhitePlayer()->timeOdds;
15827         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15828     } else {
15829         lastBlack -= blackTimeRemaining;
15830         /* Black made time control */
15831         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15832                                             / WhitePlayer()->other->timeOdds;
15833         lastWhite = whiteTimeRemaining;
15834     }
15835 }
15836
15837 void
15838 DisplayBothClocks ()
15839 {
15840     int wom = gameMode == EditPosition ?
15841       !blackPlaysFirst : WhiteOnMove(currentMove);
15842     DisplayWhiteClock(whiteTimeRemaining, wom);
15843     DisplayBlackClock(blackTimeRemaining, !wom);
15844 }
15845
15846
15847 /* Timekeeping seems to be a portability nightmare.  I think everyone
15848    has ftime(), but I'm really not sure, so I'm including some ifdefs
15849    to use other calls if you don't.  Clocks will be less accurate if
15850    you have neither ftime nor gettimeofday.
15851 */
15852
15853 /* VS 2008 requires the #include outside of the function */
15854 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15855 #include <sys/timeb.h>
15856 #endif
15857
15858 /* Get the current time as a TimeMark */
15859 void
15860 GetTimeMark (TimeMark *tm)
15861 {
15862 #if HAVE_GETTIMEOFDAY
15863
15864     struct timeval timeVal;
15865     struct timezone timeZone;
15866
15867     gettimeofday(&timeVal, &timeZone);
15868     tm->sec = (long) timeVal.tv_sec;
15869     tm->ms = (int) (timeVal.tv_usec / 1000L);
15870
15871 #else /*!HAVE_GETTIMEOFDAY*/
15872 #if HAVE_FTIME
15873
15874 // include <sys/timeb.h> / moved to just above start of function
15875     struct timeb timeB;
15876
15877     ftime(&timeB);
15878     tm->sec = (long) timeB.time;
15879     tm->ms = (int) timeB.millitm;
15880
15881 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15882     tm->sec = (long) time(NULL);
15883     tm->ms = 0;
15884 #endif
15885 #endif
15886 }
15887
15888 /* Return the difference in milliseconds between two
15889    time marks.  We assume the difference will fit in a long!
15890 */
15891 long
15892 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15893 {
15894     return 1000L*(tm2->sec - tm1->sec) +
15895            (long) (tm2->ms - tm1->ms);
15896 }
15897
15898
15899 /*
15900  * Code to manage the game clocks.
15901  *
15902  * In tournament play, black starts the clock and then white makes a move.
15903  * We give the human user a slight advantage if he is playing white---the
15904  * clocks don't run until he makes his first move, so it takes zero time.
15905  * Also, we don't account for network lag, so we could get out of sync
15906  * with GNU Chess's clock -- but then, referees are always right.
15907  */
15908
15909 static TimeMark tickStartTM;
15910 static long intendedTickLength;
15911
15912 long
15913 NextTickLength (long timeRemaining)
15914 {
15915     long nominalTickLength, nextTickLength;
15916
15917     if (timeRemaining > 0L && timeRemaining <= 10000L)
15918       nominalTickLength = 100L;
15919     else
15920       nominalTickLength = 1000L;
15921     nextTickLength = timeRemaining % nominalTickLength;
15922     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15923
15924     return nextTickLength;
15925 }
15926
15927 /* Adjust clock one minute up or down */
15928 void
15929 AdjustClock (Boolean which, int dir)
15930 {
15931     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15932     if(which) blackTimeRemaining += 60000*dir;
15933     else      whiteTimeRemaining += 60000*dir;
15934     DisplayBothClocks();
15935     adjustedClock = TRUE;
15936 }
15937
15938 /* Stop clocks and reset to a fresh time control */
15939 void
15940 ResetClocks ()
15941 {
15942     (void) StopClockTimer();
15943     if (appData.icsActive) {
15944         whiteTimeRemaining = blackTimeRemaining = 0;
15945     } else if (searchTime) {
15946         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15947         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15948     } else { /* [HGM] correct new time quote for time odds */
15949         whiteTC = blackTC = fullTimeControlString;
15950         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15951         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15952     }
15953     if (whiteFlag || blackFlag) {
15954         DisplayTitle("");
15955         whiteFlag = blackFlag = FALSE;
15956     }
15957     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15958     DisplayBothClocks();
15959     adjustedClock = FALSE;
15960 }
15961
15962 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15963
15964 /* Decrement running clock by amount of time that has passed */
15965 void
15966 DecrementClocks ()
15967 {
15968     long timeRemaining;
15969     long lastTickLength, fudge;
15970     TimeMark now;
15971
15972     if (!appData.clockMode) return;
15973     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15974
15975     GetTimeMark(&now);
15976
15977     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15978
15979     /* Fudge if we woke up a little too soon */
15980     fudge = intendedTickLength - lastTickLength;
15981     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15982
15983     if (WhiteOnMove(forwardMostMove)) {
15984         if(whiteNPS >= 0) lastTickLength = 0;
15985         timeRemaining = whiteTimeRemaining -= lastTickLength;
15986         if(timeRemaining < 0 && !appData.icsActive) {
15987             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15988             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15989                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15990                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15991             }
15992         }
15993         DisplayWhiteClock(whiteTimeRemaining - fudge,
15994                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15995     } else {
15996         if(blackNPS >= 0) lastTickLength = 0;
15997         timeRemaining = blackTimeRemaining -= lastTickLength;
15998         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15999             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16000             if(suddenDeath) {
16001                 blackStartMove = forwardMostMove;
16002                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16003             }
16004         }
16005         DisplayBlackClock(blackTimeRemaining - fudge,
16006                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16007     }
16008     if (CheckFlags()) return;
16009
16010     tickStartTM = now;
16011     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16012     StartClockTimer(intendedTickLength);
16013
16014     /* if the time remaining has fallen below the alarm threshold, sound the
16015      * alarm. if the alarm has sounded and (due to a takeback or time control
16016      * with increment) the time remaining has increased to a level above the
16017      * threshold, reset the alarm so it can sound again.
16018      */
16019
16020     if (appData.icsActive && appData.icsAlarm) {
16021
16022         /* make sure we are dealing with the user's clock */
16023         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16024                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16025            )) return;
16026
16027         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16028             alarmSounded = FALSE;
16029         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16030             PlayAlarmSound();
16031             alarmSounded = TRUE;
16032         }
16033     }
16034 }
16035
16036
16037 /* A player has just moved, so stop the previously running
16038    clock and (if in clock mode) start the other one.
16039    We redisplay both clocks in case we're in ICS mode, because
16040    ICS gives us an update to both clocks after every move.
16041    Note that this routine is called *after* forwardMostMove
16042    is updated, so the last fractional tick must be subtracted
16043    from the color that is *not* on move now.
16044 */
16045 void
16046 SwitchClocks (int newMoveNr)
16047 {
16048     long lastTickLength;
16049     TimeMark now;
16050     int flagged = FALSE;
16051
16052     GetTimeMark(&now);
16053
16054     if (StopClockTimer() && appData.clockMode) {
16055         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16056         if (!WhiteOnMove(forwardMostMove)) {
16057             if(blackNPS >= 0) lastTickLength = 0;
16058             blackTimeRemaining -= lastTickLength;
16059            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16060 //         if(pvInfoList[forwardMostMove].time == -1)
16061                  pvInfoList[forwardMostMove].time =               // use GUI time
16062                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16063         } else {
16064            if(whiteNPS >= 0) lastTickLength = 0;
16065            whiteTimeRemaining -= lastTickLength;
16066            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16067 //         if(pvInfoList[forwardMostMove].time == -1)
16068                  pvInfoList[forwardMostMove].time =
16069                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16070         }
16071         flagged = CheckFlags();
16072     }
16073     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16074     CheckTimeControl();
16075
16076     if (flagged || !appData.clockMode) return;
16077
16078     switch (gameMode) {
16079       case MachinePlaysBlack:
16080       case MachinePlaysWhite:
16081       case BeginningOfGame:
16082         if (pausing) return;
16083         break;
16084
16085       case EditGame:
16086       case PlayFromGameFile:
16087       case IcsExamining:
16088         return;
16089
16090       default:
16091         break;
16092     }
16093
16094     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16095         if(WhiteOnMove(forwardMostMove))
16096              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16097         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16098     }
16099
16100     tickStartTM = now;
16101     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16102       whiteTimeRemaining : blackTimeRemaining);
16103     StartClockTimer(intendedTickLength);
16104 }
16105
16106
16107 /* Stop both clocks */
16108 void
16109 StopClocks ()
16110 {
16111     long lastTickLength;
16112     TimeMark now;
16113
16114     if (!StopClockTimer()) return;
16115     if (!appData.clockMode) return;
16116
16117     GetTimeMark(&now);
16118
16119     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16120     if (WhiteOnMove(forwardMostMove)) {
16121         if(whiteNPS >= 0) lastTickLength = 0;
16122         whiteTimeRemaining -= lastTickLength;
16123         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16124     } else {
16125         if(blackNPS >= 0) lastTickLength = 0;
16126         blackTimeRemaining -= lastTickLength;
16127         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16128     }
16129     CheckFlags();
16130 }
16131
16132 /* Start clock of player on move.  Time may have been reset, so
16133    if clock is already running, stop and restart it. */
16134 void
16135 StartClocks ()
16136 {
16137     (void) StopClockTimer(); /* in case it was running already */
16138     DisplayBothClocks();
16139     if (CheckFlags()) return;
16140
16141     if (!appData.clockMode) return;
16142     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16143
16144     GetTimeMark(&tickStartTM);
16145     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16146       whiteTimeRemaining : blackTimeRemaining);
16147
16148    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16149     whiteNPS = blackNPS = -1;
16150     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16151        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16152         whiteNPS = first.nps;
16153     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16154        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16155         blackNPS = first.nps;
16156     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16157         whiteNPS = second.nps;
16158     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16159         blackNPS = second.nps;
16160     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16161
16162     StartClockTimer(intendedTickLength);
16163 }
16164
16165 char *
16166 TimeString (long ms)
16167 {
16168     long second, minute, hour, day;
16169     char *sign = "";
16170     static char buf[32];
16171
16172     if (ms > 0 && ms <= 9900) {
16173       /* convert milliseconds to tenths, rounding up */
16174       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16175
16176       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16177       return buf;
16178     }
16179
16180     /* convert milliseconds to seconds, rounding up */
16181     /* use floating point to avoid strangeness of integer division
16182        with negative dividends on many machines */
16183     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16184
16185     if (second < 0) {
16186         sign = "-";
16187         second = -second;
16188     }
16189
16190     day = second / (60 * 60 * 24);
16191     second = second % (60 * 60 * 24);
16192     hour = second / (60 * 60);
16193     second = second % (60 * 60);
16194     minute = second / 60;
16195     second = second % 60;
16196
16197     if (day > 0)
16198       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16199               sign, day, hour, minute, second);
16200     else if (hour > 0)
16201       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16202     else
16203       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16204
16205     return buf;
16206 }
16207
16208
16209 /*
16210  * This is necessary because some C libraries aren't ANSI C compliant yet.
16211  */
16212 char *
16213 StrStr (char *string, char *match)
16214 {
16215     int i, length;
16216
16217     length = strlen(match);
16218
16219     for (i = strlen(string) - length; i >= 0; i--, string++)
16220       if (!strncmp(match, string, length))
16221         return string;
16222
16223     return NULL;
16224 }
16225
16226 char *
16227 StrCaseStr (char *string, char *match)
16228 {
16229     int i, j, length;
16230
16231     length = strlen(match);
16232
16233     for (i = strlen(string) - length; i >= 0; i--, string++) {
16234         for (j = 0; j < length; j++) {
16235             if (ToLower(match[j]) != ToLower(string[j]))
16236               break;
16237         }
16238         if (j == length) return string;
16239     }
16240
16241     return NULL;
16242 }
16243
16244 #ifndef _amigados
16245 int
16246 StrCaseCmp (char *s1, char *s2)
16247 {
16248     char c1, c2;
16249
16250     for (;;) {
16251         c1 = ToLower(*s1++);
16252         c2 = ToLower(*s2++);
16253         if (c1 > c2) return 1;
16254         if (c1 < c2) return -1;
16255         if (c1 == NULLCHAR) return 0;
16256     }
16257 }
16258
16259
16260 int
16261 ToLower (int c)
16262 {
16263     return isupper(c) ? tolower(c) : c;
16264 }
16265
16266
16267 int
16268 ToUpper (int c)
16269 {
16270     return islower(c) ? toupper(c) : c;
16271 }
16272 #endif /* !_amigados    */
16273
16274 char *
16275 StrSave (char *s)
16276 {
16277   char *ret;
16278
16279   if ((ret = (char *) malloc(strlen(s) + 1)))
16280     {
16281       safeStrCpy(ret, s, strlen(s)+1);
16282     }
16283   return ret;
16284 }
16285
16286 char *
16287 StrSavePtr (char *s, char **savePtr)
16288 {
16289     if (*savePtr) {
16290         free(*savePtr);
16291     }
16292     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16293       safeStrCpy(*savePtr, s, strlen(s)+1);
16294     }
16295     return(*savePtr);
16296 }
16297
16298 char *
16299 PGNDate ()
16300 {
16301     time_t clock;
16302     struct tm *tm;
16303     char buf[MSG_SIZ];
16304
16305     clock = time((time_t *)NULL);
16306     tm = localtime(&clock);
16307     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16308             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16309     return StrSave(buf);
16310 }
16311
16312
16313 char *
16314 PositionToFEN (int move, char *overrideCastling)
16315 {
16316     int i, j, fromX, fromY, toX, toY;
16317     int whiteToPlay;
16318     char buf[MSG_SIZ];
16319     char *p, *q;
16320     int emptycount;
16321     ChessSquare piece;
16322
16323     whiteToPlay = (gameMode == EditPosition) ?
16324       !blackPlaysFirst : (move % 2 == 0);
16325     p = buf;
16326
16327     /* Piece placement data */
16328     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16329         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16330         emptycount = 0;
16331         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16332             if (boards[move][i][j] == EmptySquare) {
16333                 emptycount++;
16334             } else { ChessSquare piece = boards[move][i][j];
16335                 if (emptycount > 0) {
16336                     if(emptycount<10) /* [HGM] can be >= 10 */
16337                         *p++ = '0' + emptycount;
16338                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16339                     emptycount = 0;
16340                 }
16341                 if(PieceToChar(piece) == '+') {
16342                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16343                     *p++ = '+';
16344                     piece = (ChessSquare)(DEMOTED piece);
16345                 }
16346                 *p++ = PieceToChar(piece);
16347                 if(p[-1] == '~') {
16348                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16349                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16350                     *p++ = '~';
16351                 }
16352             }
16353         }
16354         if (emptycount > 0) {
16355             if(emptycount<10) /* [HGM] can be >= 10 */
16356                 *p++ = '0' + emptycount;
16357             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16358             emptycount = 0;
16359         }
16360         *p++ = '/';
16361     }
16362     *(p - 1) = ' ';
16363
16364     /* [HGM] print Crazyhouse or Shogi holdings */
16365     if( gameInfo.holdingsWidth ) {
16366         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16367         q = p;
16368         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16369             piece = boards[move][i][BOARD_WIDTH-1];
16370             if( piece != EmptySquare )
16371               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16372                   *p++ = PieceToChar(piece);
16373         }
16374         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16375             piece = boards[move][BOARD_HEIGHT-i-1][0];
16376             if( piece != EmptySquare )
16377               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16378                   *p++ = PieceToChar(piece);
16379         }
16380
16381         if( q == p ) *p++ = '-';
16382         *p++ = ']';
16383         *p++ = ' ';
16384     }
16385
16386     /* Active color */
16387     *p++ = whiteToPlay ? 'w' : 'b';
16388     *p++ = ' ';
16389
16390   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16391     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16392   } else {
16393   if(nrCastlingRights) {
16394      q = p;
16395      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16396        /* [HGM] write directly from rights */
16397            if(boards[move][CASTLING][2] != NoRights &&
16398               boards[move][CASTLING][0] != NoRights   )
16399                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16400            if(boards[move][CASTLING][2] != NoRights &&
16401               boards[move][CASTLING][1] != NoRights   )
16402                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16403            if(boards[move][CASTLING][5] != NoRights &&
16404               boards[move][CASTLING][3] != NoRights   )
16405                 *p++ = boards[move][CASTLING][3] + AAA;
16406            if(boards[move][CASTLING][5] != NoRights &&
16407               boards[move][CASTLING][4] != NoRights   )
16408                 *p++ = boards[move][CASTLING][4] + AAA;
16409      } else {
16410
16411         /* [HGM] write true castling rights */
16412         if( nrCastlingRights == 6 ) {
16413             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16414                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16415             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16416                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16417             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16418                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16419             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16420                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16421         }
16422      }
16423      if (q == p) *p++ = '-'; /* No castling rights */
16424      *p++ = ' ';
16425   }
16426
16427   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16428      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16429     /* En passant target square */
16430     if (move > backwardMostMove) {
16431         fromX = moveList[move - 1][0] - AAA;
16432         fromY = moveList[move - 1][1] - ONE;
16433         toX = moveList[move - 1][2] - AAA;
16434         toY = moveList[move - 1][3] - ONE;
16435         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16436             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16437             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16438             fromX == toX) {
16439             /* 2-square pawn move just happened */
16440             *p++ = toX + AAA;
16441             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16442         } else {
16443             *p++ = '-';
16444         }
16445     } else if(move == backwardMostMove) {
16446         // [HGM] perhaps we should always do it like this, and forget the above?
16447         if((signed char)boards[move][EP_STATUS] >= 0) {
16448             *p++ = boards[move][EP_STATUS] + AAA;
16449             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16450         } else {
16451             *p++ = '-';
16452         }
16453     } else {
16454         *p++ = '-';
16455     }
16456     *p++ = ' ';
16457   }
16458   }
16459
16460     /* [HGM] find reversible plies */
16461     {   int i = 0, j=move;
16462
16463         if (appData.debugMode) { int k;
16464             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16465             for(k=backwardMostMove; k<=forwardMostMove; k++)
16466                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16467
16468         }
16469
16470         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16471         if( j == backwardMostMove ) i += initialRulePlies;
16472         sprintf(p, "%d ", i);
16473         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16474     }
16475     /* Fullmove number */
16476     sprintf(p, "%d", (move / 2) + 1);
16477
16478     return StrSave(buf);
16479 }
16480
16481 Boolean
16482 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16483 {
16484     int i, j;
16485     char *p, c;
16486     int emptycount;
16487     ChessSquare piece;
16488
16489     p = fen;
16490
16491     /* [HGM] by default clear Crazyhouse holdings, if present */
16492     if(gameInfo.holdingsWidth) {
16493        for(i=0; i<BOARD_HEIGHT; i++) {
16494            board[i][0]             = EmptySquare; /* black holdings */
16495            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16496            board[i][1]             = (ChessSquare) 0; /* black counts */
16497            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16498        }
16499     }
16500
16501     /* Piece placement data */
16502     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16503         j = 0;
16504         for (;;) {
16505             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16506                 if (*p == '/') p++;
16507                 emptycount = gameInfo.boardWidth - j;
16508                 while (emptycount--)
16509                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16510                 break;
16511 #if(BOARD_FILES >= 10)
16512             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16513                 p++; emptycount=10;
16514                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16515                 while (emptycount--)
16516                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16517 #endif
16518             } else if (isdigit(*p)) {
16519                 emptycount = *p++ - '0';
16520                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16521                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16522                 while (emptycount--)
16523                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16524             } else if (*p == '+' || isalpha(*p)) {
16525                 if (j >= gameInfo.boardWidth) return FALSE;
16526                 if(*p=='+') {
16527                     piece = CharToPiece(*++p);
16528                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16529                     piece = (ChessSquare) (PROMOTED piece ); p++;
16530                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16531                 } else piece = CharToPiece(*p++);
16532
16533                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16534                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16535                     piece = (ChessSquare) (PROMOTED piece);
16536                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16537                     p++;
16538                 }
16539                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16540             } else {
16541                 return FALSE;
16542             }
16543         }
16544     }
16545     while (*p == '/' || *p == ' ') p++;
16546
16547     /* [HGM] look for Crazyhouse holdings here */
16548     while(*p==' ') p++;
16549     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16550         if(*p == '[') p++;
16551         if(*p == '-' ) p++; /* empty holdings */ else {
16552             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16553             /* if we would allow FEN reading to set board size, we would   */
16554             /* have to add holdings and shift the board read so far here   */
16555             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16556                 p++;
16557                 if((int) piece >= (int) BlackPawn ) {
16558                     i = (int)piece - (int)BlackPawn;
16559                     i = PieceToNumber((ChessSquare)i);
16560                     if( i >= gameInfo.holdingsSize ) return FALSE;
16561                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16562                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16563                 } else {
16564                     i = (int)piece - (int)WhitePawn;
16565                     i = PieceToNumber((ChessSquare)i);
16566                     if( i >= gameInfo.holdingsSize ) return FALSE;
16567                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16568                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16569                 }
16570             }
16571         }
16572         if(*p == ']') p++;
16573     }
16574
16575     while(*p == ' ') p++;
16576
16577     /* Active color */
16578     c = *p++;
16579     if(appData.colorNickNames) {
16580       if( c == appData.colorNickNames[0] ) c = 'w'; else
16581       if( c == appData.colorNickNames[1] ) c = 'b';
16582     }
16583     switch (c) {
16584       case 'w':
16585         *blackPlaysFirst = FALSE;
16586         break;
16587       case 'b':
16588         *blackPlaysFirst = TRUE;
16589         break;
16590       default:
16591         return FALSE;
16592     }
16593
16594     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16595     /* return the extra info in global variiables             */
16596
16597     /* set defaults in case FEN is incomplete */
16598     board[EP_STATUS] = EP_UNKNOWN;
16599     for(i=0; i<nrCastlingRights; i++ ) {
16600         board[CASTLING][i] =
16601             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16602     }   /* assume possible unless obviously impossible */
16603     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16604     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16605     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16606                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16607     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16608     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16609     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16610                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16611     FENrulePlies = 0;
16612
16613     while(*p==' ') p++;
16614     if(nrCastlingRights) {
16615       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16616           /* castling indicator present, so default becomes no castlings */
16617           for(i=0; i<nrCastlingRights; i++ ) {
16618                  board[CASTLING][i] = NoRights;
16619           }
16620       }
16621       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16622              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16623              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16624              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16625         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16626
16627         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16628             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16629             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16630         }
16631         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16632             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16633         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16634                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16635         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16636                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16637         switch(c) {
16638           case'K':
16639               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16640               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16641               board[CASTLING][2] = whiteKingFile;
16642               break;
16643           case'Q':
16644               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16645               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16646               board[CASTLING][2] = whiteKingFile;
16647               break;
16648           case'k':
16649               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16650               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16651               board[CASTLING][5] = blackKingFile;
16652               break;
16653           case'q':
16654               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16655               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16656               board[CASTLING][5] = blackKingFile;
16657           case '-':
16658               break;
16659           default: /* FRC castlings */
16660               if(c >= 'a') { /* black rights */
16661                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16662                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16663                   if(i == BOARD_RGHT) break;
16664                   board[CASTLING][5] = i;
16665                   c -= AAA;
16666                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16667                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16668                   if(c > i)
16669                       board[CASTLING][3] = c;
16670                   else
16671                       board[CASTLING][4] = c;
16672               } else { /* white rights */
16673                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16674                     if(board[0][i] == WhiteKing) break;
16675                   if(i == BOARD_RGHT) break;
16676                   board[CASTLING][2] = i;
16677                   c -= AAA - 'a' + 'A';
16678                   if(board[0][c] >= WhiteKing) break;
16679                   if(c > i)
16680                       board[CASTLING][0] = c;
16681                   else
16682                       board[CASTLING][1] = c;
16683               }
16684         }
16685       }
16686       for(i=0; i<nrCastlingRights; i++)
16687         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16688     if (appData.debugMode) {
16689         fprintf(debugFP, "FEN castling rights:");
16690         for(i=0; i<nrCastlingRights; i++)
16691         fprintf(debugFP, " %d", board[CASTLING][i]);
16692         fprintf(debugFP, "\n");
16693     }
16694
16695       while(*p==' ') p++;
16696     }
16697
16698     /* read e.p. field in games that know e.p. capture */
16699     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16700        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16701       if(*p=='-') {
16702         p++; board[EP_STATUS] = EP_NONE;
16703       } else {
16704          char c = *p++ - AAA;
16705
16706          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16707          if(*p >= '0' && *p <='9') p++;
16708          board[EP_STATUS] = c;
16709       }
16710     }
16711
16712
16713     if(sscanf(p, "%d", &i) == 1) {
16714         FENrulePlies = i; /* 50-move ply counter */
16715         /* (The move number is still ignored)    */
16716     }
16717
16718     return TRUE;
16719 }
16720
16721 void
16722 EditPositionPasteFEN (char *fen)
16723 {
16724   if (fen != NULL) {
16725     Board initial_position;
16726
16727     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16728       DisplayError(_("Bad FEN position in clipboard"), 0);
16729       return ;
16730     } else {
16731       int savedBlackPlaysFirst = blackPlaysFirst;
16732       EditPositionEvent();
16733       blackPlaysFirst = savedBlackPlaysFirst;
16734       CopyBoard(boards[0], initial_position);
16735       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16736       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16737       DisplayBothClocks();
16738       DrawPosition(FALSE, boards[currentMove]);
16739     }
16740   }
16741 }
16742
16743 static char cseq[12] = "\\   ";
16744
16745 Boolean
16746 set_cont_sequence (char *new_seq)
16747 {
16748     int len;
16749     Boolean ret;
16750
16751     // handle bad attempts to set the sequence
16752         if (!new_seq)
16753                 return 0; // acceptable error - no debug
16754
16755     len = strlen(new_seq);
16756     ret = (len > 0) && (len < sizeof(cseq));
16757     if (ret)
16758       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16759     else if (appData.debugMode)
16760       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16761     return ret;
16762 }
16763
16764 /*
16765     reformat a source message so words don't cross the width boundary.  internal
16766     newlines are not removed.  returns the wrapped size (no null character unless
16767     included in source message).  If dest is NULL, only calculate the size required
16768     for the dest buffer.  lp argument indicats line position upon entry, and it's
16769     passed back upon exit.
16770 */
16771 int
16772 wrap (char *dest, char *src, int count, int width, int *lp)
16773 {
16774     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16775
16776     cseq_len = strlen(cseq);
16777     old_line = line = *lp;
16778     ansi = len = clen = 0;
16779
16780     for (i=0; i < count; i++)
16781     {
16782         if (src[i] == '\033')
16783             ansi = 1;
16784
16785         // if we hit the width, back up
16786         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16787         {
16788             // store i & len in case the word is too long
16789             old_i = i, old_len = len;
16790
16791             // find the end of the last word
16792             while (i && src[i] != ' ' && src[i] != '\n')
16793             {
16794                 i--;
16795                 len--;
16796             }
16797
16798             // word too long?  restore i & len before splitting it
16799             if ((old_i-i+clen) >= width)
16800             {
16801                 i = old_i;
16802                 len = old_len;
16803             }
16804
16805             // extra space?
16806             if (i && src[i-1] == ' ')
16807                 len--;
16808
16809             if (src[i] != ' ' && src[i] != '\n')
16810             {
16811                 i--;
16812                 if (len)
16813                     len--;
16814             }
16815
16816             // now append the newline and continuation sequence
16817             if (dest)
16818                 dest[len] = '\n';
16819             len++;
16820             if (dest)
16821                 strncpy(dest+len, cseq, cseq_len);
16822             len += cseq_len;
16823             line = cseq_len;
16824             clen = cseq_len;
16825             continue;
16826         }
16827
16828         if (dest)
16829             dest[len] = src[i];
16830         len++;
16831         if (!ansi)
16832             line++;
16833         if (src[i] == '\n')
16834             line = 0;
16835         if (src[i] == 'm')
16836             ansi = 0;
16837     }
16838     if (dest && appData.debugMode)
16839     {
16840         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16841             count, width, line, len, *lp);
16842         show_bytes(debugFP, src, count);
16843         fprintf(debugFP, "\ndest: ");
16844         show_bytes(debugFP, dest, len);
16845         fprintf(debugFP, "\n");
16846     }
16847     *lp = dest ? line : old_line;
16848
16849     return len;
16850 }
16851
16852 // [HGM] vari: routines for shelving variations
16853 Boolean modeRestore = FALSE;
16854
16855 void
16856 PushInner (int firstMove, int lastMove)
16857 {
16858         int i, j, nrMoves = lastMove - firstMove;
16859
16860         // push current tail of game on stack
16861         savedResult[storedGames] = gameInfo.result;
16862         savedDetails[storedGames] = gameInfo.resultDetails;
16863         gameInfo.resultDetails = NULL;
16864         savedFirst[storedGames] = firstMove;
16865         savedLast [storedGames] = lastMove;
16866         savedFramePtr[storedGames] = framePtr;
16867         framePtr -= nrMoves; // reserve space for the boards
16868         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16869             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16870             for(j=0; j<MOVE_LEN; j++)
16871                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16872             for(j=0; j<2*MOVE_LEN; j++)
16873                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16874             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16875             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16876             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16877             pvInfoList[firstMove+i-1].depth = 0;
16878             commentList[framePtr+i] = commentList[firstMove+i];
16879             commentList[firstMove+i] = NULL;
16880         }
16881
16882         storedGames++;
16883         forwardMostMove = firstMove; // truncate game so we can start variation
16884 }
16885
16886 void
16887 PushTail (int firstMove, int lastMove)
16888 {
16889         if(appData.icsActive) { // only in local mode
16890                 forwardMostMove = currentMove; // mimic old ICS behavior
16891                 return;
16892         }
16893         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16894
16895         PushInner(firstMove, lastMove);
16896         if(storedGames == 1) GreyRevert(FALSE);
16897         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16898 }
16899
16900 void
16901 PopInner (Boolean annotate)
16902 {
16903         int i, j, nrMoves;
16904         char buf[8000], moveBuf[20];
16905
16906         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16907         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16908         nrMoves = savedLast[storedGames] - currentMove;
16909         if(annotate) {
16910                 int cnt = 10;
16911                 if(!WhiteOnMove(currentMove))
16912                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16913                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16914                 for(i=currentMove; i<forwardMostMove; i++) {
16915                         if(WhiteOnMove(i))
16916                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16917                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16918                         strcat(buf, moveBuf);
16919                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16920                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16921                 }
16922                 strcat(buf, ")");
16923         }
16924         for(i=1; i<=nrMoves; i++) { // copy last variation back
16925             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16926             for(j=0; j<MOVE_LEN; j++)
16927                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16928             for(j=0; j<2*MOVE_LEN; j++)
16929                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16930             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16931             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16932             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16933             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16934             commentList[currentMove+i] = commentList[framePtr+i];
16935             commentList[framePtr+i] = NULL;
16936         }
16937         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16938         framePtr = savedFramePtr[storedGames];
16939         gameInfo.result = savedResult[storedGames];
16940         if(gameInfo.resultDetails != NULL) {
16941             free(gameInfo.resultDetails);
16942       }
16943         gameInfo.resultDetails = savedDetails[storedGames];
16944         forwardMostMove = currentMove + nrMoves;
16945 }
16946
16947 Boolean
16948 PopTail (Boolean annotate)
16949 {
16950         if(appData.icsActive) return FALSE; // only in local mode
16951         if(!storedGames) return FALSE; // sanity
16952         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16953
16954         PopInner(annotate);
16955         if(currentMove < forwardMostMove) ForwardEvent(); else
16956         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16957
16958         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16959         return TRUE;
16960 }
16961
16962 void
16963 CleanupTail ()
16964 {       // remove all shelved variations
16965         int i;
16966         for(i=0; i<storedGames; i++) {
16967             if(savedDetails[i])
16968                 free(savedDetails[i]);
16969             savedDetails[i] = NULL;
16970         }
16971         for(i=framePtr; i<MAX_MOVES; i++) {
16972                 if(commentList[i]) free(commentList[i]);
16973                 commentList[i] = NULL;
16974         }
16975         framePtr = MAX_MOVES-1;
16976         storedGames = 0;
16977 }
16978
16979 void
16980 LoadVariation (int index, char *text)
16981 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16982         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16983         int level = 0, move;
16984
16985         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16986         // first find outermost bracketing variation
16987         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16988             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16989                 if(*p == '{') wait = '}'; else
16990                 if(*p == '[') wait = ']'; else
16991                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16992                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16993             }
16994             if(*p == wait) wait = NULLCHAR; // closing ]} found
16995             p++;
16996         }
16997         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16998         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16999         end[1] = NULLCHAR; // clip off comment beyond variation
17000         ToNrEvent(currentMove-1);
17001         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17002         // kludge: use ParsePV() to append variation to game
17003         move = currentMove;
17004         ParsePV(start, TRUE, TRUE);
17005         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17006         ClearPremoveHighlights();
17007         CommentPopDown();
17008         ToNrEvent(currentMove+1);
17009 }
17010