Make system open command configurable
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091         if( (len >= MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1489
1490 void
1491 InitBackEnd3 P((void))
1492 {
1493     GameMode initialMode;
1494     char buf[MSG_SIZ];
1495     int err, len;
1496
1497     InitChessProgram(&first, startedFromSetupPosition);
1498
1499     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1500         free(programVersion);
1501         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1502         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1503         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1504     }
1505
1506     if (appData.icsActive) {
1507 #ifdef WIN32
1508         /* [DM] Make a console window if needed [HGM] merged ifs */
1509         ConsoleCreate();
1510 #endif
1511         err = establish();
1512         if (err != 0)
1513           {
1514             if (*appData.icsCommPort != NULLCHAR)
1515               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1516                              appData.icsCommPort);
1517             else
1518               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1519                         appData.icsHost, appData.icsPort);
1520
1521             if( (len >= MSG_SIZ) && appData.debugMode )
1522               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1523
1524             DisplayFatalError(buf, err, 1);
1525             return;
1526         }
1527         SetICSMode();
1528         telnetISR =
1529           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1530         fromUserISR =
1531           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1532         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1533             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1534     } else if (appData.noChessProgram) {
1535         SetNCPMode();
1536     } else {
1537         SetGNUMode();
1538     }
1539
1540     if (*appData.cmailGameName != NULLCHAR) {
1541         SetCmailMode();
1542         OpenLoopback(&cmailPR);
1543         cmailISR =
1544           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1545     }
1546
1547     ThawUI();
1548     DisplayMessage("", "");
1549     if (StrCaseCmp(appData.initialMode, "") == 0) {
1550       initialMode = BeginningOfGame;
1551       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1552         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1553         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1554         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1555         ModeHighlight();
1556       }
1557     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1558       initialMode = TwoMachinesPlay;
1559     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1560       initialMode = AnalyzeFile;
1561     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1562       initialMode = AnalyzeMode;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1564       initialMode = MachinePlaysWhite;
1565     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1566       initialMode = MachinePlaysBlack;
1567     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1568       initialMode = EditGame;
1569     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1570       initialMode = EditPosition;
1571     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1572       initialMode = Training;
1573     } else {
1574       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1575       if( (len >= MSG_SIZ) && appData.debugMode )
1576         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1577
1578       DisplayFatalError(buf, 0, 2);
1579       return;
1580     }
1581
1582     if (appData.matchMode) {
1583         if(appData.tourneyFile[0]) { // start tourney from command line
1584             FILE *f;
1585             if(f = fopen(appData.tourneyFile, "r")) {
1586                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1587                 fclose(f);
1588                 appData.clockMode = TRUE;
1589                 SetGNUMode();
1590             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1591         }
1592         MatchEvent(TRUE);
1593     } else if (*appData.cmailGameName != NULLCHAR) {
1594         /* Set up cmail mode */
1595         ReloadCmailMsgEvent(TRUE);
1596     } else {
1597         /* Set up other modes */
1598         if (initialMode == AnalyzeFile) {
1599           if (*appData.loadGameFile == NULLCHAR) {
1600             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1601             return;
1602           }
1603         }
1604         if (*appData.loadGameFile != NULLCHAR) {
1605             (void) LoadGameFromFile(appData.loadGameFile,
1606                                     appData.loadGameIndex,
1607                                     appData.loadGameFile, TRUE);
1608         } else if (*appData.loadPositionFile != NULLCHAR) {
1609             (void) LoadPositionFromFile(appData.loadPositionFile,
1610                                         appData.loadPositionIndex,
1611                                         appData.loadPositionFile);
1612             /* [HGM] try to make self-starting even after FEN load */
1613             /* to allow automatic setup of fairy variants with wtm */
1614             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1615                 gameMode = BeginningOfGame;
1616                 setboardSpoiledMachineBlack = 1;
1617             }
1618             /* [HGM] loadPos: make that every new game uses the setup */
1619             /* from file as long as we do not switch variant          */
1620             if(!blackPlaysFirst) {
1621                 startedFromPositionFile = TRUE;
1622                 CopyBoard(filePosition, boards[0]);
1623             }
1624         }
1625         if (initialMode == AnalyzeMode) {
1626           if (appData.noChessProgram) {
1627             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1632             return;
1633           }
1634           AnalyzeModeEvent();
1635         } else if (initialMode == AnalyzeFile) {
1636           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1637           ShowThinkingEvent();
1638           AnalyzeFileEvent();
1639           AnalysisPeriodicEvent(1);
1640         } else if (initialMode == MachinePlaysWhite) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           MachineWhiteEvent();
1652         } else if (initialMode == MachinePlaysBlack) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineBlackEvent();
1664         } else if (initialMode == TwoMachinesPlay) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           TwoMachinesEvent();
1676         } else if (initialMode == EditGame) {
1677           EditGameEvent();
1678         } else if (initialMode == EditPosition) {
1679           EditPositionEvent();
1680         } else if (initialMode == Training) {
1681           if (*appData.loadGameFile == NULLCHAR) {
1682             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1683             return;
1684           }
1685           TrainingEvent();
1686         }
1687     }
1688 }
1689
1690 void
1691 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1692 {
1693     DisplayBook(current+1);
1694
1695     MoveHistorySet( movelist, first, last, current, pvInfoList );
1696
1697     EvalGraphSet( first, last, current, pvInfoList );
1698
1699     MakeEngineOutputTitle();
1700 }
1701
1702 /*
1703  * Establish will establish a contact to a remote host.port.
1704  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1705  *  used to talk to the host.
1706  * Returns 0 if okay, error code if not.
1707  */
1708 int
1709 establish ()
1710 {
1711     char buf[MSG_SIZ];
1712
1713     if (*appData.icsCommPort != NULLCHAR) {
1714         /* Talk to the host through a serial comm port */
1715         return OpenCommPort(appData.icsCommPort, &icsPR);
1716
1717     } else if (*appData.gateway != NULLCHAR) {
1718         if (*appData.remoteShell == NULLCHAR) {
1719             /* Use the rcmd protocol to run telnet program on a gateway host */
1720             snprintf(buf, sizeof(buf), "%s %s %s",
1721                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1722             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1723
1724         } else {
1725             /* Use the rsh program to run telnet program on a gateway host */
1726             if (*appData.remoteUser == NULLCHAR) {
1727                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1728                         appData.gateway, appData.telnetProgram,
1729                         appData.icsHost, appData.icsPort);
1730             } else {
1731                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1732                         appData.remoteShell, appData.gateway,
1733                         appData.remoteUser, appData.telnetProgram,
1734                         appData.icsHost, appData.icsPort);
1735             }
1736             return StartChildProcess(buf, "", &icsPR);
1737
1738         }
1739     } else if (appData.useTelnet) {
1740         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1741
1742     } else {
1743         /* TCP socket interface differs somewhat between
1744            Unix and NT; handle details in the front end.
1745            */
1746         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1747     }
1748 }
1749
1750 void
1751 EscapeExpand (char *p, char *q)
1752 {       // [HGM] initstring: routine to shape up string arguments
1753         while(*p++ = *q++) if(p[-1] == '\\')
1754             switch(*q++) {
1755                 case 'n': p[-1] = '\n'; break;
1756                 case 'r': p[-1] = '\r'; break;
1757                 case 't': p[-1] = '\t'; break;
1758                 case '\\': p[-1] = '\\'; break;
1759                 case 0: *p = 0; return;
1760                 default: p[-1] = q[-1]; break;
1761             }
1762 }
1763
1764 void
1765 show_bytes (FILE *fp, char *buf, int count)
1766 {
1767     while (count--) {
1768         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1769             fprintf(fp, "\\%03o", *buf & 0xff);
1770         } else {
1771             putc(*buf, fp);
1772         }
1773         buf++;
1774     }
1775     fflush(fp);
1776 }
1777
1778 /* Returns an errno value */
1779 int
1780 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1781 {
1782     char buf[8192], *p, *q, *buflim;
1783     int left, newcount, outcount;
1784
1785     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1786         *appData.gateway != NULLCHAR) {
1787         if (appData.debugMode) {
1788             fprintf(debugFP, ">ICS: ");
1789             show_bytes(debugFP, message, count);
1790             fprintf(debugFP, "\n");
1791         }
1792         return OutputToProcess(pr, message, count, outError);
1793     }
1794
1795     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1796     p = message;
1797     q = buf;
1798     left = count;
1799     newcount = 0;
1800     while (left) {
1801         if (q >= buflim) {
1802             if (appData.debugMode) {
1803                 fprintf(debugFP, ">ICS: ");
1804                 show_bytes(debugFP, buf, newcount);
1805                 fprintf(debugFP, "\n");
1806             }
1807             outcount = OutputToProcess(pr, buf, newcount, outError);
1808             if (outcount < newcount) return -1; /* to be sure */
1809             q = buf;
1810             newcount = 0;
1811         }
1812         if (*p == '\n') {
1813             *q++ = '\r';
1814             newcount++;
1815         } else if (((unsigned char) *p) == TN_IAC) {
1816             *q++ = (char) TN_IAC;
1817             newcount ++;
1818         }
1819         *q++ = *p++;
1820         newcount++;
1821         left--;
1822     }
1823     if (appData.debugMode) {
1824         fprintf(debugFP, ">ICS: ");
1825         show_bytes(debugFP, buf, newcount);
1826         fprintf(debugFP, "\n");
1827     }
1828     outcount = OutputToProcess(pr, buf, newcount, outError);
1829     if (outcount < newcount) return -1; /* to be sure */
1830     return count;
1831 }
1832
1833 void
1834 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1835 {
1836     int outError, outCount;
1837     static int gotEof = 0;
1838
1839     /* Pass data read from player on to ICS */
1840     if (count > 0) {
1841         gotEof = 0;
1842         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1843         if (outCount < count) {
1844             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1845         }
1846     } else if (count < 0) {
1847         RemoveInputSource(isr);
1848         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1849     } else if (gotEof++ > 0) {
1850         RemoveInputSource(isr);
1851         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1852     }
1853 }
1854
1855 void
1856 KeepAlive ()
1857 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1858     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1859     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1860     SendToICS("date\n");
1861     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1862 }
1863
1864 /* added routine for printf style output to ics */
1865 void
1866 ics_printf (char *format, ...)
1867 {
1868     char buffer[MSG_SIZ];
1869     va_list args;
1870
1871     va_start(args, format);
1872     vsnprintf(buffer, sizeof(buffer), format, args);
1873     buffer[sizeof(buffer)-1] = '\0';
1874     SendToICS(buffer);
1875     va_end(args);
1876 }
1877
1878 void
1879 SendToICS (char *s)
1880 {
1881     int count, outCount, outError;
1882
1883     if (icsPR == NoProc) return;
1884
1885     count = strlen(s);
1886     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892 /* This is used for sending logon scripts to the ICS. Sending
1893    without a delay causes problems when using timestamp on ICC
1894    (at least on my machine). */
1895 void
1896 SendToICSDelayed (char *s, long msdelay)
1897 {
1898     int count, outCount, outError;
1899
1900     if (icsPR == NoProc) return;
1901
1902     count = strlen(s);
1903     if (appData.debugMode) {
1904         fprintf(debugFP, ">ICS: ");
1905         show_bytes(debugFP, s, count);
1906         fprintf(debugFP, "\n");
1907     }
1908     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1909                                       msdelay);
1910     if (outCount < count) {
1911         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1912     }
1913 }
1914
1915
1916 /* Remove all highlighting escape sequences in s
1917    Also deletes any suffix starting with '('
1918    */
1919 char *
1920 StripHighlightAndTitle (char *s)
1921 {
1922     static char retbuf[MSG_SIZ];
1923     char *p = retbuf;
1924
1925     while (*s != NULLCHAR) {
1926         while (*s == '\033') {
1927             while (*s != NULLCHAR && !isalpha(*s)) s++;
1928             if (*s != NULLCHAR) s++;
1929         }
1930         while (*s != NULLCHAR && *s != '\033') {
1931             if (*s == '(' || *s == '[') {
1932                 *p = NULLCHAR;
1933                 return retbuf;
1934             }
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 /* Remove all highlighting escape sequences in s */
1943 char *
1944 StripHighlight (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             *p++ = *s++;
1956         }
1957     }
1958     *p = NULLCHAR;
1959     return retbuf;
1960 }
1961
1962 char *variantNames[] = VARIANT_NAMES;
1963 char *
1964 VariantName (VariantClass v)
1965 {
1966     return variantNames[v];
1967 }
1968
1969
1970 /* Identify a variant from the strings the chess servers use or the
1971    PGN Variant tag names we use. */
1972 VariantClass
1973 StringToVariant (char *e)
1974 {
1975     char *p;
1976     int wnum = -1;
1977     VariantClass v = VariantNormal;
1978     int i, found = FALSE;
1979     char buf[MSG_SIZ];
1980     int len;
1981
1982     if (!e) return v;
1983
1984     /* [HGM] skip over optional board-size prefixes */
1985     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1986         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1987         while( *e++ != '_');
1988     }
1989
1990     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1991         v = VariantNormal;
1992         found = TRUE;
1993     } else
1994     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1995       if (StrCaseStr(e, variantNames[i])) {
1996         v = (VariantClass) i;
1997         found = TRUE;
1998         break;
1999       }
2000     }
2001
2002     if (!found) {
2003       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2004           || StrCaseStr(e, "wild/fr")
2005           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2006         v = VariantFischeRandom;
2007       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2008                  (i = 1, p = StrCaseStr(e, "w"))) {
2009         p += i;
2010         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011         if (isdigit(*p)) {
2012           wnum = atoi(p);
2013         } else {
2014           wnum = -1;
2015         }
2016         switch (wnum) {
2017         case 0: /* FICS only, actually */
2018         case 1:
2019           /* Castling legal even if K starts on d-file */
2020           v = VariantWildCastle;
2021           break;
2022         case 2:
2023         case 3:
2024         case 4:
2025           /* Castling illegal even if K & R happen to start in
2026              normal positions. */
2027           v = VariantNoCastle;
2028           break;
2029         case 5:
2030         case 7:
2031         case 8:
2032         case 10:
2033         case 11:
2034         case 12:
2035         case 13:
2036         case 14:
2037         case 15:
2038         case 18:
2039         case 19:
2040           /* Castling legal iff K & R start in normal positions */
2041           v = VariantNormal;
2042           break;
2043         case 6:
2044         case 20:
2045         case 21:
2046           /* Special wilds for position setup; unclear what to do here */
2047           v = VariantLoadable;
2048           break;
2049         case 9:
2050           /* Bizarre ICC game */
2051           v = VariantTwoKings;
2052           break;
2053         case 16:
2054           v = VariantKriegspiel;
2055           break;
2056         case 17:
2057           v = VariantLosers;
2058           break;
2059         case 22:
2060           v = VariantFischeRandom;
2061           break;
2062         case 23:
2063           v = VariantCrazyhouse;
2064           break;
2065         case 24:
2066           v = VariantBughouse;
2067           break;
2068         case 25:
2069           v = Variant3Check;
2070           break;
2071         case 26:
2072           /* Not quite the same as FICS suicide! */
2073           v = VariantGiveaway;
2074           break;
2075         case 27:
2076           v = VariantAtomic;
2077           break;
2078         case 28:
2079           v = VariantShatranj;
2080           break;
2081
2082         /* Temporary names for future ICC types.  The name *will* change in
2083            the next xboard/WinBoard release after ICC defines it. */
2084         case 29:
2085           v = Variant29;
2086           break;
2087         case 30:
2088           v = Variant30;
2089           break;
2090         case 31:
2091           v = Variant31;
2092           break;
2093         case 32:
2094           v = Variant32;
2095           break;
2096         case 33:
2097           v = Variant33;
2098           break;
2099         case 34:
2100           v = Variant34;
2101           break;
2102         case 35:
2103           v = Variant35;
2104           break;
2105         case 36:
2106           v = Variant36;
2107           break;
2108         case 37:
2109           v = VariantShogi;
2110           break;
2111         case 38:
2112           v = VariantXiangqi;
2113           break;
2114         case 39:
2115           v = VariantCourier;
2116           break;
2117         case 40:
2118           v = VariantGothic;
2119           break;
2120         case 41:
2121           v = VariantCapablanca;
2122           break;
2123         case 42:
2124           v = VariantKnightmate;
2125           break;
2126         case 43:
2127           v = VariantFairy;
2128           break;
2129         case 44:
2130           v = VariantCylinder;
2131           break;
2132         case 45:
2133           v = VariantFalcon;
2134           break;
2135         case 46:
2136           v = VariantCapaRandom;
2137           break;
2138         case 47:
2139           v = VariantBerolina;
2140           break;
2141         case 48:
2142           v = VariantJanus;
2143           break;
2144         case 49:
2145           v = VariantSuper;
2146           break;
2147         case 50:
2148           v = VariantGreat;
2149           break;
2150         case -1:
2151           /* Found "wild" or "w" in the string but no number;
2152              must assume it's normal chess. */
2153           v = VariantNormal;
2154           break;
2155         default:
2156           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2157           if( (len >= MSG_SIZ) && appData.debugMode )
2158             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2159
2160           DisplayError(buf, 0);
2161           v = VariantUnknown;
2162           break;
2163         }
2164       }
2165     }
2166     if (appData.debugMode) {
2167       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2168               e, wnum, VariantName(v));
2169     }
2170     return v;
2171 }
2172
2173 static int leftover_start = 0, leftover_len = 0;
2174 char star_match[STAR_MATCH_N][MSG_SIZ];
2175
2176 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2177    advance *index beyond it, and set leftover_start to the new value of
2178    *index; else return FALSE.  If pattern contains the character '*', it
2179    matches any sequence of characters not containing '\r', '\n', or the
2180    character following the '*' (if any), and the matched sequence(s) are
2181    copied into star_match.
2182    */
2183 int
2184 looking_at ( char *buf, int *index, char *pattern)
2185 {
2186     char *bufp = &buf[*index], *patternp = pattern;
2187     int star_count = 0;
2188     char *matchp = star_match[0];
2189
2190     for (;;) {
2191         if (*patternp == NULLCHAR) {
2192             *index = leftover_start = bufp - buf;
2193             *matchp = NULLCHAR;
2194             return TRUE;
2195         }
2196         if (*bufp == NULLCHAR) return FALSE;
2197         if (*patternp == '*') {
2198             if (*bufp == *(patternp + 1)) {
2199                 *matchp = NULLCHAR;
2200                 matchp = star_match[++star_count];
2201                 patternp += 2;
2202                 bufp++;
2203                 continue;
2204             } else if (*bufp == '\n' || *bufp == '\r') {
2205                 patternp++;
2206                 if (*patternp == NULLCHAR)
2207                   continue;
2208                 else
2209                   return FALSE;
2210             } else {
2211                 *matchp++ = *bufp++;
2212                 continue;
2213             }
2214         }
2215         if (*patternp != *bufp) return FALSE;
2216         patternp++;
2217         bufp++;
2218     }
2219 }
2220
2221 void
2222 SendToPlayer (char *data, int length)
2223 {
2224     int error, outCount;
2225     outCount = OutputToProcess(NoProc, data, length, &error);
2226     if (outCount < length) {
2227         DisplayFatalError(_("Error writing to display"), error, 1);
2228     }
2229 }
2230
2231 void
2232 PackHolding (char packed[], char *holding)
2233 {
2234     char *p = holding;
2235     char *q = packed;
2236     int runlength = 0;
2237     int curr = 9999;
2238     do {
2239         if (*p == curr) {
2240             runlength++;
2241         } else {
2242             switch (runlength) {
2243               case 0:
2244                 break;
2245               case 1:
2246                 *q++ = curr;
2247                 break;
2248               case 2:
2249                 *q++ = curr;
2250                 *q++ = curr;
2251                 break;
2252               default:
2253                 sprintf(q, "%d", runlength);
2254                 while (*q) q++;
2255                 *q++ = curr;
2256                 break;
2257             }
2258             runlength = 1;
2259             curr = *p;
2260         }
2261     } while (*p++);
2262     *q = NULLCHAR;
2263 }
2264
2265 /* Telnet protocol requests from the front end */
2266 void
2267 TelnetRequest (unsigned char ddww, unsigned char option)
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho ()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch (Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd (int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot (int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd (int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine (char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph ()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int
2615 SeekGraphClick (ClickType click, int x, int y, int moving)
2616 {
2617     static int lastDown = 0, displayed = 0, lastSecond;
2618     if(y < 0) return FALSE;
2619     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2620         if(click == Release || moving) return FALSE;
2621         nrOfSeekAds = 0;
2622         soughtPending = TRUE;
2623         SendToICS(ics_prefix);
2624         SendToICS("sought\n"); // should this be "sought all"?
2625     } else { // issue challenge based on clicked ad
2626         int dist = 10000; int i, closest = 0, second = 0;
2627         for(i=0; i<nrOfSeekAds; i++) {
2628             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2629             if(d < dist) { dist = d; closest = i; }
2630             second += (d - zList[i] < 120); // count in-range ads
2631             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2632         }
2633         if(dist < 120) {
2634             char buf[MSG_SIZ];
2635             second = (second > 1);
2636             if(displayed != closest || second != lastSecond) {
2637                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2638                 lastSecond = second; displayed = closest;
2639             }
2640             if(click == Press) {
2641                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2642                 lastDown = closest;
2643                 return TRUE;
2644             } // on press 'hit', only show info
2645             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2646             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2647             SendToICS(ics_prefix);
2648             SendToICS(buf);
2649             return TRUE; // let incoming board of started game pop down the graph
2650         } else if(click == Release) { // release 'miss' is ignored
2651             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2652             if(moving == 2) { // right up-click
2653                 nrOfSeekAds = 0; // refresh graph
2654                 soughtPending = TRUE;
2655                 SendToICS(ics_prefix);
2656                 SendToICS("sought\n"); // should this be "sought all"?
2657             }
2658             return TRUE;
2659         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2660         // press miss or release hit 'pop down' seek graph
2661         seekGraphUp = FALSE;
2662         DrawPosition(TRUE, NULL);
2663     }
2664     return TRUE;
2665 }
2666
2667 void
2668 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2669 {
2670 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2671 #define STARTED_NONE 0
2672 #define STARTED_MOVES 1
2673 #define STARTED_BOARD 2
2674 #define STARTED_OBSERVE 3
2675 #define STARTED_HOLDINGS 4
2676 #define STARTED_CHATTER 5
2677 #define STARTED_COMMENT 6
2678 #define STARTED_MOVES_NOHIDE 7
2679
2680     static int started = STARTED_NONE;
2681     static char parse[20000];
2682     static int parse_pos = 0;
2683     static char buf[BUF_SIZE + 1];
2684     static int firstTime = TRUE, intfSet = FALSE;
2685     static ColorClass prevColor = ColorNormal;
2686     static int savingComment = FALSE;
2687     static int cmatch = 0; // continuation sequence match
2688     char *bp;
2689     char str[MSG_SIZ];
2690     int i, oldi;
2691     int buf_len;
2692     int next_out;
2693     int tkind;
2694     int backup;    /* [DM] For zippy color lines */
2695     char *p;
2696     char talker[MSG_SIZ]; // [HGM] chat
2697     int channel;
2698
2699     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2700
2701     if (appData.debugMode) {
2702       if (!error) {
2703         fprintf(debugFP, "<ICS: ");
2704         show_bytes(debugFP, data, count);
2705         fprintf(debugFP, "\n");
2706       }
2707     }
2708
2709     if (appData.debugMode) { int f = forwardMostMove;
2710         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2711                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2712                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2713     }
2714     if (count > 0) {
2715         /* If last read ended with a partial line that we couldn't parse,
2716            prepend it to the new read and try again. */
2717         if (leftover_len > 0) {
2718             for (i=0; i<leftover_len; i++)
2719               buf[i] = buf[leftover_start + i];
2720         }
2721
2722     /* copy new characters into the buffer */
2723     bp = buf + leftover_len;
2724     buf_len=leftover_len;
2725     for (i=0; i<count; i++)
2726     {
2727         // ignore these
2728         if (data[i] == '\r')
2729             continue;
2730
2731         // join lines split by ICS?
2732         if (!appData.noJoin)
2733         {
2734             /*
2735                 Joining just consists of finding matches against the
2736                 continuation sequence, and discarding that sequence
2737                 if found instead of copying it.  So, until a match
2738                 fails, there's nothing to do since it might be the
2739                 complete sequence, and thus, something we don't want
2740                 copied.
2741             */
2742             if (data[i] == cont_seq[cmatch])
2743             {
2744                 cmatch++;
2745                 if (cmatch == strlen(cont_seq))
2746                 {
2747                     cmatch = 0; // complete match.  just reset the counter
2748
2749                     /*
2750                         it's possible for the ICS to not include the space
2751                         at the end of the last word, making our [correct]
2752                         join operation fuse two separate words.  the server
2753                         does this when the space occurs at the width setting.
2754                     */
2755                     if (!buf_len || buf[buf_len-1] != ' ')
2756                     {
2757                         *bp++ = ' ';
2758                         buf_len++;
2759                     }
2760                 }
2761                 continue;
2762             }
2763             else if (cmatch)
2764             {
2765                 /*
2766                     match failed, so we have to copy what matched before
2767                     falling through and copying this character.  In reality,
2768                     this will only ever be just the newline character, but
2769                     it doesn't hurt to be precise.
2770                 */
2771                 strncpy(bp, cont_seq, cmatch);
2772                 bp += cmatch;
2773                 buf_len += cmatch;
2774                 cmatch = 0;
2775             }
2776         }
2777
2778         // copy this char
2779         *bp++ = data[i];
2780         buf_len++;
2781     }
2782
2783         buf[buf_len] = NULLCHAR;
2784 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2785         next_out = 0;
2786         leftover_start = 0;
2787
2788         i = 0;
2789         while (i < buf_len) {
2790             /* Deal with part of the TELNET option negotiation
2791                protocol.  We refuse to do anything beyond the
2792                defaults, except that we allow the WILL ECHO option,
2793                which ICS uses to turn off password echoing when we are
2794                directly connected to it.  We reject this option
2795                if localLineEditing mode is on (always on in xboard)
2796                and we are talking to port 23, which might be a real
2797                telnet server that will try to keep WILL ECHO on permanently.
2798              */
2799             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2800                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2801                 unsigned char option;
2802                 oldi = i;
2803                 switch ((unsigned char) buf[++i]) {
2804                   case TN_WILL:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<WILL ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       case TN_ECHO:
2809                         if (appData.debugMode)
2810                           fprintf(debugFP, "ECHO ");
2811                         /* Reply only if this is a change, according
2812                            to the protocol rules. */
2813                         if (remoteEchoOption) break;
2814                         if (appData.localLineEditing &&
2815                             atoi(appData.icsPort) == TN_PORT) {
2816                             TelnetRequest(TN_DONT, TN_ECHO);
2817                         } else {
2818                             EchoOff();
2819                             TelnetRequest(TN_DO, TN_ECHO);
2820                             remoteEchoOption = TRUE;
2821                         }
2822                         break;
2823                       default:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "%d ", option);
2826                         /* Whatever this is, we don't want it. */
2827                         TelnetRequest(TN_DONT, option);
2828                         break;
2829                     }
2830                     break;
2831                   case TN_WONT:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<WONT ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       case TN_ECHO:
2836                         if (appData.debugMode)
2837                           fprintf(debugFP, "ECHO ");
2838                         /* Reply only if this is a change, according
2839                            to the protocol rules. */
2840                         if (!remoteEchoOption) break;
2841                         EchoOn();
2842                         TelnetRequest(TN_DONT, TN_ECHO);
2843                         remoteEchoOption = FALSE;
2844                         break;
2845                       default:
2846                         if (appData.debugMode)
2847                           fprintf(debugFP, "%d ", (unsigned char) option);
2848                         /* Whatever this is, it must already be turned
2849                            off, because we never agree to turn on
2850                            anything non-default, so according to the
2851                            protocol rules, we don't reply. */
2852                         break;
2853                     }
2854                     break;
2855                   case TN_DO:
2856                     if (appData.debugMode)
2857                       fprintf(debugFP, "\n<DO ");
2858                     switch (option = (unsigned char) buf[++i]) {
2859                       default:
2860                         /* Whatever this is, we refuse to do it. */
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", option);
2863                         TelnetRequest(TN_WONT, option);
2864                         break;
2865                     }
2866                     break;
2867                   case TN_DONT:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<DONT ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       default:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "%d ", option);
2874                         /* Whatever this is, we are already not doing
2875                            it, because we never agree to do anything
2876                            non-default, so according to the protocol
2877                            rules, we don't reply. */
2878                         break;
2879                     }
2880                     break;
2881                   case TN_IAC:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<IAC ");
2884                     /* Doubled IAC; pass it through */
2885                     i--;
2886                     break;
2887                   default:
2888                     if (appData.debugMode)
2889                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2890                     /* Drop all other telnet commands on the floor */
2891                     break;
2892                 }
2893                 if (oldi > next_out)
2894                   SendToPlayer(&buf[next_out], oldi - next_out);
2895                 if (++i > next_out)
2896                   next_out = i;
2897                 continue;
2898             }
2899
2900             /* OK, this at least will *usually* work */
2901             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2902                 loggedOn = TRUE;
2903             }
2904
2905             if (loggedOn && !intfSet) {
2906                 if (ics_type == ICS_ICC) {
2907                   snprintf(str, MSG_SIZ,
2908                           "/set-quietly interface %s\n/set-quietly style 12\n",
2909                           programVersion);
2910                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2912                 } else if (ics_type == ICS_CHESSNET) {
2913                   snprintf(str, MSG_SIZ, "/style 12\n");
2914                 } else {
2915                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2916                   strcat(str, programVersion);
2917                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2918                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2920 #ifdef WIN32
2921                   strcat(str, "$iset nohighlight 1\n");
2922 #endif
2923                   strcat(str, "$iset lock 1\n$style 12\n");
2924                 }
2925                 SendToICS(str);
2926                 NotifyFrontendLogin();
2927                 intfSet = TRUE;
2928             }
2929
2930             if (started == STARTED_COMMENT) {
2931                 /* Accumulate characters in comment */
2932                 parse[parse_pos++] = buf[i];
2933                 if (buf[i] == '\n') {
2934                     parse[parse_pos] = NULLCHAR;
2935                     if(chattingPartner>=0) {
2936                         char mess[MSG_SIZ];
2937                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2938                         OutputChatMessage(chattingPartner, mess);
2939                         chattingPartner = -1;
2940                         next_out = i+1; // [HGM] suppress printing in ICS window
2941                     } else
2942                     if(!suppressKibitz) // [HGM] kibitz
2943                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2944                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2945                         int nrDigit = 0, nrAlph = 0, j;
2946                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2947                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2948                         parse[parse_pos] = NULLCHAR;
2949                         // try to be smart: if it does not look like search info, it should go to
2950                         // ICS interaction window after all, not to engine-output window.
2951                         for(j=0; j<parse_pos; j++) { // count letters and digits
2952                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2953                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2954                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2955                         }
2956                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2957                             int depth=0; float score;
2958                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2959                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2960                                 pvInfoList[forwardMostMove-1].depth = depth;
2961                                 pvInfoList[forwardMostMove-1].score = 100*score;
2962                             }
2963                             OutputKibitz(suppressKibitz, parse);
2964                         } else {
2965                             char tmp[MSG_SIZ];
2966                             if(gameMode == IcsObserving) // restore original ICS messages
2967                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2968                             else
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
3008                 ics_prefix = "$";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3103                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3104                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3105                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3106                         suppressKibitz = TRUE;
3107                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                         next_out = i;
3109                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3110                                 && (gameMode == IcsPlayingWhite)) ||
3111                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3112                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3113                             started = STARTED_CHATTER; // own kibitz we simply discard
3114                         else {
3115                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3116                             parse_pos = 0; parse[0] = NULLCHAR;
3117                             savingComment = TRUE;
3118                             suppressKibitz = gameMode != IcsObserving ? 2 :
3119                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3120                         }
3121                         continue;
3122                 } else
3123                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3124                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3125                          && atoi(star_match[0])) {
3126                     // suppress the acknowledgements of our own autoKibitz
3127                     char *p;
3128                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3129                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3130                     SendToPlayer(star_match[0], strlen(star_match[0]));
3131                     if(looking_at(buf, &i, "*% ")) // eat prompt
3132                         suppressKibitz = FALSE;
3133                     next_out = i;
3134                     continue;
3135                 }
3136             } // [HGM] kibitz: end of patch
3137
3138             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3139
3140             // [HGM] chat: intercept tells by users for which we have an open chat window
3141             channel = -1;
3142             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3143                                            looking_at(buf, &i, "* whispers:") ||
3144                                            looking_at(buf, &i, "* kibitzes:") ||
3145                                            looking_at(buf, &i, "* shouts:") ||
3146                                            looking_at(buf, &i, "* c-shouts:") ||
3147                                            looking_at(buf, &i, "--> * ") ||
3148                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3150                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3151                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3152                 int p;
3153                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3154                 chattingPartner = -1;
3155
3156                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3157                 for(p=0; p<MAX_CHAT; p++) {
3158                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3159                     talker[0] = '['; strcat(talker, "] ");
3160                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3161                     chattingPartner = p; break;
3162                     }
3163                 } else
3164                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3165                 for(p=0; p<MAX_CHAT; p++) {
3166                     if(!strcmp("kibitzes", chatPartner[p])) {
3167                         talker[0] = '['; strcat(talker, "] ");
3168                         chattingPartner = p; break;
3169                     }
3170                 } else
3171                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(!strcmp("whispers", chatPartner[p])) {
3174                         talker[0] = '['; strcat(talker, "] ");
3175                         chattingPartner = p; break;
3176                     }
3177                 } else
3178                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3179                   if(buf[i-8] == '-' && buf[i-3] == 't')
3180                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3181                     if(!strcmp("c-shouts", chatPartner[p])) {
3182                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3183                         chattingPartner = p; break;
3184                     }
3185                   }
3186                   if(chattingPartner < 0)
3187                   for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("shouts", chatPartner[p])) {
3189                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3190                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3191                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3192                         chattingPartner = p; break;
3193                     }
3194                   }
3195                 }
3196                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3197                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3198                     talker[0] = 0; Colorize(ColorTell, FALSE);
3199                     chattingPartner = p; break;
3200                 }
3201                 if(chattingPartner<0) i = oldi; else {
3202                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3203                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3204                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3205                     started = STARTED_COMMENT;
3206                     parse_pos = 0; parse[0] = NULLCHAR;
3207                     savingComment = 3 + chattingPartner; // counts as TRUE
3208                     suppressKibitz = TRUE;
3209                     continue;
3210                 }
3211             } // [HGM] chat: end of patch
3212
3213           backup = i;
3214             if (appData.zippyTalk || appData.zippyPlay) {
3215                 /* [DM] Backup address for color zippy lines */
3216 #if ZIPPY
3217                if (loggedOn == TRUE)
3218                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3219                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3220 #endif
3221             } // [DM] 'else { ' deleted
3222                 if (
3223                     /* Regular tells and says */
3224                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3225                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3226                     looking_at(buf, &i, "* says: ") ||
3227                     /* Don't color "message" or "messages" output */
3228                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3229                     looking_at(buf, &i, "*. * at *:*: ") ||
3230                     looking_at(buf, &i, "--* (*:*): ") ||
3231                     /* Message notifications (same color as tells) */
3232                     looking_at(buf, &i, "* has left a message ") ||
3233                     looking_at(buf, &i, "* just sent you a message:\n") ||
3234                     /* Whispers and kibitzes */
3235                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3236                     looking_at(buf, &i, "* kibitzes: ") ||
3237                     /* Channel tells */
3238                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3239
3240                   if (tkind == 1 && strchr(star_match[0], ':')) {
3241                       /* Avoid "tells you:" spoofs in channels */
3242                      tkind = 3;
3243                   }
3244                   if (star_match[0][0] == NULLCHAR ||
3245                       strchr(star_match[0], ' ') ||
3246                       (tkind == 3 && strchr(star_match[1], ' '))) {
3247                     /* Reject bogus matches */
3248                     i = oldi;
3249                   } else {
3250                     if (appData.colorize) {
3251                       if (oldi > next_out) {
3252                         SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = oldi;
3254                       }
3255                       switch (tkind) {
3256                       case 1:
3257                         Colorize(ColorTell, FALSE);
3258                         curColor = ColorTell;
3259                         break;
3260                       case 2:
3261                         Colorize(ColorKibitz, FALSE);
3262                         curColor = ColorKibitz;
3263                         break;
3264                       case 3:
3265                         p = strrchr(star_match[1], '(');
3266                         if (p == NULL) {
3267                           p = star_match[1];
3268                         } else {
3269                           p++;
3270                         }
3271                         if (atoi(p) == 1) {
3272                           Colorize(ColorChannel1, FALSE);
3273                           curColor = ColorChannel1;
3274                         } else {
3275                           Colorize(ColorChannel, FALSE);
3276                           curColor = ColorChannel;
3277                         }
3278                         break;
3279                       case 5:
3280                         curColor = ColorNormal;
3281                         break;
3282                       }
3283                     }
3284                     if (started == STARTED_NONE && appData.autoComment &&
3285                         (gameMode == IcsObserving ||
3286                          gameMode == IcsPlayingWhite ||
3287                          gameMode == IcsPlayingBlack)) {
3288                       parse_pos = i - oldi;
3289                       memcpy(parse, &buf[oldi], parse_pos);
3290                       parse[parse_pos] = NULLCHAR;
3291                       started = STARTED_COMMENT;
3292                       savingComment = TRUE;
3293                     } else {
3294                       started = STARTED_CHATTER;
3295                       savingComment = FALSE;
3296                     }
3297                     loggedOn = TRUE;
3298                     continue;
3299                   }
3300                 }
3301
3302                 if (looking_at(buf, &i, "* s-shouts: ") ||
3303                     looking_at(buf, &i, "* c-shouts: ")) {
3304                     if (appData.colorize) {
3305                         if (oldi > next_out) {
3306                             SendToPlayer(&buf[next_out], oldi - next_out);
3307                             next_out = oldi;
3308                         }
3309                         Colorize(ColorSShout, FALSE);
3310                         curColor = ColorSShout;
3311                     }
3312                     loggedOn = TRUE;
3313                     started = STARTED_CHATTER;
3314                     continue;
3315                 }
3316
3317                 if (looking_at(buf, &i, "--->")) {
3318                     loggedOn = TRUE;
3319                     continue;
3320                 }
3321
3322                 if (looking_at(buf, &i, "* shouts: ") ||
3323                     looking_at(buf, &i, "--> ")) {
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorShout, FALSE);
3330                         curColor = ColorShout;
3331                     }
3332                     loggedOn = TRUE;
3333                     started = STARTED_CHATTER;
3334                     continue;
3335                 }
3336
3337                 if (looking_at( buf, &i, "Challenge:")) {
3338                     if (appData.colorize) {
3339                         if (oldi > next_out) {
3340                             SendToPlayer(&buf[next_out], oldi - next_out);
3341                             next_out = oldi;
3342                         }
3343                         Colorize(ColorChallenge, FALSE);
3344                         curColor = ColorChallenge;
3345                     }
3346                     loggedOn = TRUE;
3347                     continue;
3348                 }
3349
3350                 if (looking_at(buf, &i, "* offers you") ||
3351                     looking_at(buf, &i, "* offers to be") ||
3352                     looking_at(buf, &i, "* would like to") ||
3353                     looking_at(buf, &i, "* requests to") ||
3354                     looking_at(buf, &i, "Your opponent offers") ||
3355                     looking_at(buf, &i, "Your opponent requests")) {
3356
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorRequest, FALSE);
3363                         curColor = ColorRequest;
3364                     }
3365                     continue;
3366                 }
3367
3368                 if (looking_at(buf, &i, "* (*) seeking")) {
3369                     if (appData.colorize) {
3370                         if (oldi > next_out) {
3371                             SendToPlayer(&buf[next_out], oldi - next_out);
3372                             next_out = oldi;
3373                         }
3374                         Colorize(ColorSeek, FALSE);
3375                         curColor = ColorSeek;
3376                     }
3377                     continue;
3378             }
3379
3380           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3381
3382             if (looking_at(buf, &i, "\\   ")) {
3383                 if (prevColor != ColorNormal) {
3384                     if (oldi > next_out) {
3385                         SendToPlayer(&buf[next_out], oldi - next_out);
3386                         next_out = oldi;
3387                     }
3388                     Colorize(prevColor, TRUE);
3389                     curColor = prevColor;
3390                 }
3391                 if (savingComment) {
3392                     parse_pos = i - oldi;
3393                     memcpy(parse, &buf[oldi], parse_pos);
3394                     parse[parse_pos] = NULLCHAR;
3395                     started = STARTED_COMMENT;
3396                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3397                         chattingPartner = savingComment - 3; // kludge to remember the box
3398                 } else {
3399                     started = STARTED_CHATTER;
3400                 }
3401                 continue;
3402             }
3403
3404             if (looking_at(buf, &i, "Black Strength :") ||
3405                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3406                 looking_at(buf, &i, "<10>") ||
3407                 looking_at(buf, &i, "#@#")) {
3408                 /* Wrong board style */
3409                 loggedOn = TRUE;
3410                 SendToICS(ics_prefix);
3411                 SendToICS("set style 12\n");
3412                 SendToICS(ics_prefix);
3413                 SendToICS("refresh\n");
3414                 continue;
3415             }
3416
3417             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3418                 ICSInitScript();
3419                 have_sent_ICS_logon = 1;
3420                 continue;
3421             }
3422
3423             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3424                 (looking_at(buf, &i, "\n<12> ") ||
3425                  looking_at(buf, &i, "<12> "))) {
3426                 loggedOn = TRUE;
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_BOARD;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3437                 looking_at(buf, &i, "<b1> ")) {
3438                 if (oldi > next_out) {
3439                     SendToPlayer(&buf[next_out], oldi - next_out);
3440                 }
3441                 next_out = i;
3442                 started = STARTED_HOLDINGS;
3443                 parse_pos = 0;
3444                 continue;
3445             }
3446
3447             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3448                 loggedOn = TRUE;
3449                 /* Header for a move list -- first line */
3450
3451                 switch (ics_getting_history) {
3452                   case H_FALSE:
3453                     switch (gameMode) {
3454                       case IcsIdle:
3455                       case BeginningOfGame:
3456                         /* User typed "moves" or "oldmoves" while we
3457                            were idle.  Pretend we asked for these
3458                            moves and soak them up so user can step
3459                            through them and/or save them.
3460                            */
3461                         Reset(FALSE, TRUE);
3462                         gameMode = IcsObserving;
3463                         ModeHighlight();
3464                         ics_gamenum = -1;
3465                         ics_getting_history = H_GOT_UNREQ_HEADER;
3466                         break;
3467                       case EditGame: /*?*/
3468                       case EditPosition: /*?*/
3469                         /* Should above feature work in these modes too? */
3470                         /* For now it doesn't */
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                       default:
3474                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3475                         break;
3476                     }
3477                     break;
3478                   case H_REQUESTED:
3479                     /* Is this the right one? */
3480                     if (gameInfo.white && gameInfo.black &&
3481                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3482                         strcmp(gameInfo.black, star_match[2]) == 0) {
3483                         /* All is well */
3484                         ics_getting_history = H_GOT_REQ_HEADER;
3485                     }
3486                     break;
3487                   case H_GOT_REQ_HEADER:
3488                   case H_GOT_UNREQ_HEADER:
3489                   case H_GOT_UNWANTED_HEADER:
3490                   case H_GETTING_MOVES:
3491                     /* Should not happen */
3492                     DisplayError(_("Error gathering move list: two headers"), 0);
3493                     ics_getting_history = H_FALSE;
3494                     break;
3495                 }
3496
3497                 /* Save player ratings into gameInfo if needed */
3498                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3499                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3500                     (gameInfo.whiteRating == -1 ||
3501                      gameInfo.blackRating == -1)) {
3502
3503                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3504                     gameInfo.blackRating = string_to_rating(star_match[3]);
3505                     if (appData.debugMode)
3506                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3507                               gameInfo.whiteRating, gameInfo.blackRating);
3508                 }
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i,
3513               "* * match, initial time: * minute*, increment: * second")) {
3514                 /* Header for a move list -- second line */
3515                 /* Initial board will follow if this is a wild game */
3516                 if (gameInfo.event != NULL) free(gameInfo.event);
3517                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3518                 gameInfo.event = StrSave(str);
3519                 /* [HGM] we switched variant. Translate boards if needed. */
3520                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3521                 continue;
3522             }
3523
3524             if (looking_at(buf, &i, "Move  ")) {
3525                 /* Beginning of a move list */
3526                 switch (ics_getting_history) {
3527                   case H_FALSE:
3528                     /* Normally should not happen */
3529                     /* Maybe user hit reset while we were parsing */
3530                     break;
3531                   case H_REQUESTED:
3532                     /* Happens if we are ignoring a move list that is not
3533                      * the one we just requested.  Common if the user
3534                      * tries to observe two games without turning off
3535                      * getMoveList */
3536                     break;
3537                   case H_GETTING_MOVES:
3538                     /* Should not happen */
3539                     DisplayError(_("Error gathering move list: nested"), 0);
3540                     ics_getting_history = H_FALSE;
3541                     break;
3542                   case H_GOT_REQ_HEADER:
3543                     ics_getting_history = H_GETTING_MOVES;
3544                     started = STARTED_MOVES;
3545                     parse_pos = 0;
3546                     if (oldi > next_out) {
3547                         SendToPlayer(&buf[next_out], oldi - next_out);
3548                     }
3549                     break;
3550                   case H_GOT_UNREQ_HEADER:
3551                     ics_getting_history = H_GETTING_MOVES;
3552                     started = STARTED_MOVES_NOHIDE;
3553                     parse_pos = 0;
3554                     break;
3555                   case H_GOT_UNWANTED_HEADER:
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "% ") ||
3563                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3564                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3565                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3566                     soughtPending = FALSE;
3567                     seekGraphUp = TRUE;
3568                     DrawSeekGraph();
3569                 }
3570                 if(suppressKibitz) next_out = i;
3571                 savingComment = FALSE;
3572                 suppressKibitz = 0;
3573                 switch (started) {
3574                   case STARTED_MOVES:
3575                   case STARTED_MOVES_NOHIDE:
3576                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3577                     parse[parse_pos + i - oldi] = NULLCHAR;
3578                     ParseGameHistory(parse);
3579 #if ZIPPY
3580                     if (appData.zippyPlay && first.initDone) {
3581                         FeedMovesToProgram(&first, forwardMostMove);
3582                         if (gameMode == IcsPlayingWhite) {
3583                             if (WhiteOnMove(forwardMostMove)) {
3584                                 if (first.sendTime) {
3585                                   if (first.useColors) {
3586                                     SendToProgram("black\n", &first);
3587                                   }
3588                                   SendTimeRemaining(&first, TRUE);
3589                                 }
3590                                 if (first.useColors) {
3591                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3592                                 }
3593                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3594                                 first.maybeThinking = TRUE;
3595                             } else {
3596                                 if (first.usePlayother) {
3597                                   if (first.sendTime) {
3598                                     SendTimeRemaining(&first, TRUE);
3599                                   }
3600                                   SendToProgram("playother\n", &first);
3601                                   firstMove = FALSE;
3602                                 } else {
3603                                   firstMove = TRUE;
3604                                 }
3605                             }
3606                         } else if (gameMode == IcsPlayingBlack) {
3607                             if (!WhiteOnMove(forwardMostMove)) {
3608                                 if (first.sendTime) {
3609                                   if (first.useColors) {
3610                                     SendToProgram("white\n", &first);
3611                                   }
3612                                   SendTimeRemaining(&first, FALSE);
3613                                 }
3614                                 if (first.useColors) {
3615                                   SendToProgram("black\n", &first);
3616                                 }
3617                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3618                                 first.maybeThinking = TRUE;
3619                             } else {
3620                                 if (first.usePlayother) {
3621                                   if (first.sendTime) {
3622                                     SendTimeRemaining(&first, FALSE);
3623                                   }
3624                                   SendToProgram("playother\n", &first);
3625                                   firstMove = FALSE;
3626                                 } else {
3627                                   firstMove = TRUE;
3628                                 }
3629                             }
3630                         }
3631                     }
3632 #endif
3633                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3634                         /* Moves came from oldmoves or moves command
3635                            while we weren't doing anything else.
3636                            */
3637                         currentMove = forwardMostMove;
3638                         ClearHighlights();/*!!could figure this out*/
3639                         flipView = appData.flipView;
3640                         DrawPosition(TRUE, boards[currentMove]);
3641                         DisplayBothClocks();
3642                         snprintf(str, MSG_SIZ, "%s %s %s",
3643                                 gameInfo.white, _("vs."),  gameInfo.black);
3644                         DisplayTitle(str);
3645                         gameMode = IcsIdle;
3646                     } else {
3647                         /* Moves were history of an active game */
3648                         if (gameInfo.resultDetails != NULL) {
3649                             free(gameInfo.resultDetails);
3650                             gameInfo.resultDetails = NULL;
3651                         }
3652                     }
3653                     HistorySet(parseList, backwardMostMove,
3654                                forwardMostMove, currentMove-1);
3655                     DisplayMove(currentMove - 1);
3656                     if (started == STARTED_MOVES) next_out = i;
3657                     started = STARTED_NONE;
3658                     ics_getting_history = H_FALSE;
3659                     break;
3660
3661                   case STARTED_OBSERVE:
3662                     started = STARTED_NONE;
3663                     SendToICS(ics_prefix);
3664                     SendToICS("refresh\n");
3665                     break;
3666
3667                   default:
3668                     break;
3669                 }
3670                 if(bookHit) { // [HGM] book: simulate book reply
3671                     static char bookMove[MSG_SIZ]; // a bit generous?
3672
3673                     programStats.nodes = programStats.depth = programStats.time =
3674                     programStats.score = programStats.got_only_move = 0;
3675                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3676
3677                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3678                     strcat(bookMove, bookHit);
3679                     HandleMachineMove(bookMove, &first);
3680                 }
3681                 continue;
3682             }
3683
3684             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3685                  started == STARTED_HOLDINGS ||
3686                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3687                 /* Accumulate characters in move list or board */
3688                 parse[parse_pos++] = buf[i];
3689             }
3690
3691             /* Start of game messages.  Mostly we detect start of game
3692                when the first board image arrives.  On some versions
3693                of the ICS, though, we need to do a "refresh" after starting
3694                to observe in order to get the current board right away. */
3695             if (looking_at(buf, &i, "Adding game * to observation list")) {
3696                 started = STARTED_OBSERVE;
3697                 continue;
3698             }
3699
3700             /* Handle auto-observe */
3701             if (appData.autoObserve &&
3702                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3703                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3704                 char *player;
3705                 /* Choose the player that was highlighted, if any. */
3706                 if (star_match[0][0] == '\033' ||
3707                     star_match[1][0] != '\033') {
3708                     player = star_match[0];
3709                 } else {
3710                     player = star_match[2];
3711                 }
3712                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3713                         ics_prefix, StripHighlightAndTitle(player));
3714                 SendToICS(str);
3715
3716                 /* Save ratings from notify string */
3717                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3718                 player1Rating = string_to_rating(star_match[1]);
3719                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3720                 player2Rating = string_to_rating(star_match[3]);
3721
3722                 if (appData.debugMode)
3723                   fprintf(debugFP,
3724                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3725                           player1Name, player1Rating,
3726                           player2Name, player2Rating);
3727
3728                 continue;
3729             }
3730
3731             /* Deal with automatic examine mode after a game,
3732                and with IcsObserving -> IcsExamining transition */
3733             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3734                 looking_at(buf, &i, "has made you an examiner of game *")) {
3735
3736                 int gamenum = atoi(star_match[0]);
3737                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3738                     gamenum == ics_gamenum) {
3739                     /* We were already playing or observing this game;
3740                        no need to refetch history */
3741                     gameMode = IcsExamining;
3742                     if (pausing) {
3743                         pauseExamForwardMostMove = forwardMostMove;
3744                     } else if (currentMove < forwardMostMove) {
3745                         ForwardInner(forwardMostMove);
3746                     }
3747                 } else {
3748                     /* I don't think this case really can happen */
3749                     SendToICS(ics_prefix);
3750                     SendToICS("refresh\n");
3751                 }
3752                 continue;
3753             }
3754
3755             /* Error messages */
3756 //          if (ics_user_moved) {
3757             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3758                 if (looking_at(buf, &i, "Illegal move") ||
3759                     looking_at(buf, &i, "Not a legal move") ||
3760                     looking_at(buf, &i, "Your king is in check") ||
3761                     looking_at(buf, &i, "It isn't your turn") ||
3762                     looking_at(buf, &i, "It is not your move")) {
3763                     /* Illegal move */
3764                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3765                         currentMove = forwardMostMove-1;
3766                         DisplayMove(currentMove - 1); /* before DMError */
3767                         DrawPosition(FALSE, boards[currentMove]);
3768                         SwitchClocks(forwardMostMove-1); // [HGM] race
3769                         DisplayBothClocks();
3770                     }
3771                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3772                     ics_user_moved = 0;
3773                     continue;
3774                 }
3775             }
3776
3777             if (looking_at(buf, &i, "still have time") ||
3778                 looking_at(buf, &i, "not out of time") ||
3779                 looking_at(buf, &i, "either player is out of time") ||
3780                 looking_at(buf, &i, "has timeseal; checking")) {
3781                 /* We must have called his flag a little too soon */
3782                 whiteFlag = blackFlag = FALSE;
3783                 continue;
3784             }
3785
3786             if (looking_at(buf, &i, "added * seconds to") ||
3787                 looking_at(buf, &i, "seconds were added to")) {
3788                 /* Update the clocks */
3789                 SendToICS(ics_prefix);
3790                 SendToICS("refresh\n");
3791                 continue;
3792             }
3793
3794             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3795                 ics_clock_paused = TRUE;
3796                 StopClocks();
3797                 continue;
3798             }
3799
3800             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3801                 ics_clock_paused = FALSE;
3802                 StartClocks();
3803                 continue;
3804             }
3805
3806             /* Grab player ratings from the Creating: message.
3807                Note we have to check for the special case when
3808                the ICS inserts things like [white] or [black]. */
3809             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3810                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3811                 /* star_matches:
3812                    0    player 1 name (not necessarily white)
3813                    1    player 1 rating
3814                    2    empty, white, or black (IGNORED)
3815                    3    player 2 name (not necessarily black)
3816                    4    player 2 rating
3817
3818                    The names/ratings are sorted out when the game
3819                    actually starts (below).
3820                 */
3821                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3822                 player1Rating = string_to_rating(star_match[1]);
3823                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3824                 player2Rating = string_to_rating(star_match[4]);
3825
3826                 if (appData.debugMode)
3827                   fprintf(debugFP,
3828                           "Ratings from 'Creating:' %s %d, %s %d\n",
3829                           player1Name, player1Rating,
3830                           player2Name, player2Rating);
3831
3832                 continue;
3833             }
3834
3835             /* Improved generic start/end-of-game messages */
3836             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3837                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3838                 /* If tkind == 0: */
3839                 /* star_match[0] is the game number */
3840                 /*           [1] is the white player's name */
3841                 /*           [2] is the black player's name */
3842                 /* For end-of-game: */
3843                 /*           [3] is the reason for the game end */
3844                 /*           [4] is a PGN end game-token, preceded by " " */
3845                 /* For start-of-game: */
3846                 /*           [3] begins with "Creating" or "Continuing" */
3847                 /*           [4] is " *" or empty (don't care). */
3848                 int gamenum = atoi(star_match[0]);
3849                 char *whitename, *blackname, *why, *endtoken;
3850                 ChessMove endtype = EndOfFile;
3851
3852                 if (tkind == 0) {
3853                   whitename = star_match[1];
3854                   blackname = star_match[2];
3855                   why = star_match[3];
3856                   endtoken = star_match[4];
3857                 } else {
3858                   whitename = star_match[1];
3859                   blackname = star_match[3];
3860                   why = star_match[5];
3861                   endtoken = star_match[6];
3862                 }
3863
3864                 /* Game start messages */
3865                 if (strncmp(why, "Creating ", 9) == 0 ||
3866                     strncmp(why, "Continuing ", 11) == 0) {
3867                     gs_gamenum = gamenum;
3868                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3869                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3870                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3871 #if ZIPPY
3872                     if (appData.zippyPlay) {
3873                         ZippyGameStart(whitename, blackname);
3874                     }
3875 #endif /*ZIPPY*/
3876                     partnerBoardValid = FALSE; // [HGM] bughouse
3877                     continue;
3878                 }
3879
3880                 /* Game end messages */
3881                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3882                     ics_gamenum != gamenum) {
3883                     continue;
3884                 }
3885                 while (endtoken[0] == ' ') endtoken++;
3886                 switch (endtoken[0]) {
3887                   case '*':
3888                   default:
3889                     endtype = GameUnfinished;
3890                     break;
3891                   case '0':
3892                     endtype = BlackWins;
3893                     break;
3894                   case '1':
3895                     if (endtoken[1] == '/')
3896                       endtype = GameIsDrawn;
3897                     else
3898                       endtype = WhiteWins;
3899                     break;
3900                 }
3901                 GameEnds(endtype, why, GE_ICS);
3902 #if ZIPPY
3903                 if (appData.zippyPlay && first.initDone) {
3904                     ZippyGameEnd(endtype, why);
3905                     if (first.pr == NoProc) {
3906                       /* Start the next process early so that we'll
3907                          be ready for the next challenge */
3908                       StartChessProgram(&first);
3909                     }
3910                     /* Send "new" early, in case this command takes
3911                        a long time to finish, so that we'll be ready
3912                        for the next challenge. */
3913                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3914                     Reset(TRUE, TRUE);
3915                 }
3916 #endif /*ZIPPY*/
3917                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3918                 continue;
3919             }
3920
3921             if (looking_at(buf, &i, "Removing game * from observation") ||
3922                 looking_at(buf, &i, "no longer observing game *") ||
3923                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3924                 if (gameMode == IcsObserving &&
3925                     atoi(star_match[0]) == ics_gamenum)
3926                   {
3927                       /* icsEngineAnalyze */
3928                       if (appData.icsEngineAnalyze) {
3929                             ExitAnalyzeMode();
3930                             ModeHighlight();
3931                       }
3932                       StopClocks();
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             if (looking_at(buf, &i, "no longer examining game *")) {
3941                 if (gameMode == IcsExamining &&
3942                     atoi(star_match[0]) == ics_gamenum)
3943                   {
3944                       gameMode = IcsIdle;
3945                       ics_gamenum = -1;
3946                       ics_user_moved = FALSE;
3947                   }
3948                 continue;
3949             }
3950
3951             /* Advance leftover_start past any newlines we find,
3952                so only partial lines can get reparsed */
3953             if (looking_at(buf, &i, "\n")) {
3954                 prevColor = curColor;
3955                 if (curColor != ColorNormal) {
3956                     if (oldi > next_out) {
3957                         SendToPlayer(&buf[next_out], oldi - next_out);
3958                         next_out = oldi;
3959                     }
3960                     Colorize(ColorNormal, FALSE);
3961                     curColor = ColorNormal;
3962                 }
3963                 if (started == STARTED_BOARD) {
3964                     started = STARTED_NONE;
3965                     parse[parse_pos] = NULLCHAR;
3966                     ParseBoard12(parse);
3967                     ics_user_moved = 0;
3968
3969                     /* Send premove here */
3970                     if (appData.premove) {
3971                       char str[MSG_SIZ];
3972                       if (currentMove == 0 &&
3973                           gameMode == IcsPlayingWhite &&
3974                           appData.premoveWhite) {
3975                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                         SendToICS(str);
3979                       } else if (currentMove == 1 &&
3980                                  gameMode == IcsPlayingBlack &&
3981                                  appData.premoveBlack) {
3982                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                         SendToICS(str);
3986                       } else if (gotPremove) {
3987                         gotPremove = 0;
3988                         ClearPremoveHighlights();
3989                         if (appData.debugMode)
3990                           fprintf(debugFP, "Sending premove:\n");
3991                           UserMoveEvent(premoveFromX, premoveFromY,
3992                                         premoveToX, premoveToY,
3993                                         premovePromoChar);
3994                       }
3995                     }
3996
3997                     /* Usually suppress following prompt */
3998                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3999                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4000                         if (looking_at(buf, &i, "*% ")) {
4001                             savingComment = FALSE;
4002                             suppressKibitz = 0;
4003                         }
4004                     }
4005                     next_out = i;
4006                 } else if (started == STARTED_HOLDINGS) {
4007                     int gamenum;
4008                     char new_piece[MSG_SIZ];
4009                     started = STARTED_NONE;
4010                     parse[parse_pos] = NULLCHAR;
4011                     if (appData.debugMode)
4012                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4013                                                         parse, currentMove);
4014                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4015                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4016                         if (gameInfo.variant == VariantNormal) {
4017                           /* [HGM] We seem to switch variant during a game!
4018                            * Presumably no holdings were displayed, so we have
4019                            * to move the position two files to the right to
4020                            * create room for them!
4021                            */
4022                           VariantClass newVariant;
4023                           switch(gameInfo.boardWidth) { // base guess on board width
4024                                 case 9:  newVariant = VariantShogi; break;
4025                                 case 10: newVariant = VariantGreat; break;
4026                                 default: newVariant = VariantCrazyhouse; break;
4027                           }
4028                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4029                           /* Get a move list just to see the header, which
4030                              will tell us whether this is really bug or zh */
4031                           if (ics_getting_history == H_FALSE) {
4032                             ics_getting_history = H_REQUESTED;
4033                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4034                             SendToICS(str);
4035                           }
4036                         }
4037                         new_piece[0] = NULLCHAR;
4038                         sscanf(parse, "game %d white [%s black [%s <- %s",
4039                                &gamenum, white_holding, black_holding,
4040                                new_piece);
4041                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4042                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4043                         /* [HGM] copy holdings to board holdings area */
4044                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4045                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4046                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4047 #if ZIPPY
4048                         if (appData.zippyPlay && first.initDone) {
4049                             ZippyHoldings(white_holding, black_holding,
4050                                           new_piece);
4051                         }
4052 #endif /*ZIPPY*/
4053                         if (tinyLayout || smallLayout) {
4054                             char wh[16], bh[16];
4055                             PackHolding(wh, white_holding);
4056                             PackHolding(bh, black_holding);
4057                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4058                                     gameInfo.white, gameInfo.black);
4059                         } else {
4060                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4061                                     gameInfo.white, white_holding, _("vs."),
4062                                     gameInfo.black, black_holding);
4063                         }
4064                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4065                         DrawPosition(FALSE, boards[currentMove]);
4066                         DisplayTitle(str);
4067                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4068                         sscanf(parse, "game %d white [%s black [%s <- %s",
4069                                &gamenum, white_holding, black_holding,
4070                                new_piece);
4071                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4072                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4073                         /* [HGM] copy holdings to partner-board holdings area */
4074                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4075                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4076                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4077                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4078                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4079                       }
4080                     }
4081                     /* Suppress following prompt */
4082                     if (looking_at(buf, &i, "*% ")) {
4083                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4084                         savingComment = FALSE;
4085                         suppressKibitz = 0;
4086                     }
4087                     next_out = i;
4088                 }
4089                 continue;
4090             }
4091
4092             i++;                /* skip unparsed character and loop back */
4093         }
4094
4095         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4096 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4097 //          SendToPlayer(&buf[next_out], i - next_out);
4098             started != STARTED_HOLDINGS && leftover_start > next_out) {
4099             SendToPlayer(&buf[next_out], leftover_start - next_out);
4100             next_out = i;
4101         }
4102
4103         leftover_len = buf_len - leftover_start;
4104         /* if buffer ends with something we couldn't parse,
4105            reparse it after appending the next read */
4106
4107     } else if (count == 0) {
4108         RemoveInputSource(isr);
4109         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4110     } else {
4111         DisplayFatalError(_("Error reading from ICS"), error, 1);
4112     }
4113 }
4114
4115
4116 /* Board style 12 looks like this:
4117
4118    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4119
4120  * The "<12> " is stripped before it gets to this routine.  The two
4121  * trailing 0's (flip state and clock ticking) are later addition, and
4122  * some chess servers may not have them, or may have only the first.
4123  * Additional trailing fields may be added in the future.
4124  */
4125
4126 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4127
4128 #define RELATION_OBSERVING_PLAYED    0
4129 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4130 #define RELATION_PLAYING_MYMOVE      1
4131 #define RELATION_PLAYING_NOTMYMOVE  -1
4132 #define RELATION_EXAMINING           2
4133 #define RELATION_ISOLATED_BOARD     -3
4134 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4135
4136 void
4137 ParseBoard12 (char *string)
4138 {
4139     GameMode newGameMode;
4140     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4141     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4142     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4143     char to_play, board_chars[200];
4144     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4145     char black[32], white[32];
4146     Board board;
4147     int prevMove = currentMove;
4148     int ticking = 2;
4149     ChessMove moveType;
4150     int fromX, fromY, toX, toY;
4151     char promoChar;
4152     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4153     char *bookHit = NULL; // [HGM] book
4154     Boolean weird = FALSE, reqFlag = FALSE;
4155
4156     fromX = fromY = toX = toY = -1;
4157
4158     newGame = FALSE;
4159
4160     if (appData.debugMode)
4161       fprintf(debugFP, _("Parsing board: %s\n"), string);
4162
4163     move_str[0] = NULLCHAR;
4164     elapsed_time[0] = NULLCHAR;
4165     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4166         int  i = 0, j;
4167         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4168             if(string[i] == ' ') { ranks++; files = 0; }
4169             else files++;
4170             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4171             i++;
4172         }
4173         for(j = 0; j <i; j++) board_chars[j] = string[j];
4174         board_chars[i] = '\0';
4175         string += i + 1;
4176     }
4177     n = sscanf(string, PATTERN, &to_play, &double_push,
4178                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4179                &gamenum, white, black, &relation, &basetime, &increment,
4180                &white_stren, &black_stren, &white_time, &black_time,
4181                &moveNum, str, elapsed_time, move_str, &ics_flip,
4182                &ticking);
4183
4184     if (n < 21) {
4185         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4186         DisplayError(str, 0);
4187         return;
4188     }
4189
4190     /* Convert the move number to internal form */
4191     moveNum = (moveNum - 1) * 2;
4192     if (to_play == 'B') moveNum++;
4193     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4194       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4195                         0, 1);
4196       return;
4197     }
4198
4199     switch (relation) {
4200       case RELATION_OBSERVING_PLAYED:
4201       case RELATION_OBSERVING_STATIC:
4202         if (gamenum == -1) {
4203             /* Old ICC buglet */
4204             relation = RELATION_OBSERVING_STATIC;
4205         }
4206         newGameMode = IcsObserving;
4207         break;
4208       case RELATION_PLAYING_MYMOVE:
4209       case RELATION_PLAYING_NOTMYMOVE:
4210         newGameMode =
4211           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4212             IcsPlayingWhite : IcsPlayingBlack;
4213         break;
4214       case RELATION_EXAMINING:
4215         newGameMode = IcsExamining;
4216         break;
4217       case RELATION_ISOLATED_BOARD:
4218       default:
4219         /* Just display this board.  If user was doing something else,
4220            we will forget about it until the next board comes. */
4221         newGameMode = IcsIdle;
4222         break;
4223       case RELATION_STARTING_POSITION:
4224         newGameMode = gameMode;
4225         break;
4226     }
4227
4228     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4229          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4230       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4231       char *toSqr;
4232       for (k = 0; k < ranks; k++) {
4233         for (j = 0; j < files; j++)
4234           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4235         if(gameInfo.holdingsWidth > 1) {
4236              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4237              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4238         }
4239       }
4240       CopyBoard(partnerBoard, board);
4241       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4242         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4243         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4244       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4245       if(toSqr = strchr(str, '-')) {
4246         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4247         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4248       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4249       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4250       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4251       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4252       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4253       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4254                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4255       DisplayMessage(partnerStatus, "");
4256         partnerBoardValid = TRUE;
4257       return;
4258     }
4259
4260     /* Modify behavior for initial board display on move listing
4261        of wild games.
4262        */
4263     switch (ics_getting_history) {
4264       case H_FALSE:
4265       case H_REQUESTED:
4266         break;
4267       case H_GOT_REQ_HEADER:
4268       case H_GOT_UNREQ_HEADER:
4269         /* This is the initial position of the current game */
4270         gamenum = ics_gamenum;
4271         moveNum = 0;            /* old ICS bug workaround */
4272         if (to_play == 'B') {
4273           startedFromSetupPosition = TRUE;
4274           blackPlaysFirst = TRUE;
4275           moveNum = 1;
4276           if (forwardMostMove == 0) forwardMostMove = 1;
4277           if (backwardMostMove == 0) backwardMostMove = 1;
4278           if (currentMove == 0) currentMove = 1;
4279         }
4280         newGameMode = gameMode;
4281         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4282         break;
4283       case H_GOT_UNWANTED_HEADER:
4284         /* This is an initial board that we don't want */
4285         return;
4286       case H_GETTING_MOVES:
4287         /* Should not happen */
4288         DisplayError(_("Error gathering move list: extra board"), 0);
4289         ics_getting_history = H_FALSE;
4290         return;
4291     }
4292
4293    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4294                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4295      /* [HGM] We seem to have switched variant unexpectedly
4296       * Try to guess new variant from board size
4297       */
4298           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4299           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4300           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4301           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4302           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4303           if(!weird) newVariant = VariantNormal;
4304           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4305           /* Get a move list just to see the header, which
4306              will tell us whether this is really bug or zh */
4307           if (ics_getting_history == H_FALSE) {
4308             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4309             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4310             SendToICS(str);
4311           }
4312     }
4313
4314     /* Take action if this is the first board of a new game, or of a
4315        different game than is currently being displayed.  */
4316     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4317         relation == RELATION_ISOLATED_BOARD) {
4318
4319         /* Forget the old game and get the history (if any) of the new one */
4320         if (gameMode != BeginningOfGame) {
4321           Reset(TRUE, TRUE);
4322         }
4323         newGame = TRUE;
4324         if (appData.autoRaiseBoard) BoardToTop();
4325         prevMove = -3;
4326         if (gamenum == -1) {
4327             newGameMode = IcsIdle;
4328         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4329                    appData.getMoveList && !reqFlag) {
4330             /* Need to get game history */
4331             ics_getting_history = H_REQUESTED;
4332             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4333             SendToICS(str);
4334         }
4335
4336         /* Initially flip the board to have black on the bottom if playing
4337            black or if the ICS flip flag is set, but let the user change
4338            it with the Flip View button. */
4339         flipView = appData.autoFlipView ?
4340           (newGameMode == IcsPlayingBlack) || ics_flip :
4341           appData.flipView;
4342
4343         /* Done with values from previous mode; copy in new ones */
4344         gameMode = newGameMode;
4345         ModeHighlight();
4346         ics_gamenum = gamenum;
4347         if (gamenum == gs_gamenum) {
4348             int klen = strlen(gs_kind);
4349             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4350             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4351             gameInfo.event = StrSave(str);
4352         } else {
4353             gameInfo.event = StrSave("ICS game");
4354         }
4355         gameInfo.site = StrSave(appData.icsHost);
4356         gameInfo.date = PGNDate();
4357         gameInfo.round = StrSave("-");
4358         gameInfo.white = StrSave(white);
4359         gameInfo.black = StrSave(black);
4360         timeControl = basetime * 60 * 1000;
4361         timeControl_2 = 0;
4362         timeIncrement = increment * 1000;
4363         movesPerSession = 0;
4364         gameInfo.timeControl = TimeControlTagValue();
4365         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4366   if (appData.debugMode) {
4367     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4368     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4369     setbuf(debugFP, NULL);
4370   }
4371
4372         gameInfo.outOfBook = NULL;
4373
4374         /* Do we have the ratings? */
4375         if (strcmp(player1Name, white) == 0 &&
4376             strcmp(player2Name, black) == 0) {
4377             if (appData.debugMode)
4378               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4379                       player1Rating, player2Rating);
4380             gameInfo.whiteRating = player1Rating;
4381             gameInfo.blackRating = player2Rating;
4382         } else if (strcmp(player2Name, white) == 0 &&
4383                    strcmp(player1Name, black) == 0) {
4384             if (appData.debugMode)
4385               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4386                       player2Rating, player1Rating);
4387             gameInfo.whiteRating = player2Rating;
4388             gameInfo.blackRating = player1Rating;
4389         }
4390         player1Name[0] = player2Name[0] = NULLCHAR;
4391
4392         /* Silence shouts if requested */
4393         if (appData.quietPlay &&
4394             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4395             SendToICS(ics_prefix);
4396             SendToICS("set shout 0\n");
4397         }
4398     }
4399
4400     /* Deal with midgame name changes */
4401     if (!newGame) {
4402         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4403             if (gameInfo.white) free(gameInfo.white);
4404             gameInfo.white = StrSave(white);
4405         }
4406         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4407             if (gameInfo.black) free(gameInfo.black);
4408             gameInfo.black = StrSave(black);
4409         }
4410     }
4411
4412     /* Throw away game result if anything actually changes in examine mode */
4413     if (gameMode == IcsExamining && !newGame) {
4414         gameInfo.result = GameUnfinished;
4415         if (gameInfo.resultDetails != NULL) {
4416             free(gameInfo.resultDetails);
4417             gameInfo.resultDetails = NULL;
4418         }
4419     }
4420
4421     /* In pausing && IcsExamining mode, we ignore boards coming
4422        in if they are in a different variation than we are. */
4423     if (pauseExamInvalid) return;
4424     if (pausing && gameMode == IcsExamining) {
4425         if (moveNum <= pauseExamForwardMostMove) {
4426             pauseExamInvalid = TRUE;
4427             forwardMostMove = pauseExamForwardMostMove;
4428             return;
4429         }
4430     }
4431
4432   if (appData.debugMode) {
4433     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4434   }
4435     /* Parse the board */
4436     for (k = 0; k < ranks; k++) {
4437       for (j = 0; j < files; j++)
4438         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4439       if(gameInfo.holdingsWidth > 1) {
4440            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4441            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4442       }
4443     }
4444     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4445       board[5][BOARD_RGHT+1] = WhiteAngel;
4446       board[6][BOARD_RGHT+1] = WhiteMarshall;
4447       board[1][0] = BlackMarshall;
4448       board[2][0] = BlackAngel;
4449       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4450     }
4451     CopyBoard(boards[moveNum], board);
4452     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4453     if (moveNum == 0) {
4454         startedFromSetupPosition =
4455           !CompareBoards(board, initialPosition);
4456         if(startedFromSetupPosition)
4457             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4458     }
4459
4460     /* [HGM] Set castling rights. Take the outermost Rooks,
4461        to make it also work for FRC opening positions. Note that board12
4462        is really defective for later FRC positions, as it has no way to
4463        indicate which Rook can castle if they are on the same side of King.
4464        For the initial position we grant rights to the outermost Rooks,
4465        and remember thos rights, and we then copy them on positions
4466        later in an FRC game. This means WB might not recognize castlings with
4467        Rooks that have moved back to their original position as illegal,
4468        but in ICS mode that is not its job anyway.
4469     */
4470     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4471     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4472
4473         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474             if(board[0][i] == WhiteRook) j = i;
4475         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477             if(board[0][i] == WhiteRook) j = i;
4478         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4480             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4481         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4482         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4483             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4484         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4485
4486         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4487         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4488         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4490         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4491             if(board[BOARD_HEIGHT-1][k] == bKing)
4492                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4493         if(gameInfo.variant == VariantTwoKings) {
4494             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4495             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4496             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4497         }
4498     } else { int r;
4499         r = boards[moveNum][CASTLING][0] = initialRights[0];
4500         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4501         r = boards[moveNum][CASTLING][1] = initialRights[1];
4502         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4503         r = boards[moveNum][CASTLING][3] = initialRights[3];
4504         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4505         r = boards[moveNum][CASTLING][4] = initialRights[4];
4506         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4507         /* wildcastle kludge: always assume King has rights */
4508         r = boards[moveNum][CASTLING][2] = initialRights[2];
4509         r = boards[moveNum][CASTLING][5] = initialRights[5];
4510     }
4511     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4512     boards[moveNum][EP_STATUS] = EP_NONE;
4513     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4514     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4515     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4516
4517
4518     if (ics_getting_history == H_GOT_REQ_HEADER ||
4519         ics_getting_history == H_GOT_UNREQ_HEADER) {
4520         /* This was an initial position from a move list, not
4521            the current position */
4522         return;
4523     }
4524
4525     /* Update currentMove and known move number limits */
4526     newMove = newGame || moveNum > forwardMostMove;
4527
4528     if (newGame) {
4529         forwardMostMove = backwardMostMove = currentMove = moveNum;
4530         if (gameMode == IcsExamining && moveNum == 0) {
4531           /* Workaround for ICS limitation: we are not told the wild
4532              type when starting to examine a game.  But if we ask for
4533              the move list, the move list header will tell us */
4534             ics_getting_history = H_REQUESTED;
4535             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4536             SendToICS(str);
4537         }
4538     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4539                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4540 #if ZIPPY
4541         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4542         /* [HGM] applied this also to an engine that is silently watching        */
4543         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4544             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4545             gameInfo.variant == currentlyInitializedVariant) {
4546           takeback = forwardMostMove - moveNum;
4547           for (i = 0; i < takeback; i++) {
4548             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4549             SendToProgram("undo\n", &first);
4550           }
4551         }
4552 #endif
4553
4554         forwardMostMove = moveNum;
4555         if (!pausing || currentMove > forwardMostMove)
4556           currentMove = forwardMostMove;
4557     } else {
4558         /* New part of history that is not contiguous with old part */
4559         if (pausing && gameMode == IcsExamining) {
4560             pauseExamInvalid = TRUE;
4561             forwardMostMove = pauseExamForwardMostMove;
4562             return;
4563         }
4564         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4565 #if ZIPPY
4566             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4567                 // [HGM] when we will receive the move list we now request, it will be
4568                 // fed to the engine from the first move on. So if the engine is not
4569                 // in the initial position now, bring it there.
4570                 InitChessProgram(&first, 0);
4571             }
4572 #endif
4573             ics_getting_history = H_REQUESTED;
4574             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4575             SendToICS(str);
4576         }
4577         forwardMostMove = backwardMostMove = currentMove = moveNum;
4578     }
4579
4580     /* Update the clocks */
4581     if (strchr(elapsed_time, '.')) {
4582       /* Time is in ms */
4583       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4584       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4585     } else {
4586       /* Time is in seconds */
4587       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4588       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4589     }
4590
4591
4592 #if ZIPPY
4593     if (appData.zippyPlay && newGame &&
4594         gameMode != IcsObserving && gameMode != IcsIdle &&
4595         gameMode != IcsExamining)
4596       ZippyFirstBoard(moveNum, basetime, increment);
4597 #endif
4598
4599     /* Put the move on the move list, first converting
4600        to canonical algebraic form. */
4601     if (moveNum > 0) {
4602   if (appData.debugMode) {
4603     if (appData.debugMode) { int f = forwardMostMove;
4604         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4605                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4606                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4607     }
4608     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4609     fprintf(debugFP, "moveNum = %d\n", moveNum);
4610     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4611     setbuf(debugFP, NULL);
4612   }
4613         if (moveNum <= backwardMostMove) {
4614             /* We don't know what the board looked like before
4615                this move.  Punt. */
4616           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4617             strcat(parseList[moveNum - 1], " ");
4618             strcat(parseList[moveNum - 1], elapsed_time);
4619             moveList[moveNum - 1][0] = NULLCHAR;
4620         } else if (strcmp(move_str, "none") == 0) {
4621             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4622             /* Again, we don't know what the board looked like;
4623                this is really the start of the game. */
4624             parseList[moveNum - 1][0] = NULLCHAR;
4625             moveList[moveNum - 1][0] = NULLCHAR;
4626             backwardMostMove = moveNum;
4627             startedFromSetupPosition = TRUE;
4628             fromX = fromY = toX = toY = -1;
4629         } else {
4630           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4631           //                 So we parse the long-algebraic move string in stead of the SAN move
4632           int valid; char buf[MSG_SIZ], *prom;
4633
4634           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4635                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4636           // str looks something like "Q/a1-a2"; kill the slash
4637           if(str[1] == '/')
4638             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4639           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4640           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4641                 strcat(buf, prom); // long move lacks promo specification!
4642           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4643                 if(appData.debugMode)
4644                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4645                 safeStrCpy(move_str, buf, MSG_SIZ);
4646           }
4647           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4648                                 &fromX, &fromY, &toX, &toY, &promoChar)
4649                || ParseOneMove(buf, moveNum - 1, &moveType,
4650                                 &fromX, &fromY, &toX, &toY, &promoChar);
4651           // end of long SAN patch
4652           if (valid) {
4653             (void) CoordsToAlgebraic(boards[moveNum - 1],
4654                                      PosFlags(moveNum - 1),
4655                                      fromY, fromX, toY, toX, promoChar,
4656                                      parseList[moveNum-1]);
4657             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4658               case MT_NONE:
4659               case MT_STALEMATE:
4660               default:
4661                 break;
4662               case MT_CHECK:
4663                 if(gameInfo.variant != VariantShogi)
4664                     strcat(parseList[moveNum - 1], "+");
4665                 break;
4666               case MT_CHECKMATE:
4667               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4668                 strcat(parseList[moveNum - 1], "#");
4669                 break;
4670             }
4671             strcat(parseList[moveNum - 1], " ");
4672             strcat(parseList[moveNum - 1], elapsed_time);
4673             /* currentMoveString is set as a side-effect of ParseOneMove */
4674             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4675             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4676             strcat(moveList[moveNum - 1], "\n");
4677
4678             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4679                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4680               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4681                 ChessSquare old, new = boards[moveNum][k][j];
4682                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4683                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4684                   if(old == new) continue;
4685                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4686                   else if(new == WhiteWazir || new == BlackWazir) {
4687                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4688                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4689                       else boards[moveNum][k][j] = old; // preserve type of Gold
4690                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4691                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4692               }
4693           } else {
4694             /* Move from ICS was illegal!?  Punt. */
4695             if (appData.debugMode) {
4696               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4697               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4698             }
4699             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4700             strcat(parseList[moveNum - 1], " ");
4701             strcat(parseList[moveNum - 1], elapsed_time);
4702             moveList[moveNum - 1][0] = NULLCHAR;
4703             fromX = fromY = toX = toY = -1;
4704           }
4705         }
4706   if (appData.debugMode) {
4707     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4708     setbuf(debugFP, NULL);
4709   }
4710
4711 #if ZIPPY
4712         /* Send move to chess program (BEFORE animating it). */
4713         if (appData.zippyPlay && !newGame && newMove &&
4714            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4715
4716             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4717                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4718                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4719                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4720                             move_str);
4721                     DisplayError(str, 0);
4722                 } else {
4723                     if (first.sendTime) {
4724                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4725                     }
4726                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4727                     if (firstMove && !bookHit) {
4728                         firstMove = FALSE;
4729                         if (first.useColors) {
4730                           SendToProgram(gameMode == IcsPlayingWhite ?
4731                                         "white\ngo\n" :
4732                                         "black\ngo\n", &first);
4733                         } else {
4734                           SendToProgram("go\n", &first);
4735                         }
4736                         first.maybeThinking = TRUE;
4737                     }
4738                 }
4739             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4740               if (moveList[moveNum - 1][0] == NULLCHAR) {
4741                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4742                 DisplayError(str, 0);
4743               } else {
4744                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4745                 SendMoveToProgram(moveNum - 1, &first);
4746               }
4747             }
4748         }
4749 #endif
4750     }
4751
4752     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4753         /* If move comes from a remote source, animate it.  If it
4754            isn't remote, it will have already been animated. */
4755         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4756             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4757         }
4758         if (!pausing && appData.highlightLastMove) {
4759             SetHighlights(fromX, fromY, toX, toY);
4760         }
4761     }
4762
4763     /* Start the clocks */
4764     whiteFlag = blackFlag = FALSE;
4765     appData.clockMode = !(basetime == 0 && increment == 0);
4766     if (ticking == 0) {
4767       ics_clock_paused = TRUE;
4768       StopClocks();
4769     } else if (ticking == 1) {
4770       ics_clock_paused = FALSE;
4771     }
4772     if (gameMode == IcsIdle ||
4773         relation == RELATION_OBSERVING_STATIC ||
4774         relation == RELATION_EXAMINING ||
4775         ics_clock_paused)
4776       DisplayBothClocks();
4777     else
4778       StartClocks();
4779
4780     /* Display opponents and material strengths */
4781     if (gameInfo.variant != VariantBughouse &&
4782         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4783         if (tinyLayout || smallLayout) {
4784             if(gameInfo.variant == VariantNormal)
4785               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4786                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4787                     basetime, increment);
4788             else
4789               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4790                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4791                     basetime, increment, (int) gameInfo.variant);
4792         } else {
4793             if(gameInfo.variant == VariantNormal)
4794               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4795                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4796                     basetime, increment);
4797             else
4798               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4799                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4800                     basetime, increment, VariantName(gameInfo.variant));
4801         }
4802         DisplayTitle(str);
4803   if (appData.debugMode) {
4804     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4805   }
4806     }
4807
4808
4809     /* Display the board */
4810     if (!pausing && !appData.noGUI) {
4811
4812       if (appData.premove)
4813           if (!gotPremove ||
4814              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4815              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4816               ClearPremoveHighlights();
4817
4818       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4819         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4820       DrawPosition(j, boards[currentMove]);
4821
4822       DisplayMove(moveNum - 1);
4823       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4824             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4825               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4826         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4827       }
4828     }
4829
4830     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4831 #if ZIPPY
4832     if(bookHit) { // [HGM] book: simulate book reply
4833         static char bookMove[MSG_SIZ]; // a bit generous?
4834
4835         programStats.nodes = programStats.depth = programStats.time =
4836         programStats.score = programStats.got_only_move = 0;
4837         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4838
4839         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4840         strcat(bookMove, bookHit);
4841         HandleMachineMove(bookMove, &first);
4842     }
4843 #endif
4844 }
4845
4846 void
4847 GetMoveListEvent ()
4848 {
4849     char buf[MSG_SIZ];
4850     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4851         ics_getting_history = H_REQUESTED;
4852         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4853         SendToICS(buf);
4854     }
4855 }
4856
4857 void
4858 AnalysisPeriodicEvent (int force)
4859 {
4860     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4861          && !force) || !appData.periodicUpdates)
4862       return;
4863
4864     /* Send . command to Crafty to collect stats */
4865     SendToProgram(".\n", &first);
4866
4867     /* Don't send another until we get a response (this makes
4868        us stop sending to old Crafty's which don't understand
4869        the "." command (sending illegal cmds resets node count & time,
4870        which looks bad)) */
4871     programStats.ok_to_send = 0;
4872 }
4873
4874 void
4875 ics_update_width (int new_width)
4876 {
4877         ics_printf("set width %d\n", new_width);
4878 }
4879
4880 void
4881 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4882 {
4883     char buf[MSG_SIZ];
4884
4885     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4886         // null move in variant where engine does not understand it (for analysis purposes)
4887         SendBoard(cps, moveNum + 1); // send position after move in stead.
4888         return;
4889     }
4890     if (cps->useUsermove) {
4891       SendToProgram("usermove ", cps);
4892     }
4893     if (cps->useSAN) {
4894       char *space;
4895       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4896         int len = space - parseList[moveNum];
4897         memcpy(buf, parseList[moveNum], len);
4898         buf[len++] = '\n';
4899         buf[len] = NULLCHAR;
4900       } else {
4901         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4902       }
4903       SendToProgram(buf, cps);
4904     } else {
4905       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4906         AlphaRank(moveList[moveNum], 4);
4907         SendToProgram(moveList[moveNum], cps);
4908         AlphaRank(moveList[moveNum], 4); // and back
4909       } else
4910       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4911        * the engine. It would be nice to have a better way to identify castle
4912        * moves here. */
4913       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4914                                                                          && cps->useOOCastle) {
4915         int fromX = moveList[moveNum][0] - AAA;
4916         int fromY = moveList[moveNum][1] - ONE;
4917         int toX = moveList[moveNum][2] - AAA;
4918         int toY = moveList[moveNum][3] - ONE;
4919         if((boards[moveNum][fromY][fromX] == WhiteKing
4920             && boards[moveNum][toY][toX] == WhiteRook)
4921            || (boards[moveNum][fromY][fromX] == BlackKing
4922                && boards[moveNum][toY][toX] == BlackRook)) {
4923           if(toX > fromX) SendToProgram("O-O\n", cps);
4924           else SendToProgram("O-O-O\n", cps);
4925         }
4926         else SendToProgram(moveList[moveNum], cps);
4927       } else
4928       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4929         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4930           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4931           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4932                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4933         } else
4934           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4935                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4936         SendToProgram(buf, cps);
4937       }
4938       else SendToProgram(moveList[moveNum], cps);
4939       /* End of additions by Tord */
4940     }
4941
4942     /* [HGM] setting up the opening has brought engine in force mode! */
4943     /*       Send 'go' if we are in a mode where machine should play. */
4944     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4945         (gameMode == TwoMachinesPlay   ||
4946 #if ZIPPY
4947          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4948 #endif
4949          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4950         SendToProgram("go\n", cps);
4951   if (appData.debugMode) {
4952     fprintf(debugFP, "(extra)\n");
4953   }
4954     }
4955     setboardSpoiledMachineBlack = 0;
4956 }
4957
4958 void
4959 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4960 {
4961     char user_move[MSG_SIZ];
4962     char suffix[4];
4963
4964     if(gameInfo.variant == VariantSChess && promoChar) {
4965         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4966         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4967     } else suffix[0] = NULLCHAR;
4968
4969     switch (moveType) {
4970       default:
4971         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4972                 (int)moveType, fromX, fromY, toX, toY);
4973         DisplayError(user_move + strlen("say "), 0);
4974         break;
4975       case WhiteKingSideCastle:
4976       case BlackKingSideCastle:
4977       case WhiteQueenSideCastleWild:
4978       case BlackQueenSideCastleWild:
4979       /* PUSH Fabien */
4980       case WhiteHSideCastleFR:
4981       case BlackHSideCastleFR:
4982       /* POP Fabien */
4983         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4984         break;
4985       case WhiteQueenSideCastle:
4986       case BlackQueenSideCastle:
4987       case WhiteKingSideCastleWild:
4988       case BlackKingSideCastleWild:
4989       /* PUSH Fabien */
4990       case WhiteASideCastleFR:
4991       case BlackASideCastleFR:
4992       /* POP Fabien */
4993         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4994         break;
4995       case WhiteNonPromotion:
4996       case BlackNonPromotion:
4997         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4998         break;
4999       case WhitePromotion:
5000       case BlackPromotion:
5001         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5002           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5004                 PieceToChar(WhiteFerz));
5005         else if(gameInfo.variant == VariantGreat)
5006           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5007                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5008                 PieceToChar(WhiteMan));
5009         else
5010           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5011                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5012                 promoChar);
5013         break;
5014       case WhiteDrop:
5015       case BlackDrop:
5016       drop:
5017         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5018                  ToUpper(PieceToChar((ChessSquare) fromX)),
5019                  AAA + toX, ONE + toY);
5020         break;
5021       case IllegalMove:  /* could be a variant we don't quite understand */
5022         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5023       case NormalMove:
5024       case WhiteCapturesEnPassant:
5025       case BlackCapturesEnPassant:
5026         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5027                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5028         break;
5029     }
5030     SendToICS(user_move);
5031     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5032         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5033 }
5034
5035 void
5036 UploadGameEvent ()
5037 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5038     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5039     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5040     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5041       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5042       return;
5043     }
5044     if(gameMode != IcsExamining) { // is this ever not the case?
5045         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5046
5047         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5048           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5049         } else { // on FICS we must first go to general examine mode
5050           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5051         }
5052         if(gameInfo.variant != VariantNormal) {
5053             // try figure out wild number, as xboard names are not always valid on ICS
5054             for(i=1; i<=36; i++) {
5055               snprintf(buf, MSG_SIZ, "wild/%d", i);
5056                 if(StringToVariant(buf) == gameInfo.variant) break;
5057             }
5058             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5059             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5060             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5061         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5062         SendToICS(ics_prefix);
5063         SendToICS(buf);
5064         if(startedFromSetupPosition || backwardMostMove != 0) {
5065           fen = PositionToFEN(backwardMostMove, NULL);
5066           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5067             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5068             SendToICS(buf);
5069           } else { // FICS: everything has to set by separate bsetup commands
5070             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5071             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5072             SendToICS(buf);
5073             if(!WhiteOnMove(backwardMostMove)) {
5074                 SendToICS("bsetup tomove black\n");
5075             }
5076             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5077             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5078             SendToICS(buf);
5079             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5080             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5081             SendToICS(buf);
5082             i = boards[backwardMostMove][EP_STATUS];
5083             if(i >= 0) { // set e.p.
5084               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5085                 SendToICS(buf);
5086             }
5087             bsetup++;
5088           }
5089         }
5090       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5091             SendToICS("bsetup done\n"); // switch to normal examining.
5092     }
5093     for(i = backwardMostMove; i<last; i++) {
5094         char buf[20];
5095         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5096         SendToICS(buf);
5097     }
5098     SendToICS(ics_prefix);
5099     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5100 }
5101
5102 void
5103 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5104 {
5105     if (rf == DROP_RANK) {
5106       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5107       sprintf(move, "%c@%c%c\n",
5108                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5109     } else {
5110         if (promoChar == 'x' || promoChar == NULLCHAR) {
5111           sprintf(move, "%c%c%c%c\n",
5112                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5113         } else {
5114             sprintf(move, "%c%c%c%c%c\n",
5115                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5116         }
5117     }
5118 }
5119
5120 void
5121 ProcessICSInitScript (FILE *f)
5122 {
5123     char buf[MSG_SIZ];
5124
5125     while (fgets(buf, MSG_SIZ, f)) {
5126         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5127     }
5128
5129     fclose(f);
5130 }
5131
5132
5133 static int lastX, lastY, selectFlag, dragging;
5134
5135 void
5136 Sweep (int step)
5137 {
5138     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5139     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5140     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5141     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5142     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5143     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5144     do {
5145         promoSweep -= step;
5146         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5147         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5148         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5149         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5150         if(!step) step = -1;
5151     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5152             appData.testLegality && (promoSweep == king ||
5153             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5154     ChangeDragPiece(promoSweep);
5155 }
5156
5157 int
5158 PromoScroll (int x, int y)
5159 {
5160   int step = 0;
5161
5162   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5163   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5164   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5165   if(!step) return FALSE;
5166   lastX = x; lastY = y;
5167   if((promoSweep < BlackPawn) == flipView) step = -step;
5168   if(step > 0) selectFlag = 1;
5169   if(!selectFlag) Sweep(step);
5170   return FALSE;
5171 }
5172
5173 void
5174 NextPiece (int step)
5175 {
5176     ChessSquare piece = boards[currentMove][toY][toX];
5177     do {
5178         pieceSweep -= step;
5179         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5180         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5181         if(!step) step = -1;
5182     } while(PieceToChar(pieceSweep) == '.');
5183     boards[currentMove][toY][toX] = pieceSweep;
5184     DrawPosition(FALSE, boards[currentMove]);
5185     boards[currentMove][toY][toX] = piece;
5186 }
5187 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5188 void
5189 AlphaRank (char *move, int n)
5190 {
5191 //    char *p = move, c; int x, y;
5192
5193     if (appData.debugMode) {
5194         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5195     }
5196
5197     if(move[1]=='*' &&
5198        move[2]>='0' && move[2]<='9' &&
5199        move[3]>='a' && move[3]<='x'    ) {
5200         move[1] = '@';
5201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5203     } else
5204     if(move[0]>='0' && move[0]<='9' &&
5205        move[1]>='a' && move[1]<='x' &&
5206        move[2]>='0' && move[2]<='9' &&
5207        move[3]>='a' && move[3]<='x'    ) {
5208         /* input move, Shogi -> normal */
5209         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5210         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5211         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5212         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5213     } else
5214     if(move[1]=='@' &&
5215        move[3]>='0' && move[3]<='9' &&
5216        move[2]>='a' && move[2]<='x'    ) {
5217         move[1] = '*';
5218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5220     } else
5221     if(
5222        move[0]>='a' && move[0]<='x' &&
5223        move[3]>='0' && move[3]<='9' &&
5224        move[2]>='a' && move[2]<='x'    ) {
5225          /* output move, normal -> Shogi */
5226         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5227         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5228         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5229         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5230         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5231     }
5232     if (appData.debugMode) {
5233         fprintf(debugFP, "   out = '%s'\n", move);
5234     }
5235 }
5236
5237 char yy_textstr[8000];
5238
5239 /* Parser for moves from gnuchess, ICS, or user typein box */
5240 Boolean
5241 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5242 {
5243     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5244
5245     switch (*moveType) {
5246       case WhitePromotion:
5247       case BlackPromotion:
5248       case WhiteNonPromotion:
5249       case BlackNonPromotion:
5250       case NormalMove:
5251       case WhiteCapturesEnPassant:
5252       case BlackCapturesEnPassant:
5253       case WhiteKingSideCastle:
5254       case WhiteQueenSideCastle:
5255       case BlackKingSideCastle:
5256       case BlackQueenSideCastle:
5257       case WhiteKingSideCastleWild:
5258       case WhiteQueenSideCastleWild:
5259       case BlackKingSideCastleWild:
5260       case BlackQueenSideCastleWild:
5261       /* Code added by Tord: */
5262       case WhiteHSideCastleFR:
5263       case WhiteASideCastleFR:
5264       case BlackHSideCastleFR:
5265       case BlackASideCastleFR:
5266       /* End of code added by Tord */
5267       case IllegalMove:         /* bug or odd chess variant */
5268         *fromX = currentMoveString[0] - AAA;
5269         *fromY = currentMoveString[1] - ONE;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = currentMoveString[4];
5273         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5274             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5275     if (appData.debugMode) {
5276         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5277     }
5278             *fromX = *fromY = *toX = *toY = 0;
5279             return FALSE;
5280         }
5281         if (appData.testLegality) {
5282           return (*moveType != IllegalMove);
5283         } else {
5284           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5285                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5286         }
5287
5288       case WhiteDrop:
5289       case BlackDrop:
5290         *fromX = *moveType == WhiteDrop ?
5291           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5292           (int) CharToPiece(ToLower(currentMoveString[0]));
5293         *fromY = DROP_RANK;
5294         *toX = currentMoveString[2] - AAA;
5295         *toY = currentMoveString[3] - ONE;
5296         *promoChar = NULLCHAR;
5297         return TRUE;
5298
5299       case AmbiguousMove:
5300       case ImpossibleMove:
5301       case EndOfFile:
5302       case ElapsedTime:
5303       case Comment:
5304       case PGNTag:
5305       case NAG:
5306       case WhiteWins:
5307       case BlackWins:
5308       case GameIsDrawn:
5309       default:
5310     if (appData.debugMode) {
5311         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5312     }
5313         /* bug? */
5314         *fromX = *fromY = *toX = *toY = 0;
5315         *promoChar = NULLCHAR;
5316         return FALSE;
5317     }
5318 }
5319
5320 Boolean pushed = FALSE;
5321 char *lastParseAttempt;
5322
5323 void
5324 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5325 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5326   int fromX, fromY, toX, toY; char promoChar;
5327   ChessMove moveType;
5328   Boolean valid;
5329   int nr = 0;
5330
5331   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5332     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5333     pushed = TRUE;
5334   }
5335   endPV = forwardMostMove;
5336   do {
5337     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5338     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5339     lastParseAttempt = pv;
5340     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5341     if(!valid && nr == 0 &&
5342        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5343         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5344         // Hande case where played move is different from leading PV move
5345         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5346         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5347         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5348         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5349           endPV += 2; // if position different, keep this
5350           moveList[endPV-1][0] = fromX + AAA;
5351           moveList[endPV-1][1] = fromY + ONE;
5352           moveList[endPV-1][2] = toX + AAA;
5353           moveList[endPV-1][3] = toY + ONE;
5354           parseList[endPV-1][0] = NULLCHAR;
5355           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5356         }
5357       }
5358     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5359     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5360     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5361     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5362         valid++; // allow comments in PV
5363         continue;
5364     }
5365     nr++;
5366     if(endPV+1 > framePtr) break; // no space, truncate
5367     if(!valid) break;
5368     endPV++;
5369     CopyBoard(boards[endPV], boards[endPV-1]);
5370     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5371     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5372     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5373     CoordsToAlgebraic(boards[endPV - 1],
5374                              PosFlags(endPV - 1),
5375                              fromY, fromX, toY, toX, promoChar,
5376                              parseList[endPV - 1]);
5377   } while(valid);
5378   if(atEnd == 2) return; // used hidden, for PV conversion
5379   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5380   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5381   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5382                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5383   DrawPosition(TRUE, boards[currentMove]);
5384 }
5385
5386 int
5387 MultiPV (ChessProgramState *cps)
5388 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5389         int i;
5390         for(i=0; i<cps->nrOptions; i++)
5391             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5392                 return i;
5393         return -1;
5394 }
5395
5396 Boolean
5397 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5398 {
5399         int startPV, multi, lineStart, origIndex = index;
5400         char *p, buf2[MSG_SIZ];
5401
5402         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5403         lastX = x; lastY = y;
5404         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5405         lineStart = startPV = index;
5406         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5407         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5408         index = startPV;
5409         do{ while(buf[index] && buf[index] != '\n') index++;
5410         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5411         buf[index] = 0;
5412         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5413                 int n = first.option[multi].value;
5414                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5415                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5416                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5417                 first.option[multi].value = n;
5418                 *start = *end = 0;
5419                 return FALSE;
5420         }
5421         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5422         *start = startPV; *end = index-1;
5423         return TRUE;
5424 }
5425
5426 char *
5427 PvToSAN (char *pv)
5428 {
5429         static char buf[10*MSG_SIZ];
5430         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5431         *buf = NULLCHAR;
5432         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5433         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5434         for(i = forwardMostMove; i<endPV; i++){
5435             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5436             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5437             k += strlen(buf+k);
5438         }
5439         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5440         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5441         endPV = savedEnd;
5442         return buf;
5443 }
5444
5445 Boolean
5446 LoadPV (int x, int y)
5447 { // called on right mouse click to load PV
5448   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5449   lastX = x; lastY = y;
5450   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5451   return TRUE;
5452 }
5453
5454 void
5455 UnLoadPV ()
5456 {
5457   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5458   if(endPV < 0) return;
5459   if(appData.autoCopyPV) CopyFENToClipboard();
5460   endPV = -1;
5461   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5462         Boolean saveAnimate = appData.animate;
5463         if(pushed) {
5464             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5465                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5466             } else storedGames--; // abandon shelved tail of original game
5467         }
5468         pushed = FALSE;
5469         forwardMostMove = currentMove;
5470         currentMove = oldFMM;
5471         appData.animate = FALSE;
5472         ToNrEvent(forwardMostMove);
5473         appData.animate = saveAnimate;
5474   }
5475   currentMove = forwardMostMove;
5476   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5477   ClearPremoveHighlights();
5478   DrawPosition(TRUE, boards[currentMove]);
5479 }
5480
5481 void
5482 MovePV (int x, int y, int h)
5483 { // step through PV based on mouse coordinates (called on mouse move)
5484   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5485
5486   // we must somehow check if right button is still down (might be released off board!)
5487   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5488   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5489   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5490   if(!step) return;
5491   lastX = x; lastY = y;
5492
5493   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5494   if(endPV < 0) return;
5495   if(y < margin) step = 1; else
5496   if(y > h - margin) step = -1;
5497   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5498   currentMove += step;
5499   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5500   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5501                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5502   DrawPosition(FALSE, boards[currentMove]);
5503 }
5504
5505
5506 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5507 // All positions will have equal probability, but the current method will not provide a unique
5508 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5509 #define DARK 1
5510 #define LITE 2
5511 #define ANY 3
5512
5513 int squaresLeft[4];
5514 int piecesLeft[(int)BlackPawn];
5515 int seed, nrOfShuffles;
5516
5517 void
5518 GetPositionNumber ()
5519 {       // sets global variable seed
5520         int i;
5521
5522         seed = appData.defaultFrcPosition;
5523         if(seed < 0) { // randomize based on time for negative FRC position numbers
5524                 for(i=0; i<50; i++) seed += random();
5525                 seed = random() ^ random() >> 8 ^ random() << 8;
5526                 if(seed<0) seed = -seed;
5527         }
5528 }
5529
5530 int
5531 put (Board board, int pieceType, int rank, int n, int shade)
5532 // put the piece on the (n-1)-th empty squares of the given shade
5533 {
5534         int i;
5535
5536         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5537                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5538                         board[rank][i] = (ChessSquare) pieceType;
5539                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5540                         squaresLeft[ANY]--;
5541                         piecesLeft[pieceType]--;
5542                         return i;
5543                 }
5544         }
5545         return -1;
5546 }
5547
5548
5549 void
5550 AddOnePiece (Board board, int pieceType, int rank, int shade)
5551 // calculate where the next piece goes, (any empty square), and put it there
5552 {
5553         int i;
5554
5555         i = seed % squaresLeft[shade];
5556         nrOfShuffles *= squaresLeft[shade];
5557         seed /= squaresLeft[shade];
5558         put(board, pieceType, rank, i, shade);
5559 }
5560
5561 void
5562 AddTwoPieces (Board board, int pieceType, int rank)
5563 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5564 {
5565         int i, n=squaresLeft[ANY], j=n-1, k;
5566
5567         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5568         i = seed % k;  // pick one
5569         nrOfShuffles *= k;
5570         seed /= k;
5571         while(i >= j) i -= j--;
5572         j = n - 1 - j; i += j;
5573         put(board, pieceType, rank, j, ANY);
5574         put(board, pieceType, rank, i, ANY);
5575 }
5576
5577 void
5578 SetUpShuffle (Board board, int number)
5579 {
5580         int i, p, first=1;
5581
5582         GetPositionNumber(); nrOfShuffles = 1;
5583
5584         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5585         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5586         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5587
5588         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5589
5590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5591             p = (int) board[0][i];
5592             if(p < (int) BlackPawn) piecesLeft[p] ++;
5593             board[0][i] = EmptySquare;
5594         }
5595
5596         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5597             // shuffles restricted to allow normal castling put KRR first
5598             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5599                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5600             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5601                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5602             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5603                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5604             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5605                 put(board, WhiteRook, 0, 0, ANY);
5606             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5607         }
5608
5609         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5610             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5611             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5612                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5613                 while(piecesLeft[p] >= 2) {
5614                     AddOnePiece(board, p, 0, LITE);
5615                     AddOnePiece(board, p, 0, DARK);
5616                 }
5617                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5618             }
5619
5620         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5621             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5622             // but we leave King and Rooks for last, to possibly obey FRC restriction
5623             if(p == (int)WhiteRook) continue;
5624             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5625             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5626         }
5627
5628         // now everything is placed, except perhaps King (Unicorn) and Rooks
5629
5630         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5631             // Last King gets castling rights
5632             while(piecesLeft[(int)WhiteUnicorn]) {
5633                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637             while(piecesLeft[(int)WhiteKing]) {
5638                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5639                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5640             }
5641
5642
5643         } else {
5644             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5645             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5646         }
5647
5648         // Only Rooks can be left; simply place them all
5649         while(piecesLeft[(int)WhiteRook]) {
5650                 i = put(board, WhiteRook, 0, 0, ANY);
5651                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5652                         if(first) {
5653                                 first=0;
5654                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5655                         }
5656                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5657                 }
5658         }
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5660             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5661         }
5662
5663         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5664 }
5665
5666 int
5667 SetCharTable (char *table, const char * map)
5668 /* [HGM] moved here from winboard.c because of its general usefulness */
5669 /*       Basically a safe strcpy that uses the last character as King */
5670 {
5671     int result = FALSE; int NrPieces;
5672
5673     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5674                     && NrPieces >= 12 && !(NrPieces&1)) {
5675         int i; /* [HGM] Accept even length from 12 to 34 */
5676
5677         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5678         for( i=0; i<NrPieces/2-1; i++ ) {
5679             table[i] = map[i];
5680             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5681         }
5682         table[(int) WhiteKing]  = map[NrPieces/2-1];
5683         table[(int) BlackKing]  = map[NrPieces-1];
5684
5685         result = TRUE;
5686     }
5687
5688     return result;
5689 }
5690
5691 void
5692 Prelude (Board board)
5693 {       // [HGM] superchess: random selection of exo-pieces
5694         int i, j, k; ChessSquare p;
5695         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5696
5697         GetPositionNumber(); // use FRC position number
5698
5699         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5700             SetCharTable(pieceToChar, appData.pieceToCharTable);
5701             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5702                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5703         }
5704
5705         j = seed%4;                 seed /= 4;
5706         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%3 + (seed%3 >= j); seed /= 3;
5710         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5711         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5712         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5713         j = seed%3;                 seed /= 3;
5714         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5717         j = seed%2 + (seed%2 >= j); seed /= 2;
5718         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5719         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5720         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5721         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5722         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5723         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5724         put(board, exoPieces[0],    0, 0, ANY);
5725         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5726 }
5727
5728 void
5729 InitPosition (int redraw)
5730 {
5731     ChessSquare (* pieces)[BOARD_FILES];
5732     int i, j, pawnRow, overrule,
5733     oldx = gameInfo.boardWidth,
5734     oldy = gameInfo.boardHeight,
5735     oldh = gameInfo.holdingsWidth;
5736     static int oldv;
5737
5738     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5739
5740     /* [AS] Initialize pv info list [HGM] and game status */
5741     {
5742         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5743             pvInfoList[i].depth = 0;
5744             boards[i][EP_STATUS] = EP_NONE;
5745             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5746         }
5747
5748         initialRulePlies = 0; /* 50-move counter start */
5749
5750         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5751         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5752     }
5753
5754
5755     /* [HGM] logic here is completely changed. In stead of full positions */
5756     /* the initialized data only consist of the two backranks. The switch */
5757     /* selects which one we will use, which is than copied to the Board   */
5758     /* initialPosition, which for the rest is initialized by Pawns and    */
5759     /* empty squares. This initial position is then copied to boards[0],  */
5760     /* possibly after shuffling, so that it remains available.            */
5761
5762     gameInfo.holdingsWidth = 0; /* default board sizes */
5763     gameInfo.boardWidth    = 8;
5764     gameInfo.boardHeight   = 8;
5765     gameInfo.holdingsSize  = 0;
5766     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5767     for(i=0; i<BOARD_FILES-2; i++)
5768       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5769     initialPosition[EP_STATUS] = EP_NONE;
5770     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5771     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5772          SetCharTable(pieceNickName, appData.pieceNickNames);
5773     else SetCharTable(pieceNickName, "............");
5774     pieces = FIDEArray;
5775
5776     switch (gameInfo.variant) {
5777     case VariantFischeRandom:
5778       shuffleOpenings = TRUE;
5779     default:
5780       break;
5781     case VariantShatranj:
5782       pieces = ShatranjArray;
5783       nrCastlingRights = 0;
5784       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5785       break;
5786     case VariantMakruk:
5787       pieces = makrukArray;
5788       nrCastlingRights = 0;
5789       startedFromSetupPosition = TRUE;
5790       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5791       break;
5792     case VariantTwoKings:
5793       pieces = twoKingsArray;
5794       break;
5795     case VariantGrand:
5796       pieces = GrandArray;
5797       nrCastlingRights = 0;
5798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5799       gameInfo.boardWidth = 10;
5800       gameInfo.boardHeight = 10;
5801       gameInfo.holdingsSize = 7;
5802       break;
5803     case VariantCapaRandom:
5804       shuffleOpenings = TRUE;
5805     case VariantCapablanca:
5806       pieces = CapablancaArray;
5807       gameInfo.boardWidth = 10;
5808       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5809       break;
5810     case VariantGothic:
5811       pieces = GothicArray;
5812       gameInfo.boardWidth = 10;
5813       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5814       break;
5815     case VariantSChess:
5816       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5817       gameInfo.holdingsSize = 7;
5818       break;
5819     case VariantJanus:
5820       pieces = JanusArray;
5821       gameInfo.boardWidth = 10;
5822       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5823       nrCastlingRights = 6;
5824         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5825         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5826         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5827         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5828         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5829         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5830       break;
5831     case VariantFalcon:
5832       pieces = FalconArray;
5833       gameInfo.boardWidth = 10;
5834       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5835       break;
5836     case VariantXiangqi:
5837       pieces = XiangqiArray;
5838       gameInfo.boardWidth  = 9;
5839       gameInfo.boardHeight = 10;
5840       nrCastlingRights = 0;
5841       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5842       break;
5843     case VariantShogi:
5844       pieces = ShogiArray;
5845       gameInfo.boardWidth  = 9;
5846       gameInfo.boardHeight = 9;
5847       gameInfo.holdingsSize = 7;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5850       break;
5851     case VariantCourier:
5852       pieces = CourierArray;
5853       gameInfo.boardWidth  = 12;
5854       nrCastlingRights = 0;
5855       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5856       break;
5857     case VariantKnightmate:
5858       pieces = KnightmateArray;
5859       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5860       break;
5861     case VariantSpartan:
5862       pieces = SpartanArray;
5863       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5864       break;
5865     case VariantFairy:
5866       pieces = fairyArray;
5867       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5868       break;
5869     case VariantGreat:
5870       pieces = GreatArray;
5871       gameInfo.boardWidth = 10;
5872       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5873       gameInfo.holdingsSize = 8;
5874       break;
5875     case VariantSuper:
5876       pieces = FIDEArray;
5877       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5878       gameInfo.holdingsSize = 8;
5879       startedFromSetupPosition = TRUE;
5880       break;
5881     case VariantCrazyhouse:
5882     case VariantBughouse:
5883       pieces = FIDEArray;
5884       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5885       gameInfo.holdingsSize = 5;
5886       break;
5887     case VariantWildCastle:
5888       pieces = FIDEArray;
5889       /* !!?shuffle with kings guaranteed to be on d or e file */
5890       shuffleOpenings = 1;
5891       break;
5892     case VariantNoCastle:
5893       pieces = FIDEArray;
5894       nrCastlingRights = 0;
5895       /* !!?unconstrained back-rank shuffle */
5896       shuffleOpenings = 1;
5897       break;
5898     }
5899
5900     overrule = 0;
5901     if(appData.NrFiles >= 0) {
5902         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5903         gameInfo.boardWidth = appData.NrFiles;
5904     }
5905     if(appData.NrRanks >= 0) {
5906         gameInfo.boardHeight = appData.NrRanks;
5907     }
5908     if(appData.holdingsSize >= 0) {
5909         i = appData.holdingsSize;
5910         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5911         gameInfo.holdingsSize = i;
5912     }
5913     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5914     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5915         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5916
5917     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5918     if(pawnRow < 1) pawnRow = 1;
5919     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5920
5921     /* User pieceToChar list overrules defaults */
5922     if(appData.pieceToCharTable != NULL)
5923         SetCharTable(pieceToChar, appData.pieceToCharTable);
5924
5925     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5926
5927         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5928             s = (ChessSquare) 0; /* account holding counts in guard band */
5929         for( i=0; i<BOARD_HEIGHT; i++ )
5930             initialPosition[i][j] = s;
5931
5932         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5933         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5934         initialPosition[pawnRow][j] = WhitePawn;
5935         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5936         if(gameInfo.variant == VariantXiangqi) {
5937             if(j&1) {
5938                 initialPosition[pawnRow][j] =
5939                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5940                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5941                    initialPosition[2][j] = WhiteCannon;
5942                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5943                 }
5944             }
5945         }
5946         if(gameInfo.variant == VariantGrand) {
5947             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5948                initialPosition[0][j] = WhiteRook;
5949                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5950             }
5951         }
5952         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5953     }
5954     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5955
5956             j=BOARD_LEFT+1;
5957             initialPosition[1][j] = WhiteBishop;
5958             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5959             j=BOARD_RGHT-2;
5960             initialPosition[1][j] = WhiteRook;
5961             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5962     }
5963
5964     if( nrCastlingRights == -1) {
5965         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5966         /*       This sets default castling rights from none to normal corners   */
5967         /* Variants with other castling rights must set them themselves above    */
5968         nrCastlingRights = 6;
5969
5970         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5971         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5972         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5973         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5974         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5975         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5976      }
5977
5978      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5979      if(gameInfo.variant == VariantGreat) { // promotion commoners
5980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5981         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5983         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5984      }
5985      if( gameInfo.variant == VariantSChess ) {
5986       initialPosition[1][0] = BlackMarshall;
5987       initialPosition[2][0] = BlackAngel;
5988       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5989       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5990       initialPosition[1][1] = initialPosition[2][1] = 
5991       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5992      }
5993   if (appData.debugMode) {
5994     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5995   }
5996     if(shuffleOpenings) {
5997         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5998         startedFromSetupPosition = TRUE;
5999     }
6000     if(startedFromPositionFile) {
6001       /* [HGM] loadPos: use PositionFile for every new game */
6002       CopyBoard(initialPosition, filePosition);
6003       for(i=0; i<nrCastlingRights; i++)
6004           initialRights[i] = filePosition[CASTLING][i];
6005       startedFromSetupPosition = TRUE;
6006     }
6007
6008     CopyBoard(boards[0], initialPosition);
6009
6010     if(oldx != gameInfo.boardWidth ||
6011        oldy != gameInfo.boardHeight ||
6012        oldv != gameInfo.variant ||
6013        oldh != gameInfo.holdingsWidth
6014                                          )
6015             InitDrawingSizes(-2 ,0);
6016
6017     oldv = gameInfo.variant;
6018     if (redraw)
6019       DrawPosition(TRUE, boards[currentMove]);
6020 }
6021
6022 void
6023 SendBoard (ChessProgramState *cps, int moveNum)
6024 {
6025     char message[MSG_SIZ];
6026
6027     if (cps->useSetboard) {
6028       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6029       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6030       SendToProgram(message, cps);
6031       free(fen);
6032
6033     } else {
6034       ChessSquare *bp;
6035       int i, j, left=0, right=BOARD_WIDTH;
6036       /* Kludge to set black to move, avoiding the troublesome and now
6037        * deprecated "black" command.
6038        */
6039       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6040         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6041
6042       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6043
6044       SendToProgram("edit\n", cps);
6045       SendToProgram("#\n", cps);
6046       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6047         bp = &boards[moveNum][i][left];
6048         for (j = left; j < right; j++, bp++) {
6049           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6050           if ((int) *bp < (int) BlackPawn) {
6051             if(j == BOARD_RGHT+1)
6052                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6053             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6054             if(message[0] == '+' || message[0] == '~') {
6055               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6056                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6057                         AAA + j, ONE + i);
6058             }
6059             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6060                 message[1] = BOARD_RGHT   - 1 - j + '1';
6061                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6062             }
6063             SendToProgram(message, cps);
6064           }
6065         }
6066       }
6067
6068       SendToProgram("c\n", cps);
6069       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6070         bp = &boards[moveNum][i][left];
6071         for (j = left; j < right; j++, bp++) {
6072           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6073           if (((int) *bp != (int) EmptySquare)
6074               && ((int) *bp >= (int) BlackPawn)) {
6075             if(j == BOARD_LEFT-2)
6076                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6077             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6078                     AAA + j, ONE + i);
6079             if(message[0] == '+' || message[0] == '~') {
6080               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6081                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6082                         AAA + j, ONE + i);
6083             }
6084             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6085                 message[1] = BOARD_RGHT   - 1 - j + '1';
6086                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6087             }
6088             SendToProgram(message, cps);
6089           }
6090         }
6091       }
6092
6093       SendToProgram(".\n", cps);
6094     }
6095     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6096 }
6097
6098 ChessSquare
6099 DefaultPromoChoice (int white)
6100 {
6101     ChessSquare result;
6102     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6103         result = WhiteFerz; // no choice
6104     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6105         result= WhiteKing; // in Suicide Q is the last thing we want
6106     else if(gameInfo.variant == VariantSpartan)
6107         result = white ? WhiteQueen : WhiteAngel;
6108     else result = WhiteQueen;
6109     if(!white) result = WHITE_TO_BLACK result;
6110     return result;
6111 }
6112
6113 static int autoQueen; // [HGM] oneclick
6114
6115 int
6116 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6117 {
6118     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6119     /* [HGM] add Shogi promotions */
6120     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6121     ChessSquare piece;
6122     ChessMove moveType;
6123     Boolean premove;
6124
6125     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6126     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6127
6128     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6129       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6130         return FALSE;
6131
6132     piece = boards[currentMove][fromY][fromX];
6133     if(gameInfo.variant == VariantShogi) {
6134         promotionZoneSize = BOARD_HEIGHT/3;
6135         highestPromotingPiece = (int)WhiteFerz;
6136     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6137         promotionZoneSize = 3;
6138     }
6139
6140     // Treat Lance as Pawn when it is not representing Amazon
6141     if(gameInfo.variant != VariantSuper) {
6142         if(piece == WhiteLance) piece = WhitePawn; else
6143         if(piece == BlackLance) piece = BlackPawn;
6144     }
6145
6146     // next weed out all moves that do not touch the promotion zone at all
6147     if((int)piece >= BlackPawn) {
6148         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6149              return FALSE;
6150         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6151     } else {
6152         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6153            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6154     }
6155
6156     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6157
6158     // weed out mandatory Shogi promotions
6159     if(gameInfo.variant == VariantShogi) {
6160         if(piece >= BlackPawn) {
6161             if(toY == 0 && piece == BlackPawn ||
6162                toY == 0 && piece == BlackQueen ||
6163                toY <= 1 && piece == BlackKnight) {
6164                 *promoChoice = '+';
6165                 return FALSE;
6166             }
6167         } else {
6168             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6169                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6170                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6171                 *promoChoice = '+';
6172                 return FALSE;
6173             }
6174         }
6175     }
6176
6177     // weed out obviously illegal Pawn moves
6178     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6179         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6180         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6181         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6182         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6183         // note we are not allowed to test for valid (non-)capture, due to premove
6184     }
6185
6186     // we either have a choice what to promote to, or (in Shogi) whether to promote
6187     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6188         *promoChoice = PieceToChar(BlackFerz);  // no choice
6189         return FALSE;
6190     }
6191     // no sense asking what we must promote to if it is going to explode...
6192     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6193         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6194         return FALSE;
6195     }
6196     // give caller the default choice even if we will not make it
6197     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6198     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6199     if(        sweepSelect && gameInfo.variant != VariantGreat
6200                            && gameInfo.variant != VariantGrand
6201                            && gameInfo.variant != VariantSuper) return FALSE;
6202     if(autoQueen) return FALSE; // predetermined
6203
6204     // suppress promotion popup on illegal moves that are not premoves
6205     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6206               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6207     if(appData.testLegality && !premove) {
6208         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6209                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6210         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6211             return FALSE;
6212     }
6213
6214     return TRUE;
6215 }
6216
6217 int
6218 InPalace (int row, int column)
6219 {   /* [HGM] for Xiangqi */
6220     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6221          column < (BOARD_WIDTH + 4)/2 &&
6222          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6223     return FALSE;
6224 }
6225
6226 int
6227 PieceForSquare (int x, int y)
6228 {
6229   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6230      return -1;
6231   else
6232      return boards[currentMove][y][x];
6233 }
6234
6235 int
6236 OKToStartUserMove (int x, int y)
6237 {
6238     ChessSquare from_piece;
6239     int white_piece;
6240
6241     if (matchMode) return FALSE;
6242     if (gameMode == EditPosition) return TRUE;
6243
6244     if (x >= 0 && y >= 0)
6245       from_piece = boards[currentMove][y][x];
6246     else
6247       from_piece = EmptySquare;
6248
6249     if (from_piece == EmptySquare) return FALSE;
6250
6251     white_piece = (int)from_piece >= (int)WhitePawn &&
6252       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6253
6254     switch (gameMode) {
6255       case AnalyzeFile:
6256       case TwoMachinesPlay:
6257       case EndOfGame:
6258         return FALSE;
6259
6260       case IcsObserving:
6261       case IcsIdle:
6262         return FALSE;
6263
6264       case MachinePlaysWhite:
6265       case IcsPlayingBlack:
6266         if (appData.zippyPlay) return FALSE;
6267         if (white_piece) {
6268             DisplayMoveError(_("You are playing Black"));
6269             return FALSE;
6270         }
6271         break;
6272
6273       case MachinePlaysBlack:
6274       case IcsPlayingWhite:
6275         if (appData.zippyPlay) return FALSE;
6276         if (!white_piece) {
6277             DisplayMoveError(_("You are playing White"));
6278             return FALSE;
6279         }
6280         break;
6281
6282       case PlayFromGameFile:
6283             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6284       case EditGame:
6285         if (!white_piece && WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is White's turn"));
6287             return FALSE;
6288         }
6289         if (white_piece && !WhiteOnMove(currentMove)) {
6290             DisplayMoveError(_("It is Black's turn"));
6291             return FALSE;
6292         }
6293         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6294             /* Editing correspondence game history */
6295             /* Could disallow this or prompt for confirmation */
6296             cmailOldMove = -1;
6297         }
6298         break;
6299
6300       case BeginningOfGame:
6301         if (appData.icsActive) return FALSE;
6302         if (!appData.noChessProgram) {
6303             if (!white_piece) {
6304                 DisplayMoveError(_("You are playing White"));
6305                 return FALSE;
6306             }
6307         }
6308         break;
6309
6310       case Training:
6311         if (!white_piece && WhiteOnMove(currentMove)) {
6312             DisplayMoveError(_("It is White's turn"));
6313             return FALSE;
6314         }
6315         if (white_piece && !WhiteOnMove(currentMove)) {
6316             DisplayMoveError(_("It is Black's turn"));
6317             return FALSE;
6318         }
6319         break;
6320
6321       default:
6322       case IcsExamining:
6323         break;
6324     }
6325     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6326         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6327         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6328         && gameMode != AnalyzeFile && gameMode != Training) {
6329         DisplayMoveError(_("Displayed position is not current"));
6330         return FALSE;
6331     }
6332     return TRUE;
6333 }
6334
6335 Boolean
6336 OnlyMove (int *x, int *y, Boolean captures) 
6337 {
6338     DisambiguateClosure cl;
6339     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6340     switch(gameMode) {
6341       case MachinePlaysBlack:
6342       case IcsPlayingWhite:
6343       case BeginningOfGame:
6344         if(!WhiteOnMove(currentMove)) return FALSE;
6345         break;
6346       case MachinePlaysWhite:
6347       case IcsPlayingBlack:
6348         if(WhiteOnMove(currentMove)) return FALSE;
6349         break;
6350       case EditGame:
6351         break;
6352       default:
6353         return FALSE;
6354     }
6355     cl.pieceIn = EmptySquare;
6356     cl.rfIn = *y;
6357     cl.ffIn = *x;
6358     cl.rtIn = -1;
6359     cl.ftIn = -1;
6360     cl.promoCharIn = NULLCHAR;
6361     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6362     if( cl.kind == NormalMove ||
6363         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6364         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6365         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6366       fromX = cl.ff;
6367       fromY = cl.rf;
6368       *x = cl.ft;
6369       *y = cl.rt;
6370       return TRUE;
6371     }
6372     if(cl.kind != ImpossibleMove) return FALSE;
6373     cl.pieceIn = EmptySquare;
6374     cl.rfIn = -1;
6375     cl.ffIn = -1;
6376     cl.rtIn = *y;
6377     cl.ftIn = *x;
6378     cl.promoCharIn = NULLCHAR;
6379     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6380     if( cl.kind == NormalMove ||
6381         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6382         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6383         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6384       fromX = cl.ff;
6385       fromY = cl.rf;
6386       *x = cl.ft;
6387       *y = cl.rt;
6388       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6389       return TRUE;
6390     }
6391     return FALSE;
6392 }
6393
6394 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6395 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6396 int lastLoadGameUseList = FALSE;
6397 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6398 ChessMove lastLoadGameStart = EndOfFile;
6399
6400 void
6401 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6402 {
6403     ChessMove moveType;
6404     ChessSquare pdown, pup;
6405
6406     /* Check if the user is playing in turn.  This is complicated because we
6407        let the user "pick up" a piece before it is his turn.  So the piece he
6408        tried to pick up may have been captured by the time he puts it down!
6409        Therefore we use the color the user is supposed to be playing in this
6410        test, not the color of the piece that is currently on the starting
6411        square---except in EditGame mode, where the user is playing both
6412        sides; fortunately there the capture race can't happen.  (It can
6413        now happen in IcsExamining mode, but that's just too bad.  The user
6414        will get a somewhat confusing message in that case.)
6415        */
6416
6417     switch (gameMode) {
6418       case AnalyzeFile:
6419       case TwoMachinesPlay:
6420       case EndOfGame:
6421       case IcsObserving:
6422       case IcsIdle:
6423         /* We switched into a game mode where moves are not accepted,
6424            perhaps while the mouse button was down. */
6425         return;
6426
6427       case MachinePlaysWhite:
6428         /* User is moving for Black */
6429         if (WhiteOnMove(currentMove)) {
6430             DisplayMoveError(_("It is White's turn"));
6431             return;
6432         }
6433         break;
6434
6435       case MachinePlaysBlack:
6436         /* User is moving for White */
6437         if (!WhiteOnMove(currentMove)) {
6438             DisplayMoveError(_("It is Black's turn"));
6439             return;
6440         }
6441         break;
6442
6443       case PlayFromGameFile:
6444             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6445       case EditGame:
6446       case IcsExamining:
6447       case BeginningOfGame:
6448       case AnalyzeMode:
6449       case Training:
6450         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6451         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6452             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6453             /* User is moving for Black */
6454             if (WhiteOnMove(currentMove)) {
6455                 DisplayMoveError(_("It is White's turn"));
6456                 return;
6457             }
6458         } else {
6459             /* User is moving for White */
6460             if (!WhiteOnMove(currentMove)) {
6461                 DisplayMoveError(_("It is Black's turn"));
6462                 return;
6463             }
6464         }
6465         break;
6466
6467       case IcsPlayingBlack:
6468         /* User is moving for Black */
6469         if (WhiteOnMove(currentMove)) {
6470             if (!appData.premove) {
6471                 DisplayMoveError(_("It is White's turn"));
6472             } else if (toX >= 0 && toY >= 0) {
6473                 premoveToX = toX;
6474                 premoveToY = toY;
6475                 premoveFromX = fromX;
6476                 premoveFromY = fromY;
6477                 premovePromoChar = promoChar;
6478                 gotPremove = 1;
6479                 if (appData.debugMode)
6480                     fprintf(debugFP, "Got premove: fromX %d,"
6481                             "fromY %d, toX %d, toY %d\n",
6482                             fromX, fromY, toX, toY);
6483             }
6484             return;
6485         }
6486         break;
6487
6488       case IcsPlayingWhite:
6489         /* User is moving for White */
6490         if (!WhiteOnMove(currentMove)) {
6491             if (!appData.premove) {
6492                 DisplayMoveError(_("It is Black's turn"));
6493             } else if (toX >= 0 && toY >= 0) {
6494                 premoveToX = toX;
6495                 premoveToY = toY;
6496                 premoveFromX = fromX;
6497                 premoveFromY = fromY;
6498                 premovePromoChar = promoChar;
6499                 gotPremove = 1;
6500                 if (appData.debugMode)
6501                     fprintf(debugFP, "Got premove: fromX %d,"
6502                             "fromY %d, toX %d, toY %d\n",
6503                             fromX, fromY, toX, toY);
6504             }
6505             return;
6506         }
6507         break;
6508
6509       default:
6510         break;
6511
6512       case EditPosition:
6513         /* EditPosition, empty square, or different color piece;
6514            click-click move is possible */
6515         if (toX == -2 || toY == -2) {
6516             boards[0][fromY][fromX] = EmptySquare;
6517             DrawPosition(FALSE, boards[currentMove]);
6518             return;
6519         } else if (toX >= 0 && toY >= 0) {
6520             boards[0][toY][toX] = boards[0][fromY][fromX];
6521             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6522                 if(boards[0][fromY][0] != EmptySquare) {
6523                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6524                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6525                 }
6526             } else
6527             if(fromX == BOARD_RGHT+1) {
6528                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6529                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6530                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6531                 }
6532             } else
6533             boards[0][fromY][fromX] = EmptySquare;
6534             DrawPosition(FALSE, boards[currentMove]);
6535             return;
6536         }
6537         return;
6538     }
6539
6540     if(toX < 0 || toY < 0) return;
6541     pdown = boards[currentMove][fromY][fromX];
6542     pup = boards[currentMove][toY][toX];
6543
6544     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6545     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6546          if( pup != EmptySquare ) return;
6547          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6548            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6549                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6550            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6551            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6552            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6553            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6554          fromY = DROP_RANK;
6555     }
6556
6557     /* [HGM] always test for legality, to get promotion info */
6558     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6559                                          fromY, fromX, toY, toX, promoChar);
6560
6561     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6562
6563     /* [HGM] but possibly ignore an IllegalMove result */
6564     if (appData.testLegality) {
6565         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6566             DisplayMoveError(_("Illegal move"));
6567             return;
6568         }
6569     }
6570
6571     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6572 }
6573
6574 /* Common tail of UserMoveEvent and DropMenuEvent */
6575 int
6576 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6577 {
6578     char *bookHit = 0;
6579
6580     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6581         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6582         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6583         if(WhiteOnMove(currentMove)) {
6584             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6585         } else {
6586             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6587         }
6588     }
6589
6590     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6591        move type in caller when we know the move is a legal promotion */
6592     if(moveType == NormalMove && promoChar)
6593         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6594
6595     /* [HGM] <popupFix> The following if has been moved here from
6596        UserMoveEvent(). Because it seemed to belong here (why not allow
6597        piece drops in training games?), and because it can only be
6598        performed after it is known to what we promote. */
6599     if (gameMode == Training) {
6600       /* compare the move played on the board to the next move in the
6601        * game. If they match, display the move and the opponent's response.
6602        * If they don't match, display an error message.
6603        */
6604       int saveAnimate;
6605       Board testBoard;
6606       CopyBoard(testBoard, boards[currentMove]);
6607       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6608
6609       if (CompareBoards(testBoard, boards[currentMove+1])) {
6610         ForwardInner(currentMove+1);
6611
6612         /* Autoplay the opponent's response.
6613          * if appData.animate was TRUE when Training mode was entered,
6614          * the response will be animated.
6615          */
6616         saveAnimate = appData.animate;
6617         appData.animate = animateTraining;
6618         ForwardInner(currentMove+1);
6619         appData.animate = saveAnimate;
6620
6621         /* check for the end of the game */
6622         if (currentMove >= forwardMostMove) {
6623           gameMode = PlayFromGameFile;
6624           ModeHighlight();
6625           SetTrainingModeOff();
6626           DisplayInformation(_("End of game"));
6627         }
6628       } else {
6629         DisplayError(_("Incorrect move"), 0);
6630       }
6631       return 1;
6632     }
6633
6634   /* Ok, now we know that the move is good, so we can kill
6635      the previous line in Analysis Mode */
6636   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6637                                 && currentMove < forwardMostMove) {
6638     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6639     else forwardMostMove = currentMove;
6640   }
6641
6642   /* If we need the chess program but it's dead, restart it */
6643   ResurrectChessProgram();
6644
6645   /* A user move restarts a paused game*/
6646   if (pausing)
6647     PauseEvent();
6648
6649   thinkOutput[0] = NULLCHAR;
6650
6651   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6652
6653   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6654     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655     return 1;
6656   }
6657
6658   if (gameMode == BeginningOfGame) {
6659     if (appData.noChessProgram) {
6660       gameMode = EditGame;
6661       SetGameInfo();
6662     } else {
6663       char buf[MSG_SIZ];
6664       gameMode = MachinePlaysBlack;
6665       StartClocks();
6666       SetGameInfo();
6667       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6668       DisplayTitle(buf);
6669       if (first.sendName) {
6670         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6671         SendToProgram(buf, &first);
6672       }
6673       StartClocks();
6674     }
6675     ModeHighlight();
6676   }
6677
6678   /* Relay move to ICS or chess engine */
6679   if (appData.icsActive) {
6680     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6681         gameMode == IcsExamining) {
6682       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6683         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6684         SendToICS("draw ");
6685         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6686       }
6687       // also send plain move, in case ICS does not understand atomic claims
6688       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6689       ics_user_moved = 1;
6690     }
6691   } else {
6692     if (first.sendTime && (gameMode == BeginningOfGame ||
6693                            gameMode == MachinePlaysWhite ||
6694                            gameMode == MachinePlaysBlack)) {
6695       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6696     }
6697     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6698          // [HGM] book: if program might be playing, let it use book
6699         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6700         first.maybeThinking = TRUE;
6701     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6702         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6703         SendBoard(&first, currentMove+1);
6704     } else SendMoveToProgram(forwardMostMove-1, &first);
6705     if (currentMove == cmailOldMove + 1) {
6706       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6707     }
6708   }
6709
6710   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711
6712   switch (gameMode) {
6713   case EditGame:
6714     if(appData.testLegality)
6715     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6716     case MT_NONE:
6717     case MT_CHECK:
6718       break;
6719     case MT_CHECKMATE:
6720     case MT_STAINMATE:
6721       if (WhiteOnMove(currentMove)) {
6722         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6723       } else {
6724         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6725       }
6726       break;
6727     case MT_STALEMATE:
6728       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6729       break;
6730     }
6731     break;
6732
6733   case MachinePlaysBlack:
6734   case MachinePlaysWhite:
6735     /* disable certain menu options while machine is thinking */
6736     SetMachineThinkingEnables();
6737     break;
6738
6739   default:
6740     break;
6741   }
6742
6743   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6744   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6745
6746   if(bookHit) { // [HGM] book: simulate book reply
6747         static char bookMove[MSG_SIZ]; // a bit generous?
6748
6749         programStats.nodes = programStats.depth = programStats.time =
6750         programStats.score = programStats.got_only_move = 0;
6751         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6752
6753         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6754         strcat(bookMove, bookHit);
6755         HandleMachineMove(bookMove, &first);
6756   }
6757   return 1;
6758 }
6759
6760 void
6761 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6762 {
6763     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6764     Markers *m = (Markers *) closure;
6765     if(rf == fromY && ff == fromX)
6766         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6767                          || kind == WhiteCapturesEnPassant
6768                          || kind == BlackCapturesEnPassant);
6769     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6770 }
6771
6772 void
6773 MarkTargetSquares (int clear)
6774 {
6775   int x, y;
6776   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6777      !appData.testLegality || gameMode == EditPosition) return;
6778   if(clear) {
6779     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6780   } else {
6781     int capt = 0;
6782     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6783     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6784       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6785       if(capt)
6786       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6787     }
6788   }
6789   DrawPosition(TRUE, NULL);
6790 }
6791
6792 int
6793 Explode (Board board, int fromX, int fromY, int toX, int toY)
6794 {
6795     if(gameInfo.variant == VariantAtomic &&
6796        (board[toY][toX] != EmptySquare ||                     // capture?
6797         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6798                          board[fromY][fromX] == BlackPawn   )
6799       )) {
6800         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6801         return TRUE;
6802     }
6803     return FALSE;
6804 }
6805
6806 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6807
6808 int
6809 CanPromote (ChessSquare piece, int y)
6810 {
6811         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6812         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6813         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6814            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6815            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6816                                                   gameInfo.variant == VariantMakruk) return FALSE;
6817         return (piece == BlackPawn && y == 1 ||
6818                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6819                 piece == BlackLance && y == 1 ||
6820                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6821 }
6822
6823 void
6824 LeftClick (ClickType clickType, int xPix, int yPix)
6825 {
6826     int x, y;
6827     Boolean saveAnimate;
6828     static int second = 0, promotionChoice = 0, clearFlag = 0;
6829     char promoChoice = NULLCHAR;
6830     ChessSquare piece;
6831
6832     if(appData.seekGraph && appData.icsActive && loggedOn &&
6833         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6834         SeekGraphClick(clickType, xPix, yPix, 0);
6835         return;
6836     }
6837
6838     if (clickType == Press) ErrorPopDown();
6839
6840     x = EventToSquare(xPix, BOARD_WIDTH);
6841     y = EventToSquare(yPix, BOARD_HEIGHT);
6842     if (!flipView && y >= 0) {
6843         y = BOARD_HEIGHT - 1 - y;
6844     }
6845     if (flipView && x >= 0) {
6846         x = BOARD_WIDTH - 1 - x;
6847     }
6848
6849     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6850         defaultPromoChoice = promoSweep;
6851         promoSweep = EmptySquare;   // terminate sweep
6852         promoDefaultAltered = TRUE;
6853         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6854     }
6855
6856     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6857         if(clickType == Release) return; // ignore upclick of click-click destination
6858         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6859         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6860         if(gameInfo.holdingsWidth &&
6861                 (WhiteOnMove(currentMove)
6862                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6863                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6864             // click in right holdings, for determining promotion piece
6865             ChessSquare p = boards[currentMove][y][x];
6866             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6867             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6868             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6869                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6870                 fromX = fromY = -1;
6871                 return;
6872             }
6873         }
6874         DrawPosition(FALSE, boards[currentMove]);
6875         return;
6876     }
6877
6878     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6879     if(clickType == Press
6880             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6881               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6882               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6883         return;
6884
6885     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6886         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6887
6888     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6889         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6890                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6891         defaultPromoChoice = DefaultPromoChoice(side);
6892     }
6893
6894     autoQueen = appData.alwaysPromoteToQueen;
6895
6896     if (fromX == -1) {
6897       int originalY = y;
6898       gatingPiece = EmptySquare;
6899       if (clickType != Press) {
6900         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6901             DragPieceEnd(xPix, yPix); dragging = 0;
6902             DrawPosition(FALSE, NULL);
6903         }
6904         return;
6905       }
6906       fromX = x; fromY = y; toX = toY = -1;
6907       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6908          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6909          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6910             /* First square */
6911             if (OKToStartUserMove(fromX, fromY)) {
6912                 second = 0;
6913                 MarkTargetSquares(0);
6914                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6915                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6916                     promoSweep = defaultPromoChoice;
6917                     selectFlag = 0; lastX = xPix; lastY = yPix;
6918                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6919                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6920                 }
6921                 if (appData.highlightDragging) {
6922                     SetHighlights(fromX, fromY, -1, -1);
6923                 }
6924             } else fromX = fromY = -1;
6925             return;
6926         }
6927     }
6928
6929     /* fromX != -1 */
6930     if (clickType == Press && gameMode != EditPosition) {
6931         ChessSquare fromP;
6932         ChessSquare toP;
6933         int frc;
6934
6935         // ignore off-board to clicks
6936         if(y < 0 || x < 0) return;
6937
6938         /* Check if clicking again on the same color piece */
6939         fromP = boards[currentMove][fromY][fromX];
6940         toP = boards[currentMove][y][x];
6941         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6942         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6943              WhitePawn <= toP && toP <= WhiteKing &&
6944              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6945              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6946             (BlackPawn <= fromP && fromP <= BlackKing &&
6947              BlackPawn <= toP && toP <= BlackKing &&
6948              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6949              !(fromP == BlackKing && toP == BlackRook && frc))) {
6950             /* Clicked again on same color piece -- changed his mind */
6951             second = (x == fromX && y == fromY);
6952             promoDefaultAltered = FALSE;
6953             MarkTargetSquares(1);
6954            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6955             if (appData.highlightDragging) {
6956                 SetHighlights(x, y, -1, -1);
6957             } else {
6958                 ClearHighlights();
6959             }
6960             if (OKToStartUserMove(x, y)) {
6961                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6962                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6963                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6964                  gatingPiece = boards[currentMove][fromY][fromX];
6965                 else gatingPiece = EmptySquare;
6966                 fromX = x;
6967                 fromY = y; dragging = 1;
6968                 MarkTargetSquares(0);
6969                 DragPieceBegin(xPix, yPix, FALSE);
6970                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6971                     promoSweep = defaultPromoChoice;
6972                     selectFlag = 0; lastX = xPix; lastY = yPix;
6973                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6974                 }
6975             }
6976            }
6977            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6978            second = FALSE; 
6979         }
6980         // ignore clicks on holdings
6981         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6982     }
6983
6984     if (clickType == Release && x == fromX && y == fromY) {
6985         DragPieceEnd(xPix, yPix); dragging = 0;
6986         if(clearFlag) {
6987             // a deferred attempt to click-click move an empty square on top of a piece
6988             boards[currentMove][y][x] = EmptySquare;
6989             ClearHighlights();
6990             DrawPosition(FALSE, boards[currentMove]);
6991             fromX = fromY = -1; clearFlag = 0;
6992             return;
6993         }
6994         if (appData.animateDragging) {
6995             /* Undo animation damage if any */
6996             DrawPosition(FALSE, NULL);
6997         }
6998         if (second) {
6999             /* Second up/down in same square; just abort move */
7000             second = 0;
7001             fromX = fromY = -1;
7002             gatingPiece = EmptySquare;
7003             ClearHighlights();
7004             gotPremove = 0;
7005             ClearPremoveHighlights();
7006         } else {
7007             /* First upclick in same square; start click-click mode */
7008             SetHighlights(x, y, -1, -1);
7009         }
7010         return;
7011     }
7012
7013     clearFlag = 0;
7014
7015     /* we now have a different from- and (possibly off-board) to-square */
7016     /* Completed move */
7017     toX = x;
7018     toY = y;
7019     saveAnimate = appData.animate;
7020     MarkTargetSquares(1);
7021     if (clickType == Press) {
7022         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7023             // must be Edit Position mode with empty-square selected
7024             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7025             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7026             return;
7027         }
7028         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7029             ChessSquare piece = boards[currentMove][fromY][fromX];
7030             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7031             promoSweep = defaultPromoChoice;
7032             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7033             selectFlag = 0; lastX = xPix; lastY = yPix;
7034             Sweep(0); // Pawn that is going to promote: preview promotion piece
7035             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7036             DrawPosition(FALSE, boards[currentMove]);
7037             return;
7038         }
7039         /* Finish clickclick move */
7040         if (appData.animate || appData.highlightLastMove) {
7041             SetHighlights(fromX, fromY, toX, toY);
7042         } else {
7043             ClearHighlights();
7044         }
7045     } else {
7046         /* Finish drag move */
7047         if (appData.highlightLastMove) {
7048             SetHighlights(fromX, fromY, toX, toY);
7049         } else {
7050             ClearHighlights();
7051         }
7052         DragPieceEnd(xPix, yPix); dragging = 0;
7053         /* Don't animate move and drag both */
7054         appData.animate = FALSE;
7055     }
7056
7057     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7058     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7059         ChessSquare piece = boards[currentMove][fromY][fromX];
7060         if(gameMode == EditPosition && piece != EmptySquare &&
7061            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7062             int n;
7063
7064             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7065                 n = PieceToNumber(piece - (int)BlackPawn);
7066                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7067                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7068                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7069             } else
7070             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7071                 n = PieceToNumber(piece);
7072                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7073                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7074                 boards[currentMove][n][BOARD_WIDTH-2]++;
7075             }
7076             boards[currentMove][fromY][fromX] = EmptySquare;
7077         }
7078         ClearHighlights();
7079         fromX = fromY = -1;
7080         DrawPosition(TRUE, boards[currentMove]);
7081         return;
7082     }
7083
7084     // off-board moves should not be highlighted
7085     if(x < 0 || y < 0) ClearHighlights();
7086
7087     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7088
7089     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7090         SetHighlights(fromX, fromY, toX, toY);
7091         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7092             // [HGM] super: promotion to captured piece selected from holdings
7093             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7094             promotionChoice = TRUE;
7095             // kludge follows to temporarily execute move on display, without promoting yet
7096             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7097             boards[currentMove][toY][toX] = p;
7098             DrawPosition(FALSE, boards[currentMove]);
7099             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7100             boards[currentMove][toY][toX] = q;
7101             DisplayMessage("Click in holdings to choose piece", "");
7102             return;
7103         }
7104         PromotionPopUp();
7105     } else {
7106         int oldMove = currentMove;
7107         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7108         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7109         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7110         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7111            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7112             DrawPosition(TRUE, boards[currentMove]);
7113         fromX = fromY = -1;
7114     }
7115     appData.animate = saveAnimate;
7116     if (appData.animate || appData.animateDragging) {
7117         /* Undo animation damage if needed */
7118         DrawPosition(FALSE, NULL);
7119     }
7120 }
7121
7122 int
7123 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7124 {   // front-end-free part taken out of PieceMenuPopup
7125     int whichMenu; int xSqr, ySqr;
7126
7127     if(seekGraphUp) { // [HGM] seekgraph
7128         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7129         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7130         return -2;
7131     }
7132
7133     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7134          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7135         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7136         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7137         if(action == Press)   {
7138             originalFlip = flipView;
7139             flipView = !flipView; // temporarily flip board to see game from partners perspective
7140             DrawPosition(TRUE, partnerBoard);
7141             DisplayMessage(partnerStatus, "");
7142             partnerUp = TRUE;
7143         } else if(action == Release) {
7144             flipView = originalFlip;
7145             DrawPosition(TRUE, boards[currentMove]);
7146             partnerUp = FALSE;
7147         }
7148         return -2;
7149     }
7150
7151     xSqr = EventToSquare(x, BOARD_WIDTH);
7152     ySqr = EventToSquare(y, BOARD_HEIGHT);
7153     if (action == Release) {
7154         if(pieceSweep != EmptySquare) {
7155             EditPositionMenuEvent(pieceSweep, toX, toY);
7156             pieceSweep = EmptySquare;
7157         } else UnLoadPV(); // [HGM] pv
7158     }
7159     if (action != Press) return -2; // return code to be ignored
7160     switch (gameMode) {
7161       case IcsExamining:
7162         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7163       case EditPosition:
7164         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7165         if (xSqr < 0 || ySqr < 0) return -1;
7166         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7167         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7168         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7169         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7170         NextPiece(0);
7171         return 2; // grab
7172       case IcsObserving:
7173         if(!appData.icsEngineAnalyze) return -1;
7174       case IcsPlayingWhite:
7175       case IcsPlayingBlack:
7176         if(!appData.zippyPlay) goto noZip;
7177       case AnalyzeMode:
7178       case AnalyzeFile:
7179       case MachinePlaysWhite:
7180       case MachinePlaysBlack:
7181       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7182         if (!appData.dropMenu) {
7183           LoadPV(x, y);
7184           return 2; // flag front-end to grab mouse events
7185         }
7186         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7187            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7188       case EditGame:
7189       noZip:
7190         if (xSqr < 0 || ySqr < 0) return -1;
7191         if (!appData.dropMenu || appData.testLegality &&
7192             gameInfo.variant != VariantBughouse &&
7193             gameInfo.variant != VariantCrazyhouse) return -1;
7194         whichMenu = 1; // drop menu
7195         break;
7196       default:
7197         return -1;
7198     }
7199
7200     if (((*fromX = xSqr) < 0) ||
7201         ((*fromY = ySqr) < 0)) {
7202         *fromX = *fromY = -1;
7203         return -1;
7204     }
7205     if (flipView)
7206       *fromX = BOARD_WIDTH - 1 - *fromX;
7207     else
7208       *fromY = BOARD_HEIGHT - 1 - *fromY;
7209
7210     return whichMenu;
7211 }
7212
7213 void
7214 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7215 {
7216 //    char * hint = lastHint;
7217     FrontEndProgramStats stats;
7218
7219     stats.which = cps == &first ? 0 : 1;
7220     stats.depth = cpstats->depth;
7221     stats.nodes = cpstats->nodes;
7222     stats.score = cpstats->score;
7223     stats.time = cpstats->time;
7224     stats.pv = cpstats->movelist;
7225     stats.hint = lastHint;
7226     stats.an_move_index = 0;
7227     stats.an_move_count = 0;
7228
7229     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7230         stats.hint = cpstats->move_name;
7231         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7232         stats.an_move_count = cpstats->nr_moves;
7233     }
7234
7235     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
7236
7237     SetProgramStats( &stats );
7238 }
7239
7240 void
7241 ClearEngineOutputPane (int which)
7242 {
7243     static FrontEndProgramStats dummyStats;
7244     dummyStats.which = which;
7245     dummyStats.pv = "#";
7246     SetProgramStats( &dummyStats );
7247 }
7248
7249 #define MAXPLAYERS 500
7250
7251 char *
7252 TourneyStandings (int display)
7253 {
7254     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7255     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7256     char result, *p, *names[MAXPLAYERS];
7257
7258     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7259         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7260     names[0] = p = strdup(appData.participants);
7261     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7262
7263     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7264
7265     while(result = appData.results[nr]) {
7266         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7267         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7268         wScore = bScore = 0;
7269         switch(result) {
7270           case '+': wScore = 2; break;
7271           case '-': bScore = 2; break;
7272           case '=': wScore = bScore = 1; break;
7273           case ' ':
7274           case '*': return strdup("busy"); // tourney not finished
7275         }
7276         score[w] += wScore;
7277         score[b] += bScore;
7278         games[w]++;
7279         games[b]++;
7280         nr++;
7281     }
7282     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7283     for(w=0; w<nPlayers; w++) {
7284         bScore = -1;
7285         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7286         ranking[w] = b; points[w] = bScore; score[b] = -2;
7287     }
7288     p = malloc(nPlayers*34+1);
7289     for(w=0; w<nPlayers && w<display; w++)
7290         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7291     free(names[0]);
7292     return p;
7293 }
7294
7295 void
7296 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7297 {       // count all piece types
7298         int p, f, r;
7299         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7300         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7302                 p = board[r][f];
7303                 pCnt[p]++;
7304                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7305                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7306                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7307                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7308                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7309                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7310         }
7311 }
7312
7313 int
7314 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7315 {
7316         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7317         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7318
7319         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7320         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7321         if(myPawns == 2 && nMine == 3) // KPP
7322             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7323         if(myPawns == 1 && nMine == 2) // KP
7324             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7325         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7326             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7327         if(myPawns) return FALSE;
7328         if(pCnt[WhiteRook+side])
7329             return pCnt[BlackRook-side] ||
7330                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7331                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7332                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7333         if(pCnt[WhiteCannon+side]) {
7334             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7335             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7336         }
7337         if(pCnt[WhiteKnight+side])
7338             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7339         return FALSE;
7340 }
7341
7342 int
7343 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7344 {
7345         VariantClass v = gameInfo.variant;
7346
7347         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7348         if(v == VariantShatranj) return TRUE; // always winnable through baring
7349         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7350         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7351
7352         if(v == VariantXiangqi) {
7353                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7354
7355                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7356                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7357                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7358                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7359                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7360                 if(stale) // we have at least one last-rank P plus perhaps C
7361                     return majors // KPKX
7362                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7363                 else // KCA*E*
7364                     return pCnt[WhiteFerz+side] // KCAK
7365                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7366                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7367                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7368
7369         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7370                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7371
7372                 if(nMine == 1) return FALSE; // bare King
7373                 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
7374                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7375                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7376                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7377                 if(pCnt[WhiteKnight+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7379                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7380                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7381                 if(nBishops)
7382                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7383                 if(pCnt[WhiteAlfil+side])
7384                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7385                 if(pCnt[WhiteWazir+side])
7386                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7387         }
7388
7389         return TRUE;
7390 }
7391
7392 int
7393 CompareWithRights (Board b1, Board b2)
7394 {
7395     int rights = 0;
7396     if(!CompareBoards(b1, b2)) return FALSE;
7397     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7398     /* compare castling rights */
7399     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7400            rights++; /* King lost rights, while rook still had them */
7401     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7402         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7403            rights++; /* but at least one rook lost them */
7404     }
7405     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7406            rights++;
7407     if( b1[CASTLING][5] != NoRights ) {
7408         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7409            rights++;
7410     }
7411     return rights == 0;
7412 }
7413
7414 int
7415 Adjudicate (ChessProgramState *cps)
7416 {       // [HGM] some adjudications useful with buggy engines
7417         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7418         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7419         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7420         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7421         int k, count = 0; static int bare = 1;
7422         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7423         Boolean canAdjudicate = !appData.icsActive;
7424
7425         // most tests only when we understand the game, i.e. legality-checking on
7426             if( appData.testLegality )
7427             {   /* [HGM] Some more adjudications for obstinate engines */
7428                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7429                 static int moveCount = 6;
7430                 ChessMove result;
7431                 char *reason = NULL;
7432
7433                 /* Count what is on board. */
7434                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7435
7436                 /* Some material-based adjudications that have to be made before stalemate test */
7437                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7438                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7439                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7440                      if(canAdjudicate && appData.checkMates) {
7441                          if(engineOpponent)
7442                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7443                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7444                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7445                          return 1;
7446                      }
7447                 }
7448
7449                 /* Bare King in Shatranj (loses) or Losers (wins) */
7450                 if( nrW == 1 || nrB == 1) {
7451                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7452                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7453                      if(canAdjudicate && appData.checkMates) {
7454                          if(engineOpponent)
7455                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7456                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7457                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7458                          return 1;
7459                      }
7460                   } else
7461                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7462                   {    /* bare King */
7463                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7464                         if(canAdjudicate && appData.checkMates) {
7465                             /* but only adjudicate if adjudication enabled */
7466                             if(engineOpponent)
7467                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7468                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7469                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7470                             return 1;
7471                         }
7472                   }
7473                 } else bare = 1;
7474
7475
7476             // don't wait for engine to announce game end if we can judge ourselves
7477             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7478               case MT_CHECK:
7479                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7480                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7481                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7482                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7483                             checkCnt++;
7484                         if(checkCnt >= 2) {
7485                             reason = "Xboard adjudication: 3rd check";
7486                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7487                             break;
7488                         }
7489                     }
7490                 }
7491               case MT_NONE:
7492               default:
7493                 break;
7494               case MT_STALEMATE:
7495               case MT_STAINMATE:
7496                 reason = "Xboard adjudication: Stalemate";
7497                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7498                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7499                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7500                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7501                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7502                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7503                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7504                                                                         EP_CHECKMATE : EP_WINS);
7505                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7506                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7507                 }
7508                 break;
7509               case MT_CHECKMATE:
7510                 reason = "Xboard adjudication: Checkmate";
7511                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7512                 break;
7513             }
7514
7515                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7516                     case EP_STALEMATE:
7517                         result = GameIsDrawn; break;
7518                     case EP_CHECKMATE:
7519                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7520                     case EP_WINS:
7521                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7522                     default:
7523                         result = EndOfFile;
7524                 }
7525                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7526                     if(engineOpponent)
7527                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7528                     GameEnds( result, reason, GE_XBOARD );
7529                     return 1;
7530                 }
7531
7532                 /* Next absolutely insufficient mating material. */
7533                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7534                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7535                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7536
7537                      /* always flag draws, for judging claims */
7538                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7539
7540                      if(canAdjudicate && appData.materialDraws) {
7541                          /* but only adjudicate them if adjudication enabled */
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7547                          return 1;
7548                      }
7549                 }
7550
7551                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7552                 if(gameInfo.variant == VariantXiangqi ?
7553                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7554                  : nrW + nrB == 4 &&
7555                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7556                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7557                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7558                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7559                    ) ) {
7560                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7561                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7562                           if(engineOpponent) {
7563                             SendToProgram("force\n", engineOpponent); // suppress reply
7564                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7565                           }
7566                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7567                           return 1;
7568                      }
7569                 } else moveCount = 6;
7570             }
7571
7572         // Repetition draws and 50-move rule can be applied independently of legality testing
7573
7574                 /* Check for rep-draws */
7575                 count = 0;
7576                 for(k = forwardMostMove-2;
7577                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7578                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7579                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7580                     k-=2)
7581                 {   int rights=0;
7582                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7583                         /* compare castling rights */
7584                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7585                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7586                                 rights++; /* King lost rights, while rook still had them */
7587                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7588                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7589                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7590                                    rights++; /* but at least one rook lost them */
7591                         }
7592                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7593                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7594                                 rights++;
7595                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7596                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7597                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7598                                    rights++;
7599                         }
7600                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7601                             && appData.drawRepeats > 1) {
7602                              /* adjudicate after user-specified nr of repeats */
7603                              int result = GameIsDrawn;
7604                              char *details = "XBoard adjudication: repetition draw";
7605                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7606                                 // [HGM] xiangqi: check for forbidden perpetuals
7607                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7608                                 for(m=forwardMostMove; m>k; m-=2) {
7609                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7610                                         ourPerpetual = 0; // the current mover did not always check
7611                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7612                                         hisPerpetual = 0; // the opponent did not always check
7613                                 }
7614                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7615                                                                         ourPerpetual, hisPerpetual);
7616                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7617                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7618                                     details = "Xboard adjudication: perpetual checking";
7619                                 } else
7620                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7621                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7622                                 } else
7623                                 // Now check for perpetual chases
7624                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7625                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7626                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7627                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7628                                         static char resdet[MSG_SIZ];
7629                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7630                                         details = resdet;
7631                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7632                                     } else
7633                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7634                                         break; // Abort repetition-checking loop.
7635                                 }
7636                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7637                              }
7638                              if(engineOpponent) {
7639                                SendToProgram("force\n", engineOpponent); // suppress reply
7640                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7641                              }
7642                              GameEnds( result, details, GE_XBOARD );
7643                              return 1;
7644                         }
7645                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7646                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7647                     }
7648                 }
7649
7650                 /* Now we test for 50-move draws. Determine ply count */
7651                 count = forwardMostMove;
7652                 /* look for last irreversble move */
7653                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7654                     count--;
7655                 /* if we hit starting position, add initial plies */
7656                 if( count == backwardMostMove )
7657                     count -= initialRulePlies;
7658                 count = forwardMostMove - count;
7659                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7660                         // adjust reversible move counter for checks in Xiangqi
7661                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7662                         if(i < backwardMostMove) i = backwardMostMove;
7663                         while(i <= forwardMostMove) {
7664                                 lastCheck = inCheck; // check evasion does not count
7665                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7666                                 if(inCheck || lastCheck) count--; // check does not count
7667                                 i++;
7668                         }
7669                 }
7670                 if( count >= 100)
7671                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7672                          /* this is used to judge if draw claims are legal */
7673                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7674                          if(engineOpponent) {
7675                            SendToProgram("force\n", engineOpponent); // suppress reply
7676                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7677                          }
7678                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7679                          return 1;
7680                 }
7681
7682                 /* if draw offer is pending, treat it as a draw claim
7683                  * when draw condition present, to allow engines a way to
7684                  * claim draws before making their move to avoid a race
7685                  * condition occurring after their move
7686                  */
7687                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7688                          char *p = NULL;
7689                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7690                              p = "Draw claim: 50-move rule";
7691                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7692                              p = "Draw claim: 3-fold repetition";
7693                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7694                              p = "Draw claim: insufficient mating material";
7695                          if( p != NULL && canAdjudicate) {
7696                              if(engineOpponent) {
7697                                SendToProgram("force\n", engineOpponent); // suppress reply
7698                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                              }
7700                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7701                              return 1;
7702                          }
7703                 }
7704
7705                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7706                     if(engineOpponent) {
7707                       SendToProgram("force\n", engineOpponent); // suppress reply
7708                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7709                     }
7710                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7711                     return 1;
7712                 }
7713         return 0;
7714 }
7715
7716 char *
7717 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7718 {   // [HGM] book: this routine intercepts moves to simulate book replies
7719     char *bookHit = NULL;
7720
7721     //first determine if the incoming move brings opponent into his book
7722     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7723         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7724     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7725     if(bookHit != NULL && !cps->bookSuspend) {
7726         // make sure opponent is not going to reply after receiving move to book position
7727         SendToProgram("force\n", cps);
7728         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7729     }
7730     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7731     // now arrange restart after book miss
7732     if(bookHit) {
7733         // after a book hit we never send 'go', and the code after the call to this routine
7734         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7735         char buf[MSG_SIZ], *move = bookHit;
7736         if(cps->useSAN) {
7737             int fromX, fromY, toX, toY;
7738             char promoChar;
7739             ChessMove moveType;
7740             move = buf + 30;
7741             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7744                                     PosFlags(forwardMostMove),
7745                                     fromY, fromX, toY, toX, promoChar, move);
7746             } else {
7747                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748                 bookHit = NULL;
7749             }
7750         }
7751         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7752         SendToProgram(buf, cps);
7753         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7754     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7755         SendToProgram("go\n", cps);
7756         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7757     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7758         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7759             SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7761     }
7762     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 }
7764
7765 char *savedMessage;
7766 ChessProgramState *savedState;
7767 void
7768 DeferredBookMove (void)
7769 {
7770         if(savedState->lastPing != savedState->lastPong)
7771                     ScheduleDelayedEvent(DeferredBookMove, 10);
7772         else
7773         HandleMachineMove(savedMessage, savedState);
7774 }
7775
7776 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7777
7778 void
7779 HandleMachineMove (char *message, ChessProgramState *cps)
7780 {
7781     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7782     char realname[MSG_SIZ];
7783     int fromX, fromY, toX, toY;
7784     ChessMove moveType;
7785     char promoChar;
7786     char *p, *pv=buf1;
7787     int machineWhite;
7788     char *bookHit;
7789
7790     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7791         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7792         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7793             DisplayError(_("Invalid pairing from pairing engine"), 0);
7794             return;
7795         }
7796         pairingReceived = 1;
7797         NextMatchGame();
7798         return; // Skim the pairing messages here.
7799     }
7800
7801     cps->userError = 0;
7802
7803 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7804     /*
7805      * Kludge to ignore BEL characters
7806      */
7807     while (*message == '\007') message++;
7808
7809     /*
7810      * [HGM] engine debug message: ignore lines starting with '#' character
7811      */
7812     if(cps->debug && *message == '#') return;
7813
7814     /*
7815      * Look for book output
7816      */
7817     if (cps == &first && bookRequested) {
7818         if (message[0] == '\t' || message[0] == ' ') {
7819             /* Part of the book output is here; append it */
7820             strcat(bookOutput, message);
7821             strcat(bookOutput, "  \n");
7822             return;
7823         } else if (bookOutput[0] != NULLCHAR) {
7824             /* All of book output has arrived; display it */
7825             char *p = bookOutput;
7826             while (*p != NULLCHAR) {
7827                 if (*p == '\t') *p = ' ';
7828                 p++;
7829             }
7830             DisplayInformation(bookOutput);
7831             bookRequested = FALSE;
7832             /* Fall through to parse the current output */
7833         }
7834     }
7835
7836     /*
7837      * Look for machine move.
7838      */
7839     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7840         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7841     {
7842         /* This method is only useful on engines that support ping */
7843         if (cps->lastPing != cps->lastPong) {
7844           if (gameMode == BeginningOfGame) {
7845             /* Extra move from before last new; ignore */
7846             if (appData.debugMode) {
7847                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7848             }
7849           } else {
7850             if (appData.debugMode) {
7851                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7852                         cps->which, gameMode);
7853             }
7854
7855             SendToProgram("undo\n", cps);
7856           }
7857           return;
7858         }
7859
7860         switch (gameMode) {
7861           case BeginningOfGame:
7862             /* Extra move from before last reset; ignore */
7863             if (appData.debugMode) {
7864                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7865             }
7866             return;
7867
7868           case EndOfGame:
7869           case IcsIdle:
7870           default:
7871             /* Extra move after we tried to stop.  The mode test is
7872                not a reliable way of detecting this problem, but it's
7873                the best we can do on engines that don't support ping.
7874             */
7875             if (appData.debugMode) {
7876                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7877                         cps->which, gameMode);
7878             }
7879             SendToProgram("undo\n", cps);
7880             return;
7881
7882           case MachinePlaysWhite:
7883           case IcsPlayingWhite:
7884             machineWhite = TRUE;
7885             break;
7886
7887           case MachinePlaysBlack:
7888           case IcsPlayingBlack:
7889             machineWhite = FALSE;
7890             break;
7891
7892           case TwoMachinesPlay:
7893             machineWhite = (cps->twoMachinesColor[0] == 'w');
7894             break;
7895         }
7896         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7897             if (appData.debugMode) {
7898                 fprintf(debugFP,
7899                         "Ignoring move out of turn by %s, gameMode %d"
7900                         ", forwardMost %d\n",
7901                         cps->which, gameMode, forwardMostMove);
7902             }
7903             return;
7904         }
7905
7906         if(cps->alphaRank) AlphaRank(machineMove, 4);
7907         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7908                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7909             /* Machine move could not be parsed; ignore it. */
7910           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7911                     machineMove, _(cps->which));
7912             DisplayError(buf1, 0);
7913             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7914                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7915             if (gameMode == TwoMachinesPlay) {
7916               GameEnds(machineWhite ? BlackWins : WhiteWins,
7917                        buf1, GE_XBOARD);
7918             }
7919             return;
7920         }
7921
7922         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7923         /* So we have to redo legality test with true e.p. status here,  */
7924         /* to make sure an illegal e.p. capture does not slip through,   */
7925         /* to cause a forfeit on a justified illegal-move complaint      */
7926         /* of the opponent.                                              */
7927         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7928            ChessMove moveType;
7929            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7930                              fromY, fromX, toY, toX, promoChar);
7931             if(moveType == IllegalMove) {
7932               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7933                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7934                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7935                            buf1, GE_XBOARD);
7936                 return;
7937            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7938            /* [HGM] Kludge to handle engines that send FRC-style castling
7939               when they shouldn't (like TSCP-Gothic) */
7940            switch(moveType) {
7941              case WhiteASideCastleFR:
7942              case BlackASideCastleFR:
7943                toX+=2;
7944                currentMoveString[2]++;
7945                break;
7946              case WhiteHSideCastleFR:
7947              case BlackHSideCastleFR:
7948                toX--;
7949                currentMoveString[2]--;
7950                break;
7951              default: ; // nothing to do, but suppresses warning of pedantic compilers
7952            }
7953         }
7954         hintRequested = FALSE;
7955         lastHint[0] = NULLCHAR;
7956         bookRequested = FALSE;
7957         /* Program may be pondering now */
7958         cps->maybeThinking = TRUE;
7959         if (cps->sendTime == 2) cps->sendTime = 1;
7960         if (cps->offeredDraw) cps->offeredDraw--;
7961
7962         /* [AS] Save move info*/
7963         pvInfoList[ forwardMostMove ].score = programStats.score;
7964         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7965         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7966
7967         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7968
7969         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7970         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7971             int count = 0;
7972
7973             while( count < adjudicateLossPlies ) {
7974                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7975
7976                 if( count & 1 ) {
7977                     score = -score; /* Flip score for winning side */
7978                 }
7979
7980                 if( score > adjudicateLossThreshold ) {
7981                     break;
7982                 }
7983
7984                 count++;
7985             }
7986
7987             if( count >= adjudicateLossPlies ) {
7988                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7989
7990                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7991                     "Xboard adjudication",
7992                     GE_XBOARD );
7993
7994                 return;
7995             }
7996         }
7997
7998         if(Adjudicate(cps)) {
7999             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8000             return; // [HGM] adjudicate: for all automatic game ends
8001         }
8002
8003 #if ZIPPY
8004         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8005             first.initDone) {
8006           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8007                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8008                 SendToICS("draw ");
8009                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8010           }
8011           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8012           ics_user_moved = 1;
8013           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8014                 char buf[3*MSG_SIZ];
8015
8016                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8017                         programStats.score / 100.,
8018                         programStats.depth,
8019                         programStats.time / 100.,
8020                         (unsigned int)programStats.nodes,
8021                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8022                         programStats.movelist);
8023                 SendToICS(buf);
8024 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8025           }
8026         }
8027 #endif
8028
8029         /* [AS] Clear stats for next move */
8030         ClearProgramStats();
8031         thinkOutput[0] = NULLCHAR;
8032         hiddenThinkOutputState = 0;
8033
8034         bookHit = NULL;
8035         if (gameMode == TwoMachinesPlay) {
8036             /* [HGM] relaying draw offers moved to after reception of move */
8037             /* and interpreting offer as claim if it brings draw condition */
8038             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8039                 SendToProgram("draw\n", cps->other);
8040             }
8041             if (cps->other->sendTime) {
8042                 SendTimeRemaining(cps->other,
8043                                   cps->other->twoMachinesColor[0] == 'w');
8044             }
8045             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8046             if (firstMove && !bookHit) {
8047                 firstMove = FALSE;
8048                 if (cps->other->useColors) {
8049                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8050                 }
8051                 SendToProgram("go\n", cps->other);
8052             }
8053             cps->other->maybeThinking = TRUE;
8054         }
8055
8056         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8057
8058         if (!pausing && appData.ringBellAfterMoves) {
8059             RingBell();
8060         }
8061
8062         /*
8063          * Reenable menu items that were disabled while
8064          * machine was thinking
8065          */
8066         if (gameMode != TwoMachinesPlay)
8067             SetUserThinkingEnables();
8068
8069         // [HGM] book: after book hit opponent has received move and is now in force mode
8070         // force the book reply into it, and then fake that it outputted this move by jumping
8071         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8072         if(bookHit) {
8073                 static char bookMove[MSG_SIZ]; // a bit generous?
8074
8075                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8076                 strcat(bookMove, bookHit);
8077                 message = bookMove;
8078                 cps = cps->other;
8079                 programStats.nodes = programStats.depth = programStats.time =
8080                 programStats.score = programStats.got_only_move = 0;
8081                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8082
8083                 if(cps->lastPing != cps->lastPong) {
8084                     savedMessage = message; // args for deferred call
8085                     savedState = cps;
8086                     ScheduleDelayedEvent(DeferredBookMove, 10);
8087                     return;
8088                 }
8089                 goto FakeBookMove;
8090         }
8091
8092         return;
8093     }
8094
8095     /* Set special modes for chess engines.  Later something general
8096      *  could be added here; for now there is just one kludge feature,
8097      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8098      *  when "xboard" is given as an interactive command.
8099      */
8100     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8101         cps->useSigint = FALSE;
8102         cps->useSigterm = FALSE;
8103     }
8104     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8105       ParseFeatures(message+8, cps);
8106       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8107     }
8108
8109     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8110                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8111       int dummy, s=6; char buf[MSG_SIZ];
8112       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8113       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8114       if(startedFromSetupPosition) return;
8115       ParseFEN(boards[0], &dummy, message+s);
8116       DrawPosition(TRUE, boards[0]);
8117       startedFromSetupPosition = TRUE;
8118       return;
8119     }
8120     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8121      * want this, I was asked to put it in, and obliged.
8122      */
8123     if (!strncmp(message, "setboard ", 9)) {
8124         Board initial_position;
8125
8126         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8127
8128         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8129             DisplayError(_("Bad FEN received from engine"), 0);
8130             return ;
8131         } else {
8132            Reset(TRUE, FALSE);
8133            CopyBoard(boards[0], initial_position);
8134            initialRulePlies = FENrulePlies;
8135            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8136            else gameMode = MachinePlaysBlack;
8137            DrawPosition(FALSE, boards[currentMove]);
8138         }
8139         return;
8140     }
8141
8142     /*
8143      * Look for communication commands
8144      */
8145     if (!strncmp(message, "telluser ", 9)) {
8146         if(message[9] == '\\' && message[10] == '\\')
8147             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8148         PlayTellSound();
8149         DisplayNote(message + 9);
8150         return;
8151     }
8152     if (!strncmp(message, "tellusererror ", 14)) {
8153         cps->userError = 1;
8154         if(message[14] == '\\' && message[15] == '\\')
8155             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8156         PlayTellSound();
8157         DisplayError(message + 14, 0);
8158         return;
8159     }
8160     if (!strncmp(message, "tellopponent ", 13)) {
8161       if (appData.icsActive) {
8162         if (loggedOn) {
8163           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164           SendToICS(buf1);
8165         }
8166       } else {
8167         DisplayNote(message + 13);
8168       }
8169       return;
8170     }
8171     if (!strncmp(message, "tellothers ", 11)) {
8172       if (appData.icsActive) {
8173         if (loggedOn) {
8174           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8175           SendToICS(buf1);
8176         }
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellall ", 8)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184           SendToICS(buf1);
8185         }
8186       } else {
8187         DisplayNote(message + 8);
8188       }
8189       return;
8190     }
8191     if (strncmp(message, "warning", 7) == 0) {
8192         /* Undocumented feature, use tellusererror in new code */
8193         DisplayError(message, 0);
8194         return;
8195     }
8196     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8197         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8198         strcat(realname, " query");
8199         AskQuestion(realname, buf2, buf1, cps->pr);
8200         return;
8201     }
8202     /* Commands from the engine directly to ICS.  We don't allow these to be
8203      *  sent until we are logged on. Crafty kibitzes have been known to
8204      *  interfere with the login process.
8205      */
8206     if (loggedOn) {
8207         if (!strncmp(message, "tellics ", 8)) {
8208             SendToICS(message + 8);
8209             SendToICS("\n");
8210             return;
8211         }
8212         if (!strncmp(message, "tellicsnoalias ", 15)) {
8213             SendToICS(ics_prefix);
8214             SendToICS(message + 15);
8215             SendToICS("\n");
8216             return;
8217         }
8218         /* The following are for backward compatibility only */
8219         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8220             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8221             SendToICS(ics_prefix);
8222             SendToICS(message);
8223             SendToICS("\n");
8224             return;
8225         }
8226     }
8227     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228         return;
8229     }
8230     /*
8231      * If the move is illegal, cancel it and redraw the board.
8232      * Also deal with other error cases.  Matching is rather loose
8233      * here to accommodate engines written before the spec.
8234      */
8235     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8236         strncmp(message, "Error", 5) == 0) {
8237         if (StrStr(message, "name") ||
8238             StrStr(message, "rating") || StrStr(message, "?") ||
8239             StrStr(message, "result") || StrStr(message, "board") ||
8240             StrStr(message, "bk") || StrStr(message, "computer") ||
8241             StrStr(message, "variant") || StrStr(message, "hint") ||
8242             StrStr(message, "random") || StrStr(message, "depth") ||
8243             StrStr(message, "accepted")) {
8244             return;
8245         }
8246         if (StrStr(message, "protover")) {
8247           /* Program is responding to input, so it's apparently done
8248              initializing, and this error message indicates it is
8249              protocol version 1.  So we don't need to wait any longer
8250              for it to initialize and send feature commands. */
8251           FeatureDone(cps, 1);
8252           cps->protocolVersion = 1;
8253           return;
8254         }
8255         cps->maybeThinking = FALSE;
8256
8257         if (StrStr(message, "draw")) {
8258             /* Program doesn't have "draw" command */
8259             cps->sendDrawOffers = 0;
8260             return;
8261         }
8262         if (cps->sendTime != 1 &&
8263             (StrStr(message, "time") || StrStr(message, "otim"))) {
8264           /* Program apparently doesn't have "time" or "otim" command */
8265           cps->sendTime = 0;
8266           return;
8267         }
8268         if (StrStr(message, "analyze")) {
8269             cps->analysisSupport = FALSE;
8270             cps->analyzing = FALSE;
8271 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8272             EditGameEvent(); // [HGM] try to preserve loaded game
8273             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8274             DisplayError(buf2, 0);
8275             return;
8276         }
8277         if (StrStr(message, "(no matching move)st")) {
8278           /* Special kludge for GNU Chess 4 only */
8279           cps->stKludge = TRUE;
8280           SendTimeControl(cps, movesPerSession, timeControl,
8281                           timeIncrement, appData.searchDepth,
8282                           searchTime);
8283           return;
8284         }
8285         if (StrStr(message, "(no matching move)sd")) {
8286           /* Special kludge for GNU Chess 4 only */
8287           cps->sdKludge = TRUE;
8288           SendTimeControl(cps, movesPerSession, timeControl,
8289                           timeIncrement, appData.searchDepth,
8290                           searchTime);
8291           return;
8292         }
8293         if (!StrStr(message, "llegal")) {
8294             return;
8295         }
8296         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8297             gameMode == IcsIdle) return;
8298         if (forwardMostMove <= backwardMostMove) return;
8299         if (pausing) PauseEvent();
8300       if(appData.forceIllegal) {
8301             // [HGM] illegal: machine refused move; force position after move into it
8302           SendToProgram("force\n", cps);
8303           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8304                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8305                 // when black is to move, while there might be nothing on a2 or black
8306                 // might already have the move. So send the board as if white has the move.
8307                 // But first we must change the stm of the engine, as it refused the last move
8308                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8309                 if(WhiteOnMove(forwardMostMove)) {
8310                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8311                     SendBoard(cps, forwardMostMove); // kludgeless board
8312                 } else {
8313                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8314                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8315                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8316                 }
8317           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8318             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8319                  gameMode == TwoMachinesPlay)
8320               SendToProgram("go\n", cps);
8321             return;
8322       } else
8323         if (gameMode == PlayFromGameFile) {
8324             /* Stop reading this game file */
8325             gameMode = EditGame;
8326             ModeHighlight();
8327         }
8328         /* [HGM] illegal-move claim should forfeit game when Xboard */
8329         /* only passes fully legal moves                            */
8330         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8331             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8332                                 "False illegal-move claim", GE_XBOARD );
8333             return; // do not take back move we tested as valid
8334         }
8335         currentMove = forwardMostMove-1;
8336         DisplayMove(currentMove-1); /* before DisplayMoveError */
8337         SwitchClocks(forwardMostMove-1); // [HGM] race
8338         DisplayBothClocks();
8339         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8340                 parseList[currentMove], _(cps->which));
8341         DisplayMoveError(buf1);
8342         DrawPosition(FALSE, boards[currentMove]);
8343
8344         SetUserThinkingEnables();
8345         return;
8346     }
8347     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8348         /* Program has a broken "time" command that
8349            outputs a string not ending in newline.
8350            Don't use it. */
8351         cps->sendTime = 0;
8352     }
8353
8354     /*
8355      * If chess program startup fails, exit with an error message.
8356      * Attempts to recover here are futile.
8357      */
8358     if ((StrStr(message, "unknown host") != NULL)
8359         || (StrStr(message, "No remote directory") != NULL)
8360         || (StrStr(message, "not found") != NULL)
8361         || (StrStr(message, "No such file") != NULL)
8362         || (StrStr(message, "can't alloc") != NULL)
8363         || (StrStr(message, "Permission denied") != NULL)) {
8364
8365         cps->maybeThinking = FALSE;
8366         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8367                 _(cps->which), cps->program, cps->host, message);
8368         RemoveInputSource(cps->isr);
8369         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8370             if(cps == &first) appData.noChessProgram = TRUE;
8371             DisplayError(buf1, 0);
8372         }
8373         return;
8374     }
8375
8376     /*
8377      * Look for hint output
8378      */
8379     if (sscanf(message, "Hint: %s", buf1) == 1) {
8380         if (cps == &first && hintRequested) {
8381             hintRequested = FALSE;
8382             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8383                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8384                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8385                                     PosFlags(forwardMostMove),
8386                                     fromY, fromX, toY, toX, promoChar, buf1);
8387                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8388                 DisplayInformation(buf2);
8389             } else {
8390                 /* Hint move could not be parsed!? */
8391               snprintf(buf2, sizeof(buf2),
8392                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8393                         buf1, _(cps->which));
8394                 DisplayError(buf2, 0);
8395             }
8396         } else {
8397           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8398         }
8399         return;
8400     }
8401
8402     /*
8403      * Ignore other messages if game is not in progress
8404      */
8405     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8406         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8407
8408     /*
8409      * look for win, lose, draw, or draw offer
8410      */
8411     if (strncmp(message, "1-0", 3) == 0) {
8412         char *p, *q, *r = "";
8413         p = strchr(message, '{');
8414         if (p) {
8415             q = strchr(p, '}');
8416             if (q) {
8417                 *q = NULLCHAR;
8418                 r = p + 1;
8419             }
8420         }
8421         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8422         return;
8423     } else if (strncmp(message, "0-1", 3) == 0) {
8424         char *p, *q, *r = "";
8425         p = strchr(message, '{');
8426         if (p) {
8427             q = strchr(p, '}');
8428             if (q) {
8429                 *q = NULLCHAR;
8430                 r = p + 1;
8431             }
8432         }
8433         /* Kludge for Arasan 4.1 bug */
8434         if (strcmp(r, "Black resigns") == 0) {
8435             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8436             return;
8437         }
8438         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8439         return;
8440     } else if (strncmp(message, "1/2", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450
8451         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8452         return;
8453
8454     } else if (strncmp(message, "White resign", 12) == 0) {
8455         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "Black resign", 12) == 0) {
8458         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8459         return;
8460     } else if (strncmp(message, "White matches", 13) == 0 ||
8461                strncmp(message, "Black matches", 13) == 0   ) {
8462         /* [HGM] ignore GNUShogi noises */
8463         return;
8464     } else if (strncmp(message, "White", 5) == 0 &&
8465                message[5] != '(' &&
8466                StrStr(message, "Black") == NULL) {
8467         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "Black", 5) == 0 &&
8470                message[5] != '(') {
8471         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8472         return;
8473     } else if (strcmp(message, "resign") == 0 ||
8474                strcmp(message, "computer resigns") == 0) {
8475         switch (gameMode) {
8476           case MachinePlaysBlack:
8477           case IcsPlayingBlack:
8478             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8479             break;
8480           case MachinePlaysWhite:
8481           case IcsPlayingWhite:
8482             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8483             break;
8484           case TwoMachinesPlay:
8485             if (cps->twoMachinesColor[0] == 'w')
8486               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8487             else
8488               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8489             break;
8490           default:
8491             /* can't happen */
8492             break;
8493         }
8494         return;
8495     } else if (strncmp(message, "opponent mates", 14) == 0) {
8496         switch (gameMode) {
8497           case MachinePlaysBlack:
8498           case IcsPlayingBlack:
8499             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8500             break;
8501           case MachinePlaysWhite:
8502           case IcsPlayingWhite:
8503             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8504             break;
8505           case TwoMachinesPlay:
8506             if (cps->twoMachinesColor[0] == 'w')
8507               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8508             else
8509               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8510             break;
8511           default:
8512             /* can't happen */
8513             break;
8514         }
8515         return;
8516     } else if (strncmp(message, "computer mates", 14) == 0) {
8517         switch (gameMode) {
8518           case MachinePlaysBlack:
8519           case IcsPlayingBlack:
8520             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8521             break;
8522           case MachinePlaysWhite:
8523           case IcsPlayingWhite:
8524             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8525             break;
8526           case TwoMachinesPlay:
8527             if (cps->twoMachinesColor[0] == 'w')
8528               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8529             else
8530               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8531             break;
8532           default:
8533             /* can't happen */
8534             break;
8535         }
8536         return;
8537     } else if (strncmp(message, "checkmate", 9) == 0) {
8538         if (WhiteOnMove(forwardMostMove)) {
8539             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8540         } else {
8541             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8542         }
8543         return;
8544     } else if (strstr(message, "Draw") != NULL ||
8545                strstr(message, "game is a draw") != NULL) {
8546         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8547         return;
8548     } else if (strstr(message, "offer") != NULL &&
8549                strstr(message, "draw") != NULL) {
8550 #if ZIPPY
8551         if (appData.zippyPlay && first.initDone) {
8552             /* Relay offer to ICS */
8553             SendToICS(ics_prefix);
8554             SendToICS("draw\n");
8555         }
8556 #endif
8557         cps->offeredDraw = 2; /* valid until this engine moves twice */
8558         if (gameMode == TwoMachinesPlay) {
8559             if (cps->other->offeredDraw) {
8560                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8561             /* [HGM] in two-machine mode we delay relaying draw offer      */
8562             /* until after we also have move, to see if it is really claim */
8563             }
8564         } else if (gameMode == MachinePlaysWhite ||
8565                    gameMode == MachinePlaysBlack) {
8566           if (userOfferedDraw) {
8567             DisplayInformation(_("Machine accepts your draw offer"));
8568             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8569           } else {
8570             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8571           }
8572         }
8573     }
8574
8575
8576     /*
8577      * Look for thinking output
8578      */
8579     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8580           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8581                                 ) {
8582         int plylev, mvleft, mvtot, curscore, time;
8583         char mvname[MOVE_LEN];
8584         u64 nodes; // [DM]
8585         char plyext;
8586         int ignore = FALSE;
8587         int prefixHint = FALSE;
8588         mvname[0] = NULLCHAR;
8589
8590         switch (gameMode) {
8591           case MachinePlaysBlack:
8592           case IcsPlayingBlack:
8593             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8594             break;
8595           case MachinePlaysWhite:
8596           case IcsPlayingWhite:
8597             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8598             break;
8599           case AnalyzeMode:
8600           case AnalyzeFile:
8601             break;
8602           case IcsObserving: /* [DM] icsEngineAnalyze */
8603             if (!appData.icsEngineAnalyze) ignore = TRUE;
8604             break;
8605           case TwoMachinesPlay:
8606             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8607                 ignore = TRUE;
8608             }
8609             break;
8610           default:
8611             ignore = TRUE;
8612             break;
8613         }
8614
8615         if (!ignore) {
8616             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8617             buf1[0] = NULLCHAR;
8618             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8619                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8620
8621                 if (plyext != ' ' && plyext != '\t') {
8622                     time *= 100;
8623                 }
8624
8625                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8626                 if( cps->scoreIsAbsolute &&
8627                     ( gameMode == MachinePlaysBlack ||
8628                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8629                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8630                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8631                      !WhiteOnMove(currentMove)
8632                     ) )
8633                 {
8634                     curscore = -curscore;
8635                 }
8636
8637                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8638
8639                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8640                         char buf[MSG_SIZ];
8641                         FILE *f;
8642                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8643                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8644                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8645                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8646                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8647                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8648                                 fclose(f);
8649                         } else DisplayError(_("failed writing PV"), 0);
8650                 }
8651
8652                 tempStats.depth = plylev;
8653                 tempStats.nodes = nodes;
8654                 tempStats.time = time;
8655                 tempStats.score = curscore;
8656                 tempStats.got_only_move = 0;
8657
8658                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8659                         int ticklen;
8660
8661                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8662                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8663                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8664                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8665                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8666                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8667                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8668                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8669                 }
8670
8671                 /* Buffer overflow protection */
8672                 if (pv[0] != NULLCHAR) {
8673                     if (strlen(pv) >= sizeof(tempStats.movelist)
8674                         && appData.debugMode) {
8675                         fprintf(debugFP,
8676                                 "PV is too long; using the first %u bytes.\n",
8677                                 (unsigned) sizeof(tempStats.movelist) - 1);
8678                     }
8679
8680                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8681                 } else {
8682                     sprintf(tempStats.movelist, " no PV\n");
8683                 }
8684
8685                 if (tempStats.seen_stat) {
8686                     tempStats.ok_to_send = 1;
8687                 }
8688
8689                 if (strchr(tempStats.movelist, '(') != NULL) {
8690                     tempStats.line_is_book = 1;
8691                     tempStats.nr_moves = 0;
8692                     tempStats.moves_left = 0;
8693                 } else {
8694                     tempStats.line_is_book = 0;
8695                 }
8696
8697                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8698                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8699
8700                 SendProgramStatsToFrontend( cps, &tempStats );
8701
8702                 /*
8703                     [AS] Protect the thinkOutput buffer from overflow... this
8704                     is only useful if buf1 hasn't overflowed first!
8705                 */
8706                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8707                          plylev,
8708                          (gameMode == TwoMachinesPlay ?
8709                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8710                          ((double) curscore) / 100.0,
8711                          prefixHint ? lastHint : "",
8712                          prefixHint ? " " : "" );
8713
8714                 if( buf1[0] != NULLCHAR ) {
8715                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8716
8717                     if( strlen(pv) > max_len ) {
8718                         if( appData.debugMode) {
8719                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8720                         }
8721                         pv[max_len+1] = '\0';
8722                     }
8723
8724                     strcat( thinkOutput, pv);
8725                 }
8726
8727                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8728                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8729                     DisplayMove(currentMove - 1);
8730                 }
8731                 return;
8732
8733             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8734                 /* crafty (9.25+) says "(only move) <move>"
8735                  * if there is only 1 legal move
8736                  */
8737                 sscanf(p, "(only move) %s", buf1);
8738                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8739                 sprintf(programStats.movelist, "%s (only move)", buf1);
8740                 programStats.depth = 1;
8741                 programStats.nr_moves = 1;
8742                 programStats.moves_left = 1;
8743                 programStats.nodes = 1;
8744                 programStats.time = 1;
8745                 programStats.got_only_move = 1;
8746
8747                 /* Not really, but we also use this member to
8748                    mean "line isn't going to change" (Crafty
8749                    isn't searching, so stats won't change) */
8750                 programStats.line_is_book = 1;
8751
8752                 SendProgramStatsToFrontend( cps, &programStats );
8753
8754                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8755                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8756                     DisplayMove(currentMove - 1);
8757                 }
8758                 return;
8759             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8760                               &time, &nodes, &plylev, &mvleft,
8761                               &mvtot, mvname) >= 5) {
8762                 /* The stat01: line is from Crafty (9.29+) in response
8763                    to the "." command */
8764                 programStats.seen_stat = 1;
8765                 cps->maybeThinking = TRUE;
8766
8767                 if (programStats.got_only_move || !appData.periodicUpdates)
8768                   return;
8769
8770                 programStats.depth = plylev;
8771                 programStats.time = time;
8772                 programStats.nodes = nodes;
8773                 programStats.moves_left = mvleft;
8774                 programStats.nr_moves = mvtot;
8775                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8776                 programStats.ok_to_send = 1;
8777                 programStats.movelist[0] = '\0';
8778
8779                 SendProgramStatsToFrontend( cps, &programStats );
8780
8781                 return;
8782
8783             } else if (strncmp(message,"++",2) == 0) {
8784                 /* Crafty 9.29+ outputs this */
8785                 programStats.got_fail = 2;
8786                 return;
8787
8788             } else if (strncmp(message,"--",2) == 0) {
8789                 /* Crafty 9.29+ outputs this */
8790                 programStats.got_fail = 1;
8791                 return;
8792
8793             } else if (thinkOutput[0] != NULLCHAR &&
8794                        strncmp(message, "    ", 4) == 0) {
8795                 unsigned message_len;
8796
8797                 p = message;
8798                 while (*p && *p == ' ') p++;
8799
8800                 message_len = strlen( p );
8801
8802                 /* [AS] Avoid buffer overflow */
8803                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8804                     strcat(thinkOutput, " ");
8805                     strcat(thinkOutput, p);
8806                 }
8807
8808                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8809                     strcat(programStats.movelist, " ");
8810                     strcat(programStats.movelist, p);
8811                 }
8812
8813                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8814                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8815                     DisplayMove(currentMove - 1);
8816                 }
8817                 return;
8818             }
8819         }
8820         else {
8821             buf1[0] = NULLCHAR;
8822
8823             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8824                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8825             {
8826                 ChessProgramStats cpstats;
8827
8828                 if (plyext != ' ' && plyext != '\t') {
8829                     time *= 100;
8830                 }
8831
8832                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8833                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8834                     curscore = -curscore;
8835                 }
8836
8837                 cpstats.depth = plylev;
8838                 cpstats.nodes = nodes;
8839                 cpstats.time = time;
8840                 cpstats.score = curscore;
8841                 cpstats.got_only_move = 0;
8842                 cpstats.movelist[0] = '\0';
8843
8844                 if (buf1[0] != NULLCHAR) {
8845                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8846                 }
8847
8848                 cpstats.ok_to_send = 0;
8849                 cpstats.line_is_book = 0;
8850                 cpstats.nr_moves = 0;
8851                 cpstats.moves_left = 0;
8852
8853                 SendProgramStatsToFrontend( cps, &cpstats );
8854             }
8855         }
8856     }
8857 }
8858
8859
8860 /* Parse a game score from the character string "game", and
8861    record it as the history of the current game.  The game
8862    score is NOT assumed to start from the standard position.
8863    The display is not updated in any way.
8864    */
8865 void
8866 ParseGameHistory (char *game)
8867 {
8868     ChessMove moveType;
8869     int fromX, fromY, toX, toY, boardIndex;
8870     char promoChar;
8871     char *p, *q;
8872     char buf[MSG_SIZ];
8873
8874     if (appData.debugMode)
8875       fprintf(debugFP, "Parsing game history: %s\n", game);
8876
8877     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8878     gameInfo.site = StrSave(appData.icsHost);
8879     gameInfo.date = PGNDate();
8880     gameInfo.round = StrSave("-");
8881
8882     /* Parse out names of players */
8883     while (*game == ' ') game++;
8884     p = buf;
8885     while (*game != ' ') *p++ = *game++;
8886     *p = NULLCHAR;
8887     gameInfo.white = StrSave(buf);
8888     while (*game == ' ') game++;
8889     p = buf;
8890     while (*game != ' ' && *game != '\n') *p++ = *game++;
8891     *p = NULLCHAR;
8892     gameInfo.black = StrSave(buf);
8893
8894     /* Parse moves */
8895     boardIndex = blackPlaysFirst ? 1 : 0;
8896     yynewstr(game);
8897     for (;;) {
8898         yyboardindex = boardIndex;
8899         moveType = (ChessMove) Myylex();
8900         switch (moveType) {
8901           case IllegalMove:             /* maybe suicide chess, etc. */
8902   if (appData.debugMode) {
8903     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8904     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8905     setbuf(debugFP, NULL);
8906   }
8907           case WhitePromotion:
8908           case BlackPromotion:
8909           case WhiteNonPromotion:
8910           case BlackNonPromotion:
8911           case NormalMove:
8912           case WhiteCapturesEnPassant:
8913           case BlackCapturesEnPassant:
8914           case WhiteKingSideCastle:
8915           case WhiteQueenSideCastle:
8916           case BlackKingSideCastle:
8917           case BlackQueenSideCastle:
8918           case WhiteKingSideCastleWild:
8919           case WhiteQueenSideCastleWild:
8920           case BlackKingSideCastleWild:
8921           case BlackQueenSideCastleWild:
8922           /* PUSH Fabien */
8923           case WhiteHSideCastleFR:
8924           case WhiteASideCastleFR:
8925           case BlackHSideCastleFR:
8926           case BlackASideCastleFR:
8927           /* POP Fabien */
8928             fromX = currentMoveString[0] - AAA;
8929             fromY = currentMoveString[1] - ONE;
8930             toX = currentMoveString[2] - AAA;
8931             toY = currentMoveString[3] - ONE;
8932             promoChar = currentMoveString[4];
8933             break;
8934           case WhiteDrop:
8935           case BlackDrop:
8936             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8937             fromX = moveType == WhiteDrop ?
8938               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8939             (int) CharToPiece(ToLower(currentMoveString[0]));
8940             fromY = DROP_RANK;
8941             toX = currentMoveString[2] - AAA;
8942             toY = currentMoveString[3] - ONE;
8943             promoChar = NULLCHAR;
8944             break;
8945           case AmbiguousMove:
8946             /* bug? */
8947             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8948   if (appData.debugMode) {
8949     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8950     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8951     setbuf(debugFP, NULL);
8952   }
8953             DisplayError(buf, 0);
8954             return;
8955           case ImpossibleMove:
8956             /* bug? */
8957             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8958   if (appData.debugMode) {
8959     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8960     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8961     setbuf(debugFP, NULL);
8962   }
8963             DisplayError(buf, 0);
8964             return;
8965           case EndOfFile:
8966             if (boardIndex < backwardMostMove) {
8967                 /* Oops, gap.  How did that happen? */
8968                 DisplayError(_("Gap in move list"), 0);
8969                 return;
8970             }
8971             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8972             if (boardIndex > forwardMostMove) {
8973                 forwardMostMove = boardIndex;
8974             }
8975             return;
8976           case ElapsedTime:
8977             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8978                 strcat(parseList[boardIndex-1], " ");
8979                 strcat(parseList[boardIndex-1], yy_text);
8980             }
8981             continue;
8982           case Comment:
8983           case PGNTag:
8984           case NAG:
8985           default:
8986             /* ignore */
8987             continue;
8988           case WhiteWins:
8989           case BlackWins:
8990           case GameIsDrawn:
8991           case GameUnfinished:
8992             if (gameMode == IcsExamining) {
8993                 if (boardIndex < backwardMostMove) {
8994                     /* Oops, gap.  How did that happen? */
8995                     return;
8996                 }
8997                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8998                 return;
8999             }
9000             gameInfo.result = moveType;
9001             p = strchr(yy_text, '{');
9002             if (p == NULL) p = strchr(yy_text, '(');
9003             if (p == NULL) {
9004                 p = yy_text;
9005                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9006             } else {
9007                 q = strchr(p, *p == '{' ? '}' : ')');
9008                 if (q != NULL) *q = NULLCHAR;
9009                 p++;
9010             }
9011             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9012             gameInfo.resultDetails = StrSave(p);
9013             continue;
9014         }
9015         if (boardIndex >= forwardMostMove &&
9016             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9017             backwardMostMove = blackPlaysFirst ? 1 : 0;
9018             return;
9019         }
9020         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9021                                  fromY, fromX, toY, toX, promoChar,
9022                                  parseList[boardIndex]);
9023         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9024         /* currentMoveString is set as a side-effect of yylex */
9025         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9026         strcat(moveList[boardIndex], "\n");
9027         boardIndex++;
9028         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9029         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9030           case MT_NONE:
9031           case MT_STALEMATE:
9032           default:
9033             break;
9034           case MT_CHECK:
9035             if(gameInfo.variant != VariantShogi)
9036                 strcat(parseList[boardIndex - 1], "+");
9037             break;
9038           case MT_CHECKMATE:
9039           case MT_STAINMATE:
9040             strcat(parseList[boardIndex - 1], "#");
9041             break;
9042         }
9043     }
9044 }
9045
9046
9047 /* Apply a move to the given board  */
9048 void
9049 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9050 {
9051   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9052   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9053
9054     /* [HGM] compute & store e.p. status and castling rights for new position */
9055     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9056
9057       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9058       oldEP = (signed char)board[EP_STATUS];
9059       board[EP_STATUS] = EP_NONE;
9060
9061   if (fromY == DROP_RANK) {
9062         /* must be first */
9063         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9064             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9065             return;
9066         }
9067         piece = board[toY][toX] = (ChessSquare) fromX;
9068   } else {
9069       int i;
9070
9071       if( board[toY][toX] != EmptySquare )
9072            board[EP_STATUS] = EP_CAPTURE;
9073
9074       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9075            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9076                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9077       } else
9078       if( board[fromY][fromX] == WhitePawn ) {
9079            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9080                board[EP_STATUS] = EP_PAWN_MOVE;
9081            if( toY-fromY==2) {
9082                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9083                         gameInfo.variant != VariantBerolina || toX < fromX)
9084                       board[EP_STATUS] = toX | berolina;
9085                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9086                         gameInfo.variant != VariantBerolina || toX > fromX)
9087                       board[EP_STATUS] = toX;
9088            }
9089       } else
9090       if( board[fromY][fromX] == BlackPawn ) {
9091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9092                board[EP_STATUS] = EP_PAWN_MOVE;
9093            if( toY-fromY== -2) {
9094                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9095                         gameInfo.variant != VariantBerolina || toX < fromX)
9096                       board[EP_STATUS] = toX | berolina;
9097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9098                         gameInfo.variant != VariantBerolina || toX > fromX)
9099                       board[EP_STATUS] = toX;
9100            }
9101        }
9102
9103        for(i=0; i<nrCastlingRights; i++) {
9104            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9105               board[CASTLING][i] == toX   && castlingRank[i] == toY
9106              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9107        }
9108
9109      if (fromX == toX && fromY == toY) return;
9110
9111      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9112      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9113      if(gameInfo.variant == VariantKnightmate)
9114          king += (int) WhiteUnicorn - (int) WhiteKing;
9115
9116     /* Code added by Tord: */
9117     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9118     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9119         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9120       board[fromY][fromX] = EmptySquare;
9121       board[toY][toX] = EmptySquare;
9122       if((toX > fromX) != (piece == WhiteRook)) {
9123         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9124       } else {
9125         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9126       }
9127     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9128                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9129       board[fromY][fromX] = EmptySquare;
9130       board[toY][toX] = EmptySquare;
9131       if((toX > fromX) != (piece == BlackRook)) {
9132         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9133       } else {
9134         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9135       }
9136     /* End of code added by Tord */
9137
9138     } else if (board[fromY][fromX] == king
9139         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9140         && toY == fromY && toX > fromX+1) {
9141         board[fromY][fromX] = EmptySquare;
9142         board[toY][toX] = king;
9143         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9144         board[fromY][BOARD_RGHT-1] = EmptySquare;
9145     } else if (board[fromY][fromX] == king
9146         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9147                && toY == fromY && toX < fromX-1) {
9148         board[fromY][fromX] = EmptySquare;
9149         board[toY][toX] = king;
9150         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9151         board[fromY][BOARD_LEFT] = EmptySquare;
9152     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9153                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9154                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9155                ) {
9156         /* white pawn promotion */
9157         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9158         if(gameInfo.variant==VariantBughouse ||
9159            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9160             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9161         board[fromY][fromX] = EmptySquare;
9162     } else if ((fromY >= BOARD_HEIGHT>>1)
9163                && (toX != fromX)
9164                && gameInfo.variant != VariantXiangqi
9165                && gameInfo.variant != VariantBerolina
9166                && (board[fromY][fromX] == WhitePawn)
9167                && (board[toY][toX] == EmptySquare)) {
9168         board[fromY][fromX] = EmptySquare;
9169         board[toY][toX] = WhitePawn;
9170         captured = board[toY - 1][toX];
9171         board[toY - 1][toX] = EmptySquare;
9172     } else if ((fromY == BOARD_HEIGHT-4)
9173                && (toX == fromX)
9174                && gameInfo.variant == VariantBerolina
9175                && (board[fromY][fromX] == WhitePawn)
9176                && (board[toY][toX] == EmptySquare)) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = WhitePawn;
9179         if(oldEP & EP_BEROLIN_A) {
9180                 captured = board[fromY][fromX-1];
9181                 board[fromY][fromX-1] = EmptySquare;
9182         }else{  captured = board[fromY][fromX+1];
9183                 board[fromY][fromX+1] = EmptySquare;
9184         }
9185     } else if (board[fromY][fromX] == king
9186         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9187                && toY == fromY && toX > fromX+1) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = king;
9190         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9191         board[fromY][BOARD_RGHT-1] = EmptySquare;
9192     } else if (board[fromY][fromX] == king
9193         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9194                && toY == fromY && toX < fromX-1) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = king;
9197         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9198         board[fromY][BOARD_LEFT] = EmptySquare;
9199     } else if (fromY == 7 && fromX == 3
9200                && board[fromY][fromX] == BlackKing
9201                && toY == 7 && toX == 5) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = BlackKing;
9204         board[fromY][7] = EmptySquare;
9205         board[toY][4] = BlackRook;
9206     } else if (fromY == 7 && fromX == 3
9207                && board[fromY][fromX] == BlackKing
9208                && toY == 7 && toX == 1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = BlackKing;
9211         board[fromY][0] = EmptySquare;
9212         board[toY][2] = BlackRook;
9213     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9214                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9215                && toY < promoRank && promoChar
9216                ) {
9217         /* black pawn promotion */
9218         board[toY][toX] = CharToPiece(ToLower(promoChar));
9219         if(gameInfo.variant==VariantBughouse ||
9220            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9221             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9222         board[fromY][fromX] = EmptySquare;
9223     } else if ((fromY < BOARD_HEIGHT>>1)
9224                && (toX != fromX)
9225                && gameInfo.variant != VariantXiangqi
9226                && gameInfo.variant != VariantBerolina
9227                && (board[fromY][fromX] == BlackPawn)
9228                && (board[toY][toX] == EmptySquare)) {
9229         board[fromY][fromX] = EmptySquare;
9230         board[toY][toX] = BlackPawn;
9231         captured = board[toY + 1][toX];
9232         board[toY + 1][toX] = EmptySquare;
9233     } else if ((fromY == 3)
9234                && (toX == fromX)
9235                && gameInfo.variant == VariantBerolina
9236                && (board[fromY][fromX] == BlackPawn)
9237                && (board[toY][toX] == EmptySquare)) {
9238         board[fromY][fromX] = EmptySquare;
9239         board[toY][toX] = BlackPawn;
9240         if(oldEP & EP_BEROLIN_A) {
9241                 captured = board[fromY][fromX-1];
9242                 board[fromY][fromX-1] = EmptySquare;
9243         }else{  captured = board[fromY][fromX+1];
9244                 board[fromY][fromX+1] = EmptySquare;
9245         }
9246     } else {
9247         board[toY][toX] = board[fromY][fromX];
9248         board[fromY][fromX] = EmptySquare;
9249     }
9250   }
9251
9252     if (gameInfo.holdingsWidth != 0) {
9253
9254       /* !!A lot more code needs to be written to support holdings  */
9255       /* [HGM] OK, so I have written it. Holdings are stored in the */
9256       /* penultimate board files, so they are automaticlly stored   */
9257       /* in the game history.                                       */
9258       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9259                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9260         /* Delete from holdings, by decreasing count */
9261         /* and erasing image if necessary            */
9262         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9263         if(p < (int) BlackPawn) { /* white drop */
9264              p -= (int)WhitePawn;
9265                  p = PieceToNumber((ChessSquare)p);
9266              if(p >= gameInfo.holdingsSize) p = 0;
9267              if(--board[p][BOARD_WIDTH-2] <= 0)
9268                   board[p][BOARD_WIDTH-1] = EmptySquare;
9269              if((int)board[p][BOARD_WIDTH-2] < 0)
9270                         board[p][BOARD_WIDTH-2] = 0;
9271         } else {                  /* black drop */
9272              p -= (int)BlackPawn;
9273                  p = PieceToNumber((ChessSquare)p);
9274              if(p >= gameInfo.holdingsSize) p = 0;
9275              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9276                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9277              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9278                         board[BOARD_HEIGHT-1-p][1] = 0;
9279         }
9280       }
9281       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9282           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9283         /* [HGM] holdings: Add to holdings, if holdings exist */
9284         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9285                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9286                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9287         }
9288         p = (int) captured;
9289         if (p >= (int) BlackPawn) {
9290           p -= (int)BlackPawn;
9291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9292                   /* in Shogi restore piece to its original  first */
9293                   captured = (ChessSquare) (DEMOTED captured);
9294                   p = DEMOTED p;
9295           }
9296           p = PieceToNumber((ChessSquare)p);
9297           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9298           board[p][BOARD_WIDTH-2]++;
9299           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9300         } else {
9301           p -= (int)WhitePawn;
9302           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9303                   captured = (ChessSquare) (DEMOTED captured);
9304                   p = DEMOTED p;
9305           }
9306           p = PieceToNumber((ChessSquare)p);
9307           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9308           board[BOARD_HEIGHT-1-p][1]++;
9309           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9310         }
9311       }
9312     } else if (gameInfo.variant == VariantAtomic) {
9313       if (captured != EmptySquare) {
9314         int y, x;
9315         for (y = toY-1; y <= toY+1; y++) {
9316           for (x = toX-1; x <= toX+1; x++) {
9317             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9318                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9319               board[y][x] = EmptySquare;
9320             }
9321           }
9322         }
9323         board[toY][toX] = EmptySquare;
9324       }
9325     }
9326     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9327         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9328     } else
9329     if(promoChar == '+') {
9330         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9331         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9332     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9333         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9334     }
9335     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9336                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9337         // [HGM] superchess: take promotion piece out of holdings
9338         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9339         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9340             if(!--board[k][BOARD_WIDTH-2])
9341                 board[k][BOARD_WIDTH-1] = EmptySquare;
9342         } else {
9343             if(!--board[BOARD_HEIGHT-1-k][1])
9344                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9345         }
9346     }
9347
9348 }
9349
9350 /* Updates forwardMostMove */
9351 void
9352 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9353 {
9354 //    forwardMostMove++; // [HGM] bare: moved downstream
9355
9356     (void) CoordsToAlgebraic(boards[forwardMostMove],
9357                              PosFlags(forwardMostMove),
9358                              fromY, fromX, toY, toX, promoChar,
9359                              parseList[forwardMostMove]);
9360
9361     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9362         int timeLeft; static int lastLoadFlag=0; int king, piece;
9363         piece = boards[forwardMostMove][fromY][fromX];
9364         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9365         if(gameInfo.variant == VariantKnightmate)
9366             king += (int) WhiteUnicorn - (int) WhiteKing;
9367         if(forwardMostMove == 0) {
9368             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9369                 fprintf(serverMoves, "%s;", UserName());
9370             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9371                 fprintf(serverMoves, "%s;", second.tidy);
9372             fprintf(serverMoves, "%s;", first.tidy);
9373             if(gameMode == MachinePlaysWhite)
9374                 fprintf(serverMoves, "%s;", UserName());
9375             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9376                 fprintf(serverMoves, "%s;", second.tidy);
9377         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9378         lastLoadFlag = loadFlag;
9379         // print base move
9380         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9381         // print castling suffix
9382         if( toY == fromY && piece == king ) {
9383             if(toX-fromX > 1)
9384                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9385             if(fromX-toX >1)
9386                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9387         }
9388         // e.p. suffix
9389         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9390              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9391              boards[forwardMostMove][toY][toX] == EmptySquare
9392              && fromX != toX && fromY != toY)
9393                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9394         // promotion suffix
9395         if(promoChar != NULLCHAR)
9396                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9397         if(!loadFlag) {
9398                 char buf[MOVE_LEN*2], *p; int len;
9399             fprintf(serverMoves, "/%d/%d",
9400                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9401             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9402             else                      timeLeft = blackTimeRemaining/1000;
9403             fprintf(serverMoves, "/%d", timeLeft);
9404                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9405                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9406                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9407             fprintf(serverMoves, "/%s", buf);
9408         }
9409         fflush(serverMoves);
9410     }
9411
9412     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9413         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9414       return;
9415     }
9416     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9417     if (commentList[forwardMostMove+1] != NULL) {
9418         free(commentList[forwardMostMove+1]);
9419         commentList[forwardMostMove+1] = NULL;
9420     }
9421     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9422     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9423     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9424     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9425     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9426     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9427     adjustedClock = FALSE;
9428     gameInfo.result = GameUnfinished;
9429     if (gameInfo.resultDetails != NULL) {
9430         free(gameInfo.resultDetails);
9431         gameInfo.resultDetails = NULL;
9432     }
9433     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9434                               moveList[forwardMostMove - 1]);
9435     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9436       case MT_NONE:
9437       case MT_STALEMATE:
9438       default:
9439         break;
9440       case MT_CHECK:
9441         if(gameInfo.variant != VariantShogi)
9442             strcat(parseList[forwardMostMove - 1], "+");
9443         break;
9444       case MT_CHECKMATE:
9445       case MT_STAINMATE:
9446         strcat(parseList[forwardMostMove - 1], "#");
9447         break;
9448     }
9449
9450 }
9451
9452 /* Updates currentMove if not pausing */
9453 void
9454 ShowMove (int fromX, int fromY, int toX, int toY)
9455 {
9456     int instant = (gameMode == PlayFromGameFile) ?
9457         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9458     if(appData.noGUI) return;
9459     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9460         if (!instant) {
9461             if (forwardMostMove == currentMove + 1) {
9462                 AnimateMove(boards[forwardMostMove - 1],
9463                             fromX, fromY, toX, toY);
9464             }
9465             if (appData.highlightLastMove) {
9466                 SetHighlights(fromX, fromY, toX, toY);
9467             }
9468         }
9469         currentMove = forwardMostMove;
9470     }
9471
9472     if (instant) return;
9473
9474     DisplayMove(currentMove - 1);
9475     DrawPosition(FALSE, boards[currentMove]);
9476     DisplayBothClocks();
9477     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9478 }
9479
9480 void
9481 SendEgtPath (ChessProgramState *cps)
9482 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9483         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9484
9485         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9486
9487         while(*p) {
9488             char c, *q = name+1, *r, *s;
9489
9490             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9491             while(*p && *p != ',') *q++ = *p++;
9492             *q++ = ':'; *q = 0;
9493             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9494                 strcmp(name, ",nalimov:") == 0 ) {
9495                 // take nalimov path from the menu-changeable option first, if it is defined
9496               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9497                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9498             } else
9499             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9500                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9501                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9502                 s = r = StrStr(s, ":") + 1; // beginning of path info
9503                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9504                 c = *r; *r = 0;             // temporarily null-terminate path info
9505                     *--q = 0;               // strip of trailig ':' from name
9506                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9507                 *r = c;
9508                 SendToProgram(buf,cps);     // send egtbpath command for this format
9509             }
9510             if(*p == ',') p++; // read away comma to position for next format name
9511         }
9512 }
9513
9514 void
9515 InitChessProgram (ChessProgramState *cps, int setup)
9516 /* setup needed to setup FRC opening position */
9517 {
9518     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9519     if (appData.noChessProgram) return;
9520     hintRequested = FALSE;
9521     bookRequested = FALSE;
9522
9523     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9524     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9525     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9526     if(cps->memSize) { /* [HGM] memory */
9527       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9528         SendToProgram(buf, cps);
9529     }
9530     SendEgtPath(cps); /* [HGM] EGT */
9531     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9532       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9533         SendToProgram(buf, cps);
9534     }
9535
9536     SendToProgram(cps->initString, cps);
9537     if (gameInfo.variant != VariantNormal &&
9538         gameInfo.variant != VariantLoadable
9539         /* [HGM] also send variant if board size non-standard */
9540         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9541                                             ) {
9542       char *v = VariantName(gameInfo.variant);
9543       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9544         /* [HGM] in protocol 1 we have to assume all variants valid */
9545         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9546         DisplayFatalError(buf, 0, 1);
9547         return;
9548       }
9549
9550       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9551       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9552       if( gameInfo.variant == VariantXiangqi )
9553            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9554       if( gameInfo.variant == VariantShogi )
9555            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9556       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9558       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9559           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9561       if( gameInfo.variant == VariantCourier )
9562            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9563       if( gameInfo.variant == VariantSuper )
9564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9565       if( gameInfo.variant == VariantGreat )
9566            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9567       if( gameInfo.variant == VariantSChess )
9568            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9569       if( gameInfo.variant == VariantGrand )
9570            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9571
9572       if(overruled) {
9573         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9574                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9575            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9576            if(StrStr(cps->variants, b) == NULL) {
9577                // specific sized variant not known, check if general sizing allowed
9578                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9579                    if(StrStr(cps->variants, "boardsize") == NULL) {
9580                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9581                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9582                        DisplayFatalError(buf, 0, 1);
9583                        return;
9584                    }
9585                    /* [HGM] here we really should compare with the maximum supported board size */
9586                }
9587            }
9588       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9589       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9590       SendToProgram(buf, cps);
9591     }
9592     currentlyInitializedVariant = gameInfo.variant;
9593
9594     /* [HGM] send opening position in FRC to first engine */
9595     if(setup) {
9596           SendToProgram("force\n", cps);
9597           SendBoard(cps, 0);
9598           /* engine is now in force mode! Set flag to wake it up after first move. */
9599           setboardSpoiledMachineBlack = 1;
9600     }
9601
9602     if (cps->sendICS) {
9603       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9604       SendToProgram(buf, cps);
9605     }
9606     cps->maybeThinking = FALSE;
9607     cps->offeredDraw = 0;
9608     if (!appData.icsActive) {
9609         SendTimeControl(cps, movesPerSession, timeControl,
9610                         timeIncrement, appData.searchDepth,
9611                         searchTime);
9612     }
9613     if (appData.showThinking
9614         // [HGM] thinking: four options require thinking output to be sent
9615         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9616                                 ) {
9617         SendToProgram("post\n", cps);
9618     }
9619     SendToProgram("hard\n", cps);
9620     if (!appData.ponderNextMove) {
9621         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9622            it without being sure what state we are in first.  "hard"
9623            is not a toggle, so that one is OK.
9624          */
9625         SendToProgram("easy\n", cps);
9626     }
9627     if (cps->usePing) {
9628       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9629       SendToProgram(buf, cps);
9630     }
9631     cps->initDone = TRUE;
9632     ClearEngineOutputPane(cps == &second);
9633 }
9634
9635
9636 void
9637 StartChessProgram (ChessProgramState *cps)
9638 {
9639     char buf[MSG_SIZ];
9640     int err;
9641
9642     if (appData.noChessProgram) return;
9643     cps->initDone = FALSE;
9644
9645     if (strcmp(cps->host, "localhost") == 0) {
9646         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9647     } else if (*appData.remoteShell == NULLCHAR) {
9648         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9649     } else {
9650         if (*appData.remoteUser == NULLCHAR) {
9651           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9652                     cps->program);
9653         } else {
9654           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9655                     cps->host, appData.remoteUser, cps->program);
9656         }
9657         err = StartChildProcess(buf, "", &cps->pr);
9658     }
9659
9660     if (err != 0) {
9661       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9662         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9663         if(cps != &first) return;
9664         appData.noChessProgram = TRUE;
9665         ThawUI();
9666         SetNCPMode();
9667 //      DisplayFatalError(buf, err, 1);
9668 //      cps->pr = NoProc;
9669 //      cps->isr = NULL;
9670         return;
9671     }
9672
9673     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9674     if (cps->protocolVersion > 1) {
9675       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9676       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9677       cps->comboCnt = 0;  //                and values of combo boxes
9678       SendToProgram(buf, cps);
9679     } else {
9680       SendToProgram("xboard\n", cps);
9681     }
9682 }
9683
9684 void
9685 TwoMachinesEventIfReady P((void))
9686 {
9687   static int curMess = 0;
9688   if (first.lastPing != first.lastPong) {
9689     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9690     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9691     return;
9692   }
9693   if (second.lastPing != second.lastPong) {
9694     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9695     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9696     return;
9697   }
9698   DisplayMessage("", ""); curMess = 0;
9699   ThawUI();
9700   TwoMachinesEvent();
9701 }
9702
9703 char *
9704 MakeName (char *template)
9705 {
9706     time_t clock;
9707     struct tm *tm;
9708     static char buf[MSG_SIZ];
9709     char *p = buf;
9710     int i;
9711
9712     clock = time((time_t *)NULL);
9713     tm = localtime(&clock);
9714
9715     while(*p++ = *template++) if(p[-1] == '%') {
9716         switch(*template++) {
9717           case 0:   *p = 0; return buf;
9718           case 'Y': i = tm->tm_year+1900; break;
9719           case 'y': i = tm->tm_year-100; break;
9720           case 'M': i = tm->tm_mon+1; break;
9721           case 'd': i = tm->tm_mday; break;
9722           case 'h': i = tm->tm_hour; break;
9723           case 'm': i = tm->tm_min; break;
9724           case 's': i = tm->tm_sec; break;
9725           default:  i = 0;
9726         }
9727         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9728     }
9729     return buf;
9730 }
9731
9732 int
9733 CountPlayers (char *p)
9734 {
9735     int n = 0;
9736     while(p = strchr(p, '\n')) p++, n++; // count participants
9737     return n;
9738 }
9739
9740 FILE *
9741 WriteTourneyFile (char *results, FILE *f)
9742 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9743     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9744     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9745         // create a file with tournament description
9746         fprintf(f, "-participants {%s}\n", appData.participants);
9747         fprintf(f, "-seedBase %d\n", appData.seedBase);
9748         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9749         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9750         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9751         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9752         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9753         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9754         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9755         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9756         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9757         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9758         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9759         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9760         if(searchTime > 0)
9761                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9762         else {
9763                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9764                 fprintf(f, "-tc %s\n", appData.timeControl);
9765                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9766         }
9767         fprintf(f, "-results \"%s\"\n", results);
9768     }
9769     return f;
9770 }
9771
9772 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9773
9774 void
9775 Substitute (char *participants, int expunge)
9776 {
9777     int i, changed, changes=0, nPlayers=0;
9778     char *p, *q, *r, buf[MSG_SIZ];
9779     if(participants == NULL) return;
9780     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9781     r = p = participants; q = appData.participants;
9782     while(*p && *p == *q) {
9783         if(*p == '\n') r = p+1, nPlayers++;
9784         p++; q++;
9785     }
9786     if(*p) { // difference
9787         while(*p && *p++ != '\n');
9788         while(*q && *q++ != '\n');
9789       changed = nPlayers;
9790         changes = 1 + (strcmp(p, q) != 0);
9791     }
9792     if(changes == 1) { // a single engine mnemonic was changed
9793         q = r; while(*q) nPlayers += (*q++ == '\n');
9794         p = buf; while(*r && (*p = *r++) != '\n') p++;
9795         *p = NULLCHAR;
9796         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9797         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9798         if(mnemonic[i]) { // The substitute is valid
9799             FILE *f;
9800             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9801                 flock(fileno(f), LOCK_EX);
9802                 ParseArgsFromFile(f);
9803                 fseek(f, 0, SEEK_SET);
9804                 FREE(appData.participants); appData.participants = participants;
9805                 if(expunge) { // erase results of replaced engine
9806                     int len = strlen(appData.results), w, b, dummy;
9807                     for(i=0; i<len; i++) {
9808                         Pairing(i, nPlayers, &w, &b, &dummy);
9809                         if((w == changed || b == changed) && appData.results[i] == '*') {
9810                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9811                             fclose(f);
9812                             return;
9813                         }
9814                     }
9815                     for(i=0; i<len; i++) {
9816                         Pairing(i, nPlayers, &w, &b, &dummy);
9817                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9818                     }
9819                 }
9820                 WriteTourneyFile(appData.results, f);
9821                 fclose(f); // release lock
9822                 return;
9823             }
9824         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9825     }
9826     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9827     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9828     free(participants);
9829     return;
9830 }
9831
9832 int
9833 CreateTourney (char *name)
9834 {
9835         FILE *f;
9836         if(matchMode && strcmp(name, appData.tourneyFile)) {
9837              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9838         }
9839         if(name[0] == NULLCHAR) {
9840             if(appData.participants[0])
9841                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9842             return 0;
9843         }
9844         f = fopen(name, "r");
9845         if(f) { // file exists
9846             ASSIGN(appData.tourneyFile, name);
9847             ParseArgsFromFile(f); // parse it
9848         } else {
9849             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9850             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9851                 DisplayError(_("Not enough participants"), 0);
9852                 return 0;
9853             }
9854             ASSIGN(appData.tourneyFile, name);
9855             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9856             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9857         }
9858         fclose(f);
9859         appData.noChessProgram = FALSE;
9860         appData.clockMode = TRUE;
9861         SetGNUMode();
9862         return 1;
9863 }
9864
9865 int
9866 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9867 {
9868     char buf[MSG_SIZ], *p, *q;
9869     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9870     skip = !all && group[0]; // if group requested, we start in skip mode
9871     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9872         p = names; q = buf; header = 0;
9873         while(*p && *p != '\n') *q++ = *p++;
9874         *q = 0;
9875         if(*p == '\n') p++;
9876         if(buf[0] == '#') {
9877             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9878             depth++; // we must be entering a new group
9879             if(all) continue; // suppress printing group headers when complete list requested
9880             header = 1;
9881             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9882         }
9883         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9884         if(engineList[i]) free(engineList[i]);
9885         engineList[i] = strdup(buf);
9886         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9887         if(engineMnemonic[i]) free(engineMnemonic[i]);
9888         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9889             strcat(buf, " (");
9890             sscanf(q + 8, "%s", buf + strlen(buf));
9891             strcat(buf, ")");
9892         }
9893         engineMnemonic[i] = strdup(buf);
9894         i++;
9895     }
9896     engineList[i] = engineMnemonic[i] = NULL;
9897     return i;
9898 }
9899
9900 // following implemented as macro to avoid type limitations
9901 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9902
9903 void
9904 SwapEngines (int n)
9905 {   // swap settings for first engine and other engine (so far only some selected options)
9906     int h;
9907     char *p;
9908     if(n == 0) return;
9909     SWAP(directory, p)
9910     SWAP(chessProgram, p)
9911     SWAP(isUCI, h)
9912     SWAP(hasOwnBookUCI, h)
9913     SWAP(protocolVersion, h)
9914     SWAP(reuse, h)
9915     SWAP(scoreIsAbsolute, h)
9916     SWAP(timeOdds, h)
9917     SWAP(logo, p)
9918     SWAP(pgnName, p)
9919     SWAP(pvSAN, h)
9920     SWAP(engOptions, p)
9921 }
9922
9923 int
9924 SetPlayer (int player, char *p)
9925 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9926     int i;
9927     char buf[MSG_SIZ], *engineName;
9928     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9929     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9930     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9931     if(mnemonic[i]) {
9932         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9933         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9934         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9935         ParseArgsFromString(buf);
9936     }
9937     free(engineName);
9938     return i;
9939 }
9940
9941 char *recentEngines;
9942
9943 void
9944 RecentEngineEvent (int nr)
9945 {
9946     int n;
9947 //    SwapEngines(1); // bump first to second
9948 //    ReplaceEngine(&second, 1); // and load it there
9949     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9950     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9951     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9952         ReplaceEngine(&first, 0);
9953         FloatToFront(&appData.recentEngineList, command[n]);
9954     }
9955 }
9956
9957 int
9958 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9959 {   // determine players from game number
9960     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9961
9962     if(appData.tourneyType == 0) {
9963         roundsPerCycle = (nPlayers - 1) | 1;
9964         pairingsPerRound = nPlayers / 2;
9965     } else if(appData.tourneyType > 0) {
9966         roundsPerCycle = nPlayers - appData.tourneyType;
9967         pairingsPerRound = appData.tourneyType;
9968     }
9969     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9970     gamesPerCycle = gamesPerRound * roundsPerCycle;
9971     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9972     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9973     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9974     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9975     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9976     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9977
9978     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9979     if(appData.roundSync) *syncInterval = gamesPerRound;
9980
9981     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9982
9983     if(appData.tourneyType == 0) {
9984         if(curPairing == (nPlayers-1)/2 ) {
9985             *whitePlayer = curRound;
9986             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9987         } else {
9988             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9989             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9990             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9991             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9992         }
9993     } else if(appData.tourneyType > 1) {
9994         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9995         *whitePlayer = curRound + appData.tourneyType;
9996     } else if(appData.tourneyType > 0) {
9997         *whitePlayer = curPairing;
9998         *blackPlayer = curRound + appData.tourneyType;
9999     }
10000
10001     // take care of white/black alternation per round. 
10002     // For cycles and games this is already taken care of by default, derived from matchGame!
10003     return curRound & 1;
10004 }
10005
10006 int
10007 NextTourneyGame (int nr, int *swapColors)
10008 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10009     char *p, *q;
10010     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10011     FILE *tf;
10012     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10013     tf = fopen(appData.tourneyFile, "r");
10014     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10015     ParseArgsFromFile(tf); fclose(tf);
10016     InitTimeControls(); // TC might be altered from tourney file
10017
10018     nPlayers = CountPlayers(appData.participants); // count participants
10019     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10020     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10021
10022     if(syncInterval) {
10023         p = q = appData.results;
10024         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10025         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10026             DisplayMessage(_("Waiting for other game(s)"),"");
10027             waitingForGame = TRUE;
10028             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10029             return 0;
10030         }
10031         waitingForGame = FALSE;
10032     }
10033
10034     if(appData.tourneyType < 0) {
10035         if(nr>=0 && !pairingReceived) {
10036             char buf[1<<16];
10037             if(pairing.pr == NoProc) {
10038                 if(!appData.pairingEngine[0]) {
10039                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10040                     return 0;
10041                 }
10042                 StartChessProgram(&pairing); // starts the pairing engine
10043             }
10044             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10045             SendToProgram(buf, &pairing);
10046             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10047             SendToProgram(buf, &pairing);
10048             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10049         }
10050         pairingReceived = 0;                              // ... so we continue here 
10051         *swapColors = 0;
10052         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10053         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10054         matchGame = 1; roundNr = nr / syncInterval + 1;
10055     }
10056
10057     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10058
10059     // redefine engines, engine dir, etc.
10060     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10061     if(first.pr == NoProc) {
10062       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10063       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10064     }
10065     if(second.pr == NoProc) {
10066       SwapEngines(1);
10067       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10068       SwapEngines(1);         // and make that valid for second engine by swapping
10069       InitEngine(&second, 1);
10070     }
10071     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10072     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10073     return 1;
10074 }
10075
10076 void
10077 NextMatchGame ()
10078 {   // performs game initialization that does not invoke engines, and then tries to start the game
10079     int res, firstWhite, swapColors = 0;
10080     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10081     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
10082         char buf[MSG_SIZ];
10083         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10084         if(strcmp(buf, currentDebugFile)) { // name has changed
10085             FILE *f = fopen(buf, "w");
10086             if(f) { // if opening the new file failed, just keep using the old one
10087                 ASSIGN(currentDebugFile, buf);
10088                 fclose(debugFP);
10089                 debugFP = f;
10090             }
10091             if(appData.serverFileName) {
10092                 if(serverFP) fclose(serverFP);
10093                 serverFP = fopen(appData.serverFileName, "w");
10094                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10095                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10096             }
10097         }
10098     }
10099     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10100     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10101     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10102     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10103     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10104     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10105     Reset(FALSE, first.pr != NoProc);
10106     res = LoadGameOrPosition(matchGame); // setup game
10107     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10108     if(!res) return; // abort when bad game/pos file
10109     TwoMachinesEvent();
10110 }
10111
10112 void
10113 UserAdjudicationEvent (int result)
10114 {
10115     ChessMove gameResult = GameIsDrawn;
10116
10117     if( result > 0 ) {
10118         gameResult = WhiteWins;
10119     }
10120     else if( result < 0 ) {
10121         gameResult = BlackWins;
10122     }
10123
10124     if( gameMode == TwoMachinesPlay ) {
10125         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10126     }
10127 }
10128
10129
10130 // [HGM] save: calculate checksum of game to make games easily identifiable
10131 int
10132 StringCheckSum (char *s)
10133 {
10134         int i = 0;
10135         if(s==NULL) return 0;
10136         while(*s) i = i*259 + *s++;
10137         return i;
10138 }
10139
10140 int
10141 GameCheckSum ()
10142 {
10143         int i, sum=0;
10144         for(i=backwardMostMove; i<forwardMostMove; i++) {
10145                 sum += pvInfoList[i].depth;
10146                 sum += StringCheckSum(parseList[i]);
10147                 sum += StringCheckSum(commentList[i]);
10148                 sum *= 261;
10149         }
10150         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10151         return sum + StringCheckSum(commentList[i]);
10152 } // end of save patch
10153
10154 void
10155 GameEnds (ChessMove result, char *resultDetails, int whosays)
10156 {
10157     GameMode nextGameMode;
10158     int isIcsGame;
10159     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10160
10161     if(endingGame) return; /* [HGM] crash: forbid recursion */
10162     endingGame = 1;
10163     if(twoBoards) { // [HGM] dual: switch back to one board
10164         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10165         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10166     }
10167     if (appData.debugMode) {
10168       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10169               result, resultDetails ? resultDetails : "(null)", whosays);
10170     }
10171
10172     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10173
10174     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10175         /* If we are playing on ICS, the server decides when the
10176            game is over, but the engine can offer to draw, claim
10177            a draw, or resign.
10178          */
10179 #if ZIPPY
10180         if (appData.zippyPlay && first.initDone) {
10181             if (result == GameIsDrawn) {
10182                 /* In case draw still needs to be claimed */
10183                 SendToICS(ics_prefix);
10184                 SendToICS("draw\n");
10185             } else if (StrCaseStr(resultDetails, "resign")) {
10186                 SendToICS(ics_prefix);
10187                 SendToICS("resign\n");
10188             }
10189         }
10190 #endif
10191         endingGame = 0; /* [HGM] crash */
10192         return;
10193     }
10194
10195     /* If we're loading the game from a file, stop */
10196     if (whosays == GE_FILE) {
10197       (void) StopLoadGameTimer();
10198       gameFileFP = NULL;
10199     }
10200
10201     /* Cancel draw offers */
10202     first.offeredDraw = second.offeredDraw = 0;
10203
10204     /* If this is an ICS game, only ICS can really say it's done;
10205        if not, anyone can. */
10206     isIcsGame = (gameMode == IcsPlayingWhite ||
10207                  gameMode == IcsPlayingBlack ||
10208                  gameMode == IcsObserving    ||
10209                  gameMode == IcsExamining);
10210
10211     if (!isIcsGame || whosays == GE_ICS) {
10212         /* OK -- not an ICS game, or ICS said it was done */
10213         StopClocks();
10214         if (!isIcsGame && !appData.noChessProgram)
10215           SetUserThinkingEnables();
10216
10217         /* [HGM] if a machine claims the game end we verify this claim */
10218         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10219             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10220                 char claimer;
10221                 ChessMove trueResult = (ChessMove) -1;
10222
10223                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10224                                             first.twoMachinesColor[0] :
10225                                             second.twoMachinesColor[0] ;
10226
10227                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10228                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10229                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10230                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10231                 } else
10232                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10233                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10234                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10235                 } else
10236                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10237                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10238                 }
10239
10240                 // now verify win claims, but not in drop games, as we don't understand those yet
10241                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10242                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10243                     (result == WhiteWins && claimer == 'w' ||
10244                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10245                       if (appData.debugMode) {
10246                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10247                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10248                       }
10249                       if(result != trueResult) {
10250                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10251                               result = claimer == 'w' ? BlackWins : WhiteWins;
10252                               resultDetails = buf;
10253                       }
10254                 } else
10255                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10256                     && (forwardMostMove <= backwardMostMove ||
10257                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10258                         (claimer=='b')==(forwardMostMove&1))
10259                                                                                   ) {
10260                       /* [HGM] verify: draws that were not flagged are false claims */
10261                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10262                       result = claimer == 'w' ? BlackWins : WhiteWins;
10263                       resultDetails = buf;
10264                 }
10265                 /* (Claiming a loss is accepted no questions asked!) */
10266             }
10267             /* [HGM] bare: don't allow bare King to win */
10268             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10269                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10270                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10271                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10272                && result != GameIsDrawn)
10273             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10274                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10275                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10276                         if(p >= 0 && p <= (int)WhiteKing) k++;
10277                 }
10278                 if (appData.debugMode) {
10279                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10280                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10281                 }
10282                 if(k <= 1) {
10283                         result = GameIsDrawn;
10284                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10285                         resultDetails = buf;
10286                 }
10287             }
10288         }
10289
10290
10291         if(serverMoves != NULL && !loadFlag) { char c = '=';
10292             if(result==WhiteWins) c = '+';
10293             if(result==BlackWins) c = '-';
10294             if(resultDetails != NULL)
10295                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10296         }
10297         if (resultDetails != NULL) {
10298             gameInfo.result = result;
10299             gameInfo.resultDetails = StrSave(resultDetails);
10300
10301             /* display last move only if game was not loaded from file */
10302             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10303                 DisplayMove(currentMove - 1);
10304
10305             if (forwardMostMove != 0) {
10306                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10307                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10308                                                                 ) {
10309                     if (*appData.saveGameFile != NULLCHAR) {
10310                         SaveGameToFile(appData.saveGameFile, TRUE);
10311                     } else if (appData.autoSaveGames) {
10312                         AutoSaveGame();
10313                     }
10314                     if (*appData.savePositionFile != NULLCHAR) {
10315                         SavePositionToFile(appData.savePositionFile);
10316                     }
10317                 }
10318             }
10319
10320             /* Tell program how game ended in case it is learning */
10321             /* [HGM] Moved this to after saving the PGN, just in case */
10322             /* engine died and we got here through time loss. In that */
10323             /* case we will get a fatal error writing the pipe, which */
10324             /* would otherwise lose us the PGN.                       */
10325             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10326             /* output during GameEnds should never be fatal anymore   */
10327             if (gameMode == MachinePlaysWhite ||
10328                 gameMode == MachinePlaysBlack ||
10329                 gameMode == TwoMachinesPlay ||
10330                 gameMode == IcsPlayingWhite ||
10331                 gameMode == IcsPlayingBlack ||
10332                 gameMode == BeginningOfGame) {
10333                 char buf[MSG_SIZ];
10334                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10335                         resultDetails);
10336                 if (first.pr != NoProc) {
10337                     SendToProgram(buf, &first);
10338                 }
10339                 if (second.pr != NoProc &&
10340                     gameMode == TwoMachinesPlay) {
10341                     SendToProgram(buf, &second);
10342                 }
10343             }
10344         }
10345
10346         if (appData.icsActive) {
10347             if (appData.quietPlay &&
10348                 (gameMode == IcsPlayingWhite ||
10349                  gameMode == IcsPlayingBlack)) {
10350                 SendToICS(ics_prefix);
10351                 SendToICS("set shout 1\n");
10352             }
10353             nextGameMode = IcsIdle;
10354             ics_user_moved = FALSE;
10355             /* clean up premove.  It's ugly when the game has ended and the
10356              * premove highlights are still on the board.
10357              */
10358             if (gotPremove) {
10359               gotPremove = FALSE;
10360               ClearPremoveHighlights();
10361               DrawPosition(FALSE, boards[currentMove]);
10362             }
10363             if (whosays == GE_ICS) {
10364                 switch (result) {
10365                 case WhiteWins:
10366                     if (gameMode == IcsPlayingWhite)
10367                         PlayIcsWinSound();
10368                     else if(gameMode == IcsPlayingBlack)
10369                         PlayIcsLossSound();
10370                     break;
10371                 case BlackWins:
10372                     if (gameMode == IcsPlayingBlack)
10373                         PlayIcsWinSound();
10374                     else if(gameMode == IcsPlayingWhite)
10375                         PlayIcsLossSound();
10376                     break;
10377                 case GameIsDrawn:
10378                     PlayIcsDrawSound();
10379                     break;
10380                 default:
10381                     PlayIcsUnfinishedSound();
10382                 }
10383             }
10384         } else if (gameMode == EditGame ||
10385                    gameMode == PlayFromGameFile ||
10386                    gameMode == AnalyzeMode ||
10387                    gameMode == AnalyzeFile) {
10388             nextGameMode = gameMode;
10389         } else {
10390             nextGameMode = EndOfGame;
10391         }
10392         pausing = FALSE;
10393         ModeHighlight();
10394     } else {
10395         nextGameMode = gameMode;
10396     }
10397
10398     if (appData.noChessProgram) {
10399         gameMode = nextGameMode;
10400         ModeHighlight();
10401         endingGame = 0; /* [HGM] crash */
10402         return;
10403     }
10404
10405     if (first.reuse) {
10406         /* Put first chess program into idle state */
10407         if (first.pr != NoProc &&
10408             (gameMode == MachinePlaysWhite ||
10409              gameMode == MachinePlaysBlack ||
10410              gameMode == TwoMachinesPlay ||
10411              gameMode == IcsPlayingWhite ||
10412              gameMode == IcsPlayingBlack ||
10413              gameMode == BeginningOfGame)) {
10414             SendToProgram("force\n", &first);
10415             if (first.usePing) {
10416               char buf[MSG_SIZ];
10417               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10418               SendToProgram(buf, &first);
10419             }
10420         }
10421     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10422         /* Kill off first chess program */
10423         if (first.isr != NULL)
10424           RemoveInputSource(first.isr);
10425         first.isr = NULL;
10426
10427         if (first.pr != NoProc) {
10428             ExitAnalyzeMode();
10429             DoSleep( appData.delayBeforeQuit );
10430             SendToProgram("quit\n", &first);
10431             DoSleep( appData.delayAfterQuit );
10432             DestroyChildProcess(first.pr, first.useSigterm);
10433         }
10434         first.pr = NoProc;
10435     }
10436     if (second.reuse) {
10437         /* Put second chess program into idle state */
10438         if (second.pr != NoProc &&
10439             gameMode == TwoMachinesPlay) {
10440             SendToProgram("force\n", &second);
10441             if (second.usePing) {
10442               char buf[MSG_SIZ];
10443               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10444               SendToProgram(buf, &second);
10445             }
10446         }
10447     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10448         /* Kill off second chess program */
10449         if (second.isr != NULL)
10450           RemoveInputSource(second.isr);
10451         second.isr = NULL;
10452
10453         if (second.pr != NoProc) {
10454             DoSleep( appData.delayBeforeQuit );
10455             SendToProgram("quit\n", &second);
10456             DoSleep( appData.delayAfterQuit );
10457             DestroyChildProcess(second.pr, second.useSigterm);
10458         }
10459         second.pr = NoProc;
10460     }
10461
10462     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10463         char resChar = '=';
10464         switch (result) {
10465         case WhiteWins:
10466           resChar = '+';
10467           if (first.twoMachinesColor[0] == 'w') {
10468             first.matchWins++;
10469           } else {
10470             second.matchWins++;
10471           }
10472           break;
10473         case BlackWins:
10474           resChar = '-';
10475           if (first.twoMachinesColor[0] == 'b') {
10476             first.matchWins++;
10477           } else {
10478             second.matchWins++;
10479           }
10480           break;
10481         case GameUnfinished:
10482           resChar = ' ';
10483         default:
10484           break;
10485         }
10486
10487         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10488         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10489             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10490             ReserveGame(nextGame, resChar); // sets nextGame
10491             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10492             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10493         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10494
10495         if (nextGame <= appData.matchGames && !abortMatch) {
10496             gameMode = nextGameMode;
10497             matchGame = nextGame; // this will be overruled in tourney mode!
10498             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10499             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10500             endingGame = 0; /* [HGM] crash */
10501             return;
10502         } else {
10503             gameMode = nextGameMode;
10504             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10505                      first.tidy, second.tidy,
10506                      first.matchWins, second.matchWins,
10507                      appData.matchGames - (first.matchWins + second.matchWins));
10508             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10509             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10510             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10511             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10512                 first.twoMachinesColor = "black\n";
10513                 second.twoMachinesColor = "white\n";
10514             } else {
10515                 first.twoMachinesColor = "white\n";
10516                 second.twoMachinesColor = "black\n";
10517             }
10518         }
10519     }
10520     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10521         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10522       ExitAnalyzeMode();
10523     gameMode = nextGameMode;
10524     ModeHighlight();
10525     endingGame = 0;  /* [HGM] crash */
10526     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10527         if(matchMode == TRUE) { // match through command line: exit with or without popup
10528             if(ranking) {
10529                 ToNrEvent(forwardMostMove);
10530                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10531                 else ExitEvent(0);
10532             } else DisplayFatalError(buf, 0, 0);
10533         } else { // match through menu; just stop, with or without popup
10534             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10535             ModeHighlight();
10536             if(ranking){
10537                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10538             } else DisplayNote(buf);
10539       }
10540       if(ranking) free(ranking);
10541     }
10542 }
10543
10544 /* Assumes program was just initialized (initString sent).
10545    Leaves program in force mode. */
10546 void
10547 FeedMovesToProgram (ChessProgramState *cps, int upto)
10548 {
10549     int i;
10550
10551     if (appData.debugMode)
10552       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10553               startedFromSetupPosition ? "position and " : "",
10554               backwardMostMove, upto, cps->which);
10555     if(currentlyInitializedVariant != gameInfo.variant) {
10556       char buf[MSG_SIZ];
10557         // [HGM] variantswitch: make engine aware of new variant
10558         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10559                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10560         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10561         SendToProgram(buf, cps);
10562         currentlyInitializedVariant = gameInfo.variant;
10563     }
10564     SendToProgram("force\n", cps);
10565     if (startedFromSetupPosition) {
10566         SendBoard(cps, backwardMostMove);
10567     if (appData.debugMode) {
10568         fprintf(debugFP, "feedMoves\n");
10569     }
10570     }
10571     for (i = backwardMostMove; i < upto; i++) {
10572         SendMoveToProgram(i, cps);
10573     }
10574 }
10575
10576
10577 int
10578 ResurrectChessProgram ()
10579 {
10580      /* The chess program may have exited.
10581         If so, restart it and feed it all the moves made so far. */
10582     static int doInit = 0;
10583
10584     if (appData.noChessProgram) return 1;
10585
10586     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10587         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10588         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10589         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10590     } else {
10591         if (first.pr != NoProc) return 1;
10592         StartChessProgram(&first);
10593     }
10594     InitChessProgram(&first, FALSE);
10595     FeedMovesToProgram(&first, currentMove);
10596
10597     if (!first.sendTime) {
10598         /* can't tell gnuchess what its clock should read,
10599            so we bow to its notion. */
10600         ResetClocks();
10601         timeRemaining[0][currentMove] = whiteTimeRemaining;
10602         timeRemaining[1][currentMove] = blackTimeRemaining;
10603     }
10604
10605     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10606                 appData.icsEngineAnalyze) && first.analysisSupport) {
10607       SendToProgram("analyze\n", &first);
10608       first.analyzing = TRUE;
10609     }
10610     return 1;
10611 }
10612
10613 /*
10614  * Button procedures
10615  */
10616 void
10617 Reset (int redraw, int init)
10618 {
10619     int i;
10620
10621     if (appData.debugMode) {
10622         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10623                 redraw, init, gameMode);
10624     }
10625     CleanupTail(); // [HGM] vari: delete any stored variations
10626     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10627     pausing = pauseExamInvalid = FALSE;
10628     startedFromSetupPosition = blackPlaysFirst = FALSE;
10629     firstMove = TRUE;
10630     whiteFlag = blackFlag = FALSE;
10631     userOfferedDraw = FALSE;
10632     hintRequested = bookRequested = FALSE;
10633     first.maybeThinking = FALSE;
10634     second.maybeThinking = FALSE;
10635     first.bookSuspend = FALSE; // [HGM] book
10636     second.bookSuspend = FALSE;
10637     thinkOutput[0] = NULLCHAR;
10638     lastHint[0] = NULLCHAR;
10639     ClearGameInfo(&gameInfo);
10640     gameInfo.variant = StringToVariant(appData.variant);
10641     ics_user_moved = ics_clock_paused = FALSE;
10642     ics_getting_history = H_FALSE;
10643     ics_gamenum = -1;
10644     white_holding[0] = black_holding[0] = NULLCHAR;
10645     ClearProgramStats();
10646     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10647
10648     ResetFrontEnd();
10649     ClearHighlights();
10650     flipView = appData.flipView;
10651     ClearPremoveHighlights();
10652     gotPremove = FALSE;
10653     alarmSounded = FALSE;
10654
10655     GameEnds(EndOfFile, NULL, GE_PLAYER);
10656     if(appData.serverMovesName != NULL) {
10657         /* [HGM] prepare to make moves file for broadcasting */
10658         clock_t t = clock();
10659         if(serverMoves != NULL) fclose(serverMoves);
10660         serverMoves = fopen(appData.serverMovesName, "r");
10661         if(serverMoves != NULL) {
10662             fclose(serverMoves);
10663             /* delay 15 sec before overwriting, so all clients can see end */
10664             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10665         }
10666         serverMoves = fopen(appData.serverMovesName, "w");
10667     }
10668
10669     ExitAnalyzeMode();
10670     gameMode = BeginningOfGame;
10671     ModeHighlight();
10672     if(appData.icsActive) gameInfo.variant = VariantNormal;
10673     currentMove = forwardMostMove = backwardMostMove = 0;
10674     MarkTargetSquares(1);
10675     InitPosition(redraw);
10676     for (i = 0; i < MAX_MOVES; i++) {
10677         if (commentList[i] != NULL) {
10678             free(commentList[i]);
10679             commentList[i] = NULL;
10680         }
10681     }
10682     ResetClocks();
10683     timeRemaining[0][0] = whiteTimeRemaining;
10684     timeRemaining[1][0] = blackTimeRemaining;
10685
10686     if (first.pr == NoProc) {
10687         StartChessProgram(&first);
10688     }
10689     if (init) {
10690             InitChessProgram(&first, startedFromSetupPosition);
10691     }
10692     DisplayTitle("");
10693     DisplayMessage("", "");
10694     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10695     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10696 }
10697
10698 void
10699 AutoPlayGameLoop ()
10700 {
10701     for (;;) {
10702         if (!AutoPlayOneMove())
10703           return;
10704         if (matchMode || appData.timeDelay == 0)
10705           continue;
10706         if (appData.timeDelay < 0)
10707           return;
10708         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10709         break;
10710     }
10711 }
10712
10713
10714 int
10715 AutoPlayOneMove ()
10716 {
10717     int fromX, fromY, toX, toY;
10718
10719     if (appData.debugMode) {
10720       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10721     }
10722
10723     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10724       return FALSE;
10725
10726     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10727       pvInfoList[currentMove].depth = programStats.depth;
10728       pvInfoList[currentMove].score = programStats.score;
10729       pvInfoList[currentMove].time  = 0;
10730       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10731     }
10732
10733     if (currentMove >= forwardMostMove) {
10734       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10735 //      gameMode = EndOfGame;
10736 //      ModeHighlight();
10737
10738       /* [AS] Clear current move marker at the end of a game */
10739       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10740
10741       return FALSE;
10742     }
10743
10744     toX = moveList[currentMove][2] - AAA;
10745     toY = moveList[currentMove][3] - ONE;
10746
10747     if (moveList[currentMove][1] == '@') {
10748         if (appData.highlightLastMove) {
10749             SetHighlights(-1, -1, toX, toY);
10750         }
10751     } else {
10752         fromX = moveList[currentMove][0] - AAA;
10753         fromY = moveList[currentMove][1] - ONE;
10754
10755         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10756
10757         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10758
10759         if (appData.highlightLastMove) {
10760             SetHighlights(fromX, fromY, toX, toY);
10761         }
10762     }
10763     DisplayMove(currentMove);
10764     SendMoveToProgram(currentMove++, &first);
10765     DisplayBothClocks();
10766     DrawPosition(FALSE, boards[currentMove]);
10767     // [HGM] PV info: always display, routine tests if empty
10768     DisplayComment(currentMove - 1, commentList[currentMove]);
10769     return TRUE;
10770 }
10771
10772
10773 int
10774 LoadGameOneMove (ChessMove readAhead)
10775 {
10776     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10777     char promoChar = NULLCHAR;
10778     ChessMove moveType;
10779     char move[MSG_SIZ];
10780     char *p, *q;
10781
10782     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10783         gameMode != AnalyzeMode && gameMode != Training) {
10784         gameFileFP = NULL;
10785         return FALSE;
10786     }
10787
10788     yyboardindex = forwardMostMove;
10789     if (readAhead != EndOfFile) {
10790       moveType = readAhead;
10791     } else {
10792       if (gameFileFP == NULL)
10793           return FALSE;
10794       moveType = (ChessMove) Myylex();
10795     }
10796
10797     done = FALSE;
10798     switch (moveType) {
10799       case Comment:
10800         if (appData.debugMode)
10801           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10802         p = yy_text;
10803
10804         /* append the comment but don't display it */
10805         AppendComment(currentMove, p, FALSE);
10806         return TRUE;
10807
10808       case WhiteCapturesEnPassant:
10809       case BlackCapturesEnPassant:
10810       case WhitePromotion:
10811       case BlackPromotion:
10812       case WhiteNonPromotion:
10813       case BlackNonPromotion:
10814       case NormalMove:
10815       case WhiteKingSideCastle:
10816       case WhiteQueenSideCastle:
10817       case BlackKingSideCastle:
10818       case BlackQueenSideCastle:
10819       case WhiteKingSideCastleWild:
10820       case WhiteQueenSideCastleWild:
10821       case BlackKingSideCastleWild:
10822       case BlackQueenSideCastleWild:
10823       /* PUSH Fabien */
10824       case WhiteHSideCastleFR:
10825       case WhiteASideCastleFR:
10826       case BlackHSideCastleFR:
10827       case BlackASideCastleFR:
10828       /* POP Fabien */
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10831         fromX = currentMoveString[0] - AAA;
10832         fromY = currentMoveString[1] - ONE;
10833         toX = currentMoveString[2] - AAA;
10834         toY = currentMoveString[3] - ONE;
10835         promoChar = currentMoveString[4];
10836         break;
10837
10838       case WhiteDrop:
10839       case BlackDrop:
10840         if (appData.debugMode)
10841           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10842         fromX = moveType == WhiteDrop ?
10843           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10844         (int) CharToPiece(ToLower(currentMoveString[0]));
10845         fromY = DROP_RANK;
10846         toX = currentMoveString[2] - AAA;
10847         toY = currentMoveString[3] - ONE;
10848         break;
10849
10850       case WhiteWins:
10851       case BlackWins:
10852       case GameIsDrawn:
10853       case GameUnfinished:
10854         if (appData.debugMode)
10855           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10856         p = strchr(yy_text, '{');
10857         if (p == NULL) p = strchr(yy_text, '(');
10858         if (p == NULL) {
10859             p = yy_text;
10860             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10861         } else {
10862             q = strchr(p, *p == '{' ? '}' : ')');
10863             if (q != NULL) *q = NULLCHAR;
10864             p++;
10865         }
10866         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10867         GameEnds(moveType, p, GE_FILE);
10868         done = TRUE;
10869         if (cmailMsgLoaded) {
10870             ClearHighlights();
10871             flipView = WhiteOnMove(currentMove);
10872             if (moveType == GameUnfinished) flipView = !flipView;
10873             if (appData.debugMode)
10874               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10875         }
10876         break;
10877
10878       case EndOfFile:
10879         if (appData.debugMode)
10880           fprintf(debugFP, "Parser hit end of file\n");
10881         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10882           case MT_NONE:
10883           case MT_CHECK:
10884             break;
10885           case MT_CHECKMATE:
10886           case MT_STAINMATE:
10887             if (WhiteOnMove(currentMove)) {
10888                 GameEnds(BlackWins, "Black mates", GE_FILE);
10889             } else {
10890                 GameEnds(WhiteWins, "White mates", GE_FILE);
10891             }
10892             break;
10893           case MT_STALEMATE:
10894             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10895             break;
10896         }
10897         done = TRUE;
10898         break;
10899
10900       case MoveNumberOne:
10901         if (lastLoadGameStart == GNUChessGame) {
10902             /* GNUChessGames have numbers, but they aren't move numbers */
10903             if (appData.debugMode)
10904               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10905                       yy_text, (int) moveType);
10906             return LoadGameOneMove(EndOfFile); /* tail recursion */
10907         }
10908         /* else fall thru */
10909
10910       case XBoardGame:
10911       case GNUChessGame:
10912       case PGNTag:
10913         /* Reached start of next game in file */
10914         if (appData.debugMode)
10915           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10916         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10917           case MT_NONE:
10918           case MT_CHECK:
10919             break;
10920           case MT_CHECKMATE:
10921           case MT_STAINMATE:
10922             if (WhiteOnMove(currentMove)) {
10923                 GameEnds(BlackWins, "Black mates", GE_FILE);
10924             } else {
10925                 GameEnds(WhiteWins, "White mates", GE_FILE);
10926             }
10927             break;
10928           case MT_STALEMATE:
10929             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10930             break;
10931         }
10932         done = TRUE;
10933         break;
10934
10935       case PositionDiagram:     /* should not happen; ignore */
10936       case ElapsedTime:         /* ignore */
10937       case NAG:                 /* ignore */
10938         if (appData.debugMode)
10939           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10940                   yy_text, (int) moveType);
10941         return LoadGameOneMove(EndOfFile); /* tail recursion */
10942
10943       case IllegalMove:
10944         if (appData.testLegality) {
10945             if (appData.debugMode)
10946               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10947             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10948                     (forwardMostMove / 2) + 1,
10949                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10950             DisplayError(move, 0);
10951             done = TRUE;
10952         } else {
10953             if (appData.debugMode)
10954               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10955                       yy_text, currentMoveString);
10956             fromX = currentMoveString[0] - AAA;
10957             fromY = currentMoveString[1] - ONE;
10958             toX = currentMoveString[2] - AAA;
10959             toY = currentMoveString[3] - ONE;
10960             promoChar = currentMoveString[4];
10961         }
10962         break;
10963
10964       case AmbiguousMove:
10965         if (appData.debugMode)
10966           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10967         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10968                 (forwardMostMove / 2) + 1,
10969                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10970         DisplayError(move, 0);
10971         done = TRUE;
10972         break;
10973
10974       default:
10975       case ImpossibleMove:
10976         if (appData.debugMode)
10977           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10978         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10979                 (forwardMostMove / 2) + 1,
10980                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10981         DisplayError(move, 0);
10982         done = TRUE;
10983         break;
10984     }
10985
10986     if (done) {
10987         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10988             DrawPosition(FALSE, boards[currentMove]);
10989             DisplayBothClocks();
10990             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10991               DisplayComment(currentMove - 1, commentList[currentMove]);
10992         }
10993         (void) StopLoadGameTimer();
10994         gameFileFP = NULL;
10995         cmailOldMove = forwardMostMove;
10996         return FALSE;
10997     } else {
10998         /* currentMoveString is set as a side-effect of yylex */
10999
11000         thinkOutput[0] = NULLCHAR;
11001         MakeMove(fromX, fromY, toX, toY, promoChar);
11002         currentMove = forwardMostMove;
11003         return TRUE;
11004     }
11005 }
11006
11007 /* Load the nth game from the given file */
11008 int
11009 LoadGameFromFile (char *filename, int n, char *title, int useList)
11010 {
11011     FILE *f;
11012     char buf[MSG_SIZ];
11013
11014     if (strcmp(filename, "-") == 0) {
11015         f = stdin;
11016         title = "stdin";
11017     } else {
11018         f = fopen(filename, "rb");
11019         if (f == NULL) {
11020           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11021             DisplayError(buf, errno);
11022             return FALSE;
11023         }
11024     }
11025     if (fseek(f, 0, 0) == -1) {
11026         /* f is not seekable; probably a pipe */
11027         useList = FALSE;
11028     }
11029     if (useList && n == 0) {
11030         int error = GameListBuild(f);
11031         if (error) {
11032             DisplayError(_("Cannot build game list"), error);
11033         } else if (!ListEmpty(&gameList) &&
11034                    ((ListGame *) gameList.tailPred)->number > 1) {
11035             GameListPopUp(f, title);
11036             return TRUE;
11037         }
11038         GameListDestroy();
11039         n = 1;
11040     }
11041     if (n == 0) n = 1;
11042     return LoadGame(f, n, title, FALSE);
11043 }
11044
11045
11046 void
11047 MakeRegisteredMove ()
11048 {
11049     int fromX, fromY, toX, toY;
11050     char promoChar;
11051     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11052         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11053           case CMAIL_MOVE:
11054           case CMAIL_DRAW:
11055             if (appData.debugMode)
11056               fprintf(debugFP, "Restoring %s for game %d\n",
11057                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11058
11059             thinkOutput[0] = NULLCHAR;
11060             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11061             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11062             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11063             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11064             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11065             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11066             MakeMove(fromX, fromY, toX, toY, promoChar);
11067             ShowMove(fromX, fromY, toX, toY);
11068
11069             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11070               case MT_NONE:
11071               case MT_CHECK:
11072                 break;
11073
11074               case MT_CHECKMATE:
11075               case MT_STAINMATE:
11076                 if (WhiteOnMove(currentMove)) {
11077                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11078                 } else {
11079                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11080                 }
11081                 break;
11082
11083               case MT_STALEMATE:
11084                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11085                 break;
11086             }
11087
11088             break;
11089
11090           case CMAIL_RESIGN:
11091             if (WhiteOnMove(currentMove)) {
11092                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11093             } else {
11094                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11095             }
11096             break;
11097
11098           case CMAIL_ACCEPT:
11099             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11100             break;
11101
11102           default:
11103             break;
11104         }
11105     }
11106
11107     return;
11108 }
11109
11110 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11111 int
11112 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11113 {
11114     int retVal;
11115
11116     if (gameNumber > nCmailGames) {
11117         DisplayError(_("No more games in this message"), 0);
11118         return FALSE;
11119     }
11120     if (f == lastLoadGameFP) {
11121         int offset = gameNumber - lastLoadGameNumber;
11122         if (offset == 0) {
11123             cmailMsg[0] = NULLCHAR;
11124             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11125                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11126                 nCmailMovesRegistered--;
11127             }
11128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11129             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11130                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11131             }
11132         } else {
11133             if (! RegisterMove()) return FALSE;
11134         }
11135     }
11136
11137     retVal = LoadGame(f, gameNumber, title, useList);
11138
11139     /* Make move registered during previous look at this game, if any */
11140     MakeRegisteredMove();
11141
11142     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11143         commentList[currentMove]
11144           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11145         DisplayComment(currentMove - 1, commentList[currentMove]);
11146     }
11147
11148     return retVal;
11149 }
11150
11151 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11152 int
11153 ReloadGame (int offset)
11154 {
11155     int gameNumber = lastLoadGameNumber + offset;
11156     if (lastLoadGameFP == NULL) {
11157         DisplayError(_("No game has been loaded yet"), 0);
11158         return FALSE;
11159     }
11160     if (gameNumber <= 0) {
11161         DisplayError(_("Can't back up any further"), 0);
11162         return FALSE;
11163     }
11164     if (cmailMsgLoaded) {
11165         return CmailLoadGame(lastLoadGameFP, gameNumber,
11166                              lastLoadGameTitle, lastLoadGameUseList);
11167     } else {
11168         return LoadGame(lastLoadGameFP, gameNumber,
11169                         lastLoadGameTitle, lastLoadGameUseList);
11170     }
11171 }
11172
11173 int keys[EmptySquare+1];
11174
11175 int
11176 PositionMatches (Board b1, Board b2)
11177 {
11178     int r, f, sum=0;
11179     switch(appData.searchMode) {
11180         case 1: return CompareWithRights(b1, b2);
11181         case 2:
11182             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11183                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11184             }
11185             return TRUE;
11186         case 3:
11187             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11188               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11189                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11190             }
11191             return sum==0;
11192         case 4:
11193             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11194                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11195             }
11196             return sum==0;
11197     }
11198     return TRUE;
11199 }
11200
11201 #define Q_PROMO  4
11202 #define Q_EP     3
11203 #define Q_BCASTL 2
11204 #define Q_WCASTL 1
11205
11206 int pieceList[256], quickBoard[256];
11207 ChessSquare pieceType[256] = { EmptySquare };
11208 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11209 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11210 int soughtTotal, turn;
11211 Boolean epOK, flipSearch;
11212
11213 typedef struct {
11214     unsigned char piece, to;
11215 } Move;
11216
11217 #define DSIZE (250000)
11218
11219 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11220 Move *moveDatabase = initialSpace;
11221 unsigned int movePtr, dataSize = DSIZE;
11222
11223 int
11224 MakePieceList (Board board, int *counts)
11225 {
11226     int r, f, n=Q_PROMO, total=0;
11227     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11228     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11229         int sq = f + (r<<4);
11230         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11231             quickBoard[sq] = ++n;
11232             pieceList[n] = sq;
11233             pieceType[n] = board[r][f];
11234             counts[board[r][f]]++;
11235             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11236             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11237             total++;
11238         }
11239     }
11240     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11241     return total;
11242 }
11243
11244 void
11245 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11246 {
11247     int sq = fromX + (fromY<<4);
11248     int piece = quickBoard[sq];
11249     quickBoard[sq] = 0;
11250     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11251     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11252         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11253         moveDatabase[movePtr++].piece = Q_WCASTL;
11254         quickBoard[sq] = piece;
11255         piece = quickBoard[from]; quickBoard[from] = 0;
11256         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11257     } else
11258     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11259         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11260         moveDatabase[movePtr++].piece = Q_BCASTL;
11261         quickBoard[sq] = piece;
11262         piece = quickBoard[from]; quickBoard[from] = 0;
11263         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11264     } else
11265     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11266         quickBoard[(fromY<<4)+toX] = 0;
11267         moveDatabase[movePtr].piece = Q_EP;
11268         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11269         moveDatabase[movePtr].to = sq;
11270     } else
11271     if(promoPiece != pieceType[piece]) {
11272         moveDatabase[movePtr++].piece = Q_PROMO;
11273         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11274     }
11275     moveDatabase[movePtr].piece = piece;
11276     quickBoard[sq] = piece;
11277     movePtr++;
11278 }
11279
11280 int
11281 PackGame (Board board)
11282 {
11283     Move *newSpace = NULL;
11284     moveDatabase[movePtr].piece = 0; // terminate previous game
11285     if(movePtr > dataSize) {
11286         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11287         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11288         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11289         if(newSpace) {
11290             int i;
11291             Move *p = moveDatabase, *q = newSpace;
11292             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11293             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11294             moveDatabase = newSpace;
11295         } else { // calloc failed, we must be out of memory. Too bad...
11296             dataSize = 0; // prevent calloc events for all subsequent games
11297             return 0;     // and signal this one isn't cached
11298         }
11299     }
11300     movePtr++;
11301     MakePieceList(board, counts);
11302     return movePtr;
11303 }
11304
11305 int
11306 QuickCompare (Board board, int *minCounts, int *maxCounts)
11307 {   // compare according to search mode
11308     int r, f;
11309     switch(appData.searchMode)
11310     {
11311       case 1: // exact position match
11312         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11313         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11314             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11315         }
11316         break;
11317       case 2: // can have extra material on empty squares
11318         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11319             if(board[r][f] == EmptySquare) continue;
11320             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11321         }
11322         break;
11323       case 3: // material with exact Pawn structure
11324         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11325             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11326             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11327         } // fall through to material comparison
11328       case 4: // exact material
11329         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11330         break;
11331       case 6: // material range with given imbalance
11332         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11333         // fall through to range comparison
11334       case 5: // material range
11335         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11336     }
11337     return TRUE;
11338 }
11339
11340 int
11341 QuickScan (Board board, Move *move)
11342 {   // reconstruct game,and compare all positions in it
11343     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11344     do {
11345         int piece = move->piece;
11346         int to = move->to, from = pieceList[piece];
11347         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11348           if(!piece) return -1;
11349           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11350             piece = (++move)->piece;
11351             from = pieceList[piece];
11352             counts[pieceType[piece]]--;
11353             pieceType[piece] = (ChessSquare) move->to;
11354             counts[move->to]++;
11355           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11356             counts[pieceType[quickBoard[to]]]--;
11357             quickBoard[to] = 0; total--;
11358             move++;
11359             continue;
11360           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11361             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11362             from  = pieceList[piece]; // so this must be King
11363             quickBoard[from] = 0;
11364             quickBoard[to] = piece;
11365             pieceList[piece] = to;
11366             move++;
11367             continue;
11368           }
11369         }
11370         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11371         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11372         quickBoard[from] = 0;
11373         quickBoard[to] = piece;
11374         pieceList[piece] = to;
11375         cnt++; turn ^= 3;
11376         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11377            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11378            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11379                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11380           ) {
11381             static int lastCounts[EmptySquare+1];
11382             int i;
11383             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11384             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11385         } else stretch = 0;
11386         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11387         move++;
11388     } while(1);
11389 }
11390
11391 void
11392 InitSearch ()
11393 {
11394     int r, f;
11395     flipSearch = FALSE;
11396     CopyBoard(soughtBoard, boards[currentMove]);
11397     soughtTotal = MakePieceList(soughtBoard, maxSought);
11398     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11399     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11400     CopyBoard(reverseBoard, boards[currentMove]);
11401     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11402         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11403         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11404         reverseBoard[r][f] = piece;
11405     }
11406     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11407     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11408     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11409                  || (boards[currentMove][CASTLING][2] == NoRights || 
11410                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11411                  && (boards[currentMove][CASTLING][5] == NoRights || 
11412                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11413       ) {
11414         flipSearch = TRUE;
11415         CopyBoard(flipBoard, soughtBoard);
11416         CopyBoard(rotateBoard, reverseBoard);
11417         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11418             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11419             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11420         }
11421     }
11422     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11423     if(appData.searchMode >= 5) {
11424         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11425         MakePieceList(soughtBoard, minSought);
11426         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11427     }
11428     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11429         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11430 }
11431
11432 GameInfo dummyInfo;
11433
11434 int
11435 GameContainsPosition (FILE *f, ListGame *lg)
11436 {
11437     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11438     int fromX, fromY, toX, toY;
11439     char promoChar;
11440     static int initDone=FALSE;
11441
11442     // weed out games based on numerical tag comparison
11443     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11444     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11445     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11446     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11447     if(!initDone) {
11448         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11449         initDone = TRUE;
11450     }
11451     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11452     else CopyBoard(boards[scratch], initialPosition); // default start position
11453     if(lg->moves) {
11454         turn = btm + 1;
11455         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11456         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11457     }
11458     if(btm) plyNr++;
11459     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11460     fseek(f, lg->offset, 0);
11461     yynewfile(f);
11462     while(1) {
11463         yyboardindex = scratch;
11464         quickFlag = plyNr+1;
11465         next = Myylex();
11466         quickFlag = 0;
11467         switch(next) {
11468             case PGNTag:
11469                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11470             default:
11471                 continue;
11472
11473             case XBoardGame:
11474             case GNUChessGame:
11475                 if(plyNr) return -1; // after we have seen moves, this is for new game
11476               continue;
11477
11478             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11479             case ImpossibleMove:
11480             case WhiteWins: // game ends here with these four
11481             case BlackWins:
11482             case GameIsDrawn:
11483             case GameUnfinished:
11484                 return -1;
11485
11486             case IllegalMove:
11487                 if(appData.testLegality) return -1;
11488             case WhiteCapturesEnPassant:
11489             case BlackCapturesEnPassant:
11490             case WhitePromotion:
11491             case BlackPromotion:
11492             case WhiteNonPromotion:
11493             case BlackNonPromotion:
11494             case NormalMove:
11495             case WhiteKingSideCastle:
11496             case WhiteQueenSideCastle:
11497             case BlackKingSideCastle:
11498             case BlackQueenSideCastle:
11499             case WhiteKingSideCastleWild:
11500             case WhiteQueenSideCastleWild:
11501             case BlackKingSideCastleWild:
11502             case BlackQueenSideCastleWild:
11503             case WhiteHSideCastleFR:
11504             case WhiteASideCastleFR:
11505             case BlackHSideCastleFR:
11506             case BlackASideCastleFR:
11507                 fromX = currentMoveString[0] - AAA;
11508                 fromY = currentMoveString[1] - ONE;
11509                 toX = currentMoveString[2] - AAA;
11510                 toY = currentMoveString[3] - ONE;
11511                 promoChar = currentMoveString[4];
11512                 break;
11513             case WhiteDrop:
11514             case BlackDrop:
11515                 fromX = next == WhiteDrop ?
11516                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11517                   (int) CharToPiece(ToLower(currentMoveString[0]));
11518                 fromY = DROP_RANK;
11519                 toX = currentMoveString[2] - AAA;
11520                 toY = currentMoveString[3] - ONE;
11521                 promoChar = 0;
11522                 break;
11523         }
11524         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11525         plyNr++;
11526         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11527         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11528         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11529         if(appData.findMirror) {
11530             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11531             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11532         }
11533     }
11534 }
11535
11536 /* Load the nth game from open file f */
11537 int
11538 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11539 {
11540     ChessMove cm;
11541     char buf[MSG_SIZ];
11542     int gn = gameNumber;
11543     ListGame *lg = NULL;
11544     int numPGNTags = 0;
11545     int err, pos = -1;
11546     GameMode oldGameMode;
11547     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11548
11549     if (appData.debugMode)
11550         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11551
11552     if (gameMode == Training )
11553         SetTrainingModeOff();
11554
11555     oldGameMode = gameMode;
11556     if (gameMode != BeginningOfGame) {
11557       Reset(FALSE, TRUE);
11558     }
11559
11560     gameFileFP = f;
11561     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11562         fclose(lastLoadGameFP);
11563     }
11564
11565     if (useList) {
11566         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11567
11568         if (lg) {
11569             fseek(f, lg->offset, 0);
11570             GameListHighlight(gameNumber);
11571             pos = lg->position;
11572             gn = 1;
11573         }
11574         else {
11575             DisplayError(_("Game number out of range"), 0);
11576             return FALSE;
11577         }
11578     } else {
11579         GameListDestroy();
11580         if (fseek(f, 0, 0) == -1) {
11581             if (f == lastLoadGameFP ?
11582                 gameNumber == lastLoadGameNumber + 1 :
11583                 gameNumber == 1) {
11584                 gn = 1;
11585             } else {
11586                 DisplayError(_("Can't seek on game file"), 0);
11587                 return FALSE;
11588             }
11589         }
11590     }
11591     lastLoadGameFP = f;
11592     lastLoadGameNumber = gameNumber;
11593     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11594     lastLoadGameUseList = useList;
11595
11596     yynewfile(f);
11597
11598     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11599       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11600                 lg->gameInfo.black);
11601             DisplayTitle(buf);
11602     } else if (*title != NULLCHAR) {
11603         if (gameNumber > 1) {
11604           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11605             DisplayTitle(buf);
11606         } else {
11607             DisplayTitle(title);
11608         }
11609     }
11610
11611     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11612         gameMode = PlayFromGameFile;
11613         ModeHighlight();
11614     }
11615
11616     currentMove = forwardMostMove = backwardMostMove = 0;
11617     CopyBoard(boards[0], initialPosition);
11618     StopClocks();
11619
11620     /*
11621      * Skip the first gn-1 games in the file.
11622      * Also skip over anything that precedes an identifiable
11623      * start of game marker, to avoid being confused by
11624      * garbage at the start of the file.  Currently
11625      * recognized start of game markers are the move number "1",
11626      * the pattern "gnuchess .* game", the pattern
11627      * "^[#;%] [^ ]* game file", and a PGN tag block.
11628      * A game that starts with one of the latter two patterns
11629      * will also have a move number 1, possibly
11630      * following a position diagram.
11631      * 5-4-02: Let's try being more lenient and allowing a game to
11632      * start with an unnumbered move.  Does that break anything?
11633      */
11634     cm = lastLoadGameStart = EndOfFile;
11635     while (gn > 0) {
11636         yyboardindex = forwardMostMove;
11637         cm = (ChessMove) Myylex();
11638         switch (cm) {
11639           case EndOfFile:
11640             if (cmailMsgLoaded) {
11641                 nCmailGames = CMAIL_MAX_GAMES - gn;
11642             } else {
11643                 Reset(TRUE, TRUE);
11644                 DisplayError(_("Game not found in file"), 0);
11645             }
11646             return FALSE;
11647
11648           case GNUChessGame:
11649           case XBoardGame:
11650             gn--;
11651             lastLoadGameStart = cm;
11652             break;
11653
11654           case MoveNumberOne:
11655             switch (lastLoadGameStart) {
11656               case GNUChessGame:
11657               case XBoardGame:
11658               case PGNTag:
11659                 break;
11660               case MoveNumberOne:
11661               case EndOfFile:
11662                 gn--;           /* count this game */
11663                 lastLoadGameStart = cm;
11664                 break;
11665               default:
11666                 /* impossible */
11667                 break;
11668             }
11669             break;
11670
11671           case PGNTag:
11672             switch (lastLoadGameStart) {
11673               case GNUChessGame:
11674               case PGNTag:
11675               case MoveNumberOne:
11676               case EndOfFile:
11677                 gn--;           /* count this game */
11678                 lastLoadGameStart = cm;
11679                 break;
11680               case XBoardGame:
11681                 lastLoadGameStart = cm; /* game counted already */
11682                 break;
11683               default:
11684                 /* impossible */
11685                 break;
11686             }
11687             if (gn > 0) {
11688                 do {
11689                     yyboardindex = forwardMostMove;
11690                     cm = (ChessMove) Myylex();
11691                 } while (cm == PGNTag || cm == Comment);
11692             }
11693             break;
11694
11695           case WhiteWins:
11696           case BlackWins:
11697           case GameIsDrawn:
11698             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11699                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11700                     != CMAIL_OLD_RESULT) {
11701                     nCmailResults ++ ;
11702                     cmailResult[  CMAIL_MAX_GAMES
11703                                 - gn - 1] = CMAIL_OLD_RESULT;
11704                 }
11705             }
11706             break;
11707
11708           case NormalMove:
11709             /* Only a NormalMove can be at the start of a game
11710              * without a position diagram. */
11711             if (lastLoadGameStart == EndOfFile ) {
11712               gn--;
11713               lastLoadGameStart = MoveNumberOne;
11714             }
11715             break;
11716
11717           default:
11718             break;
11719         }
11720     }
11721
11722     if (appData.debugMode)
11723       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11724
11725     if (cm == XBoardGame) {
11726         /* Skip any header junk before position diagram and/or move 1 */
11727         for (;;) {
11728             yyboardindex = forwardMostMove;
11729             cm = (ChessMove) Myylex();
11730
11731             if (cm == EndOfFile ||
11732                 cm == GNUChessGame || cm == XBoardGame) {
11733                 /* Empty game; pretend end-of-file and handle later */
11734                 cm = EndOfFile;
11735                 break;
11736             }
11737
11738             if (cm == MoveNumberOne || cm == PositionDiagram ||
11739                 cm == PGNTag || cm == Comment)
11740               break;
11741         }
11742     } else if (cm == GNUChessGame) {
11743         if (gameInfo.event != NULL) {
11744             free(gameInfo.event);
11745         }
11746         gameInfo.event = StrSave(yy_text);
11747     }
11748
11749     startedFromSetupPosition = FALSE;
11750     while (cm == PGNTag) {
11751         if (appData.debugMode)
11752           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11753         err = ParsePGNTag(yy_text, &gameInfo);
11754         if (!err) numPGNTags++;
11755
11756         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11757         if(gameInfo.variant != oldVariant) {
11758             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11759             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11760             InitPosition(TRUE);
11761             oldVariant = gameInfo.variant;
11762             if (appData.debugMode)
11763               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11764         }
11765
11766
11767         if (gameInfo.fen != NULL) {
11768           Board initial_position;
11769           startedFromSetupPosition = TRUE;
11770           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11771             Reset(TRUE, TRUE);
11772             DisplayError(_("Bad FEN position in file"), 0);
11773             return FALSE;
11774           }
11775           CopyBoard(boards[0], initial_position);
11776           if (blackPlaysFirst) {
11777             currentMove = forwardMostMove = backwardMostMove = 1;
11778             CopyBoard(boards[1], initial_position);
11779             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11780             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11781             timeRemaining[0][1] = whiteTimeRemaining;
11782             timeRemaining[1][1] = blackTimeRemaining;
11783             if (commentList[0] != NULL) {
11784               commentList[1] = commentList[0];
11785               commentList[0] = NULL;
11786             }
11787           } else {
11788             currentMove = forwardMostMove = backwardMostMove = 0;
11789           }
11790           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11791           {   int i;
11792               initialRulePlies = FENrulePlies;
11793               for( i=0; i< nrCastlingRights; i++ )
11794                   initialRights[i] = initial_position[CASTLING][i];
11795           }
11796           yyboardindex = forwardMostMove;
11797           free(gameInfo.fen);
11798           gameInfo.fen = NULL;
11799         }
11800
11801         yyboardindex = forwardMostMove;
11802         cm = (ChessMove) Myylex();
11803
11804         /* Handle comments interspersed among the tags */
11805         while (cm == Comment) {
11806             char *p;
11807             if (appData.debugMode)
11808               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11809             p = yy_text;
11810             AppendComment(currentMove, p, FALSE);
11811             yyboardindex = forwardMostMove;
11812             cm = (ChessMove) Myylex();
11813         }
11814     }
11815
11816     /* don't rely on existence of Event tag since if game was
11817      * pasted from clipboard the Event tag may not exist
11818      */
11819     if (numPGNTags > 0){
11820         char *tags;
11821         if (gameInfo.variant == VariantNormal) {
11822           VariantClass v = StringToVariant(gameInfo.event);
11823           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11824           if(v < VariantShogi) gameInfo.variant = v;
11825         }
11826         if (!matchMode) {
11827           if( appData.autoDisplayTags ) {
11828             tags = PGNTags(&gameInfo);
11829             TagsPopUp(tags, CmailMsg());
11830             free(tags);
11831           }
11832         }
11833     } else {
11834         /* Make something up, but don't display it now */
11835         SetGameInfo();
11836         TagsPopDown();
11837     }
11838
11839     if (cm == PositionDiagram) {
11840         int i, j;
11841         char *p;
11842         Board initial_position;
11843
11844         if (appData.debugMode)
11845           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11846
11847         if (!startedFromSetupPosition) {
11848             p = yy_text;
11849             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11850               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11851                 switch (*p) {
11852                   case '{':
11853                   case '[':
11854                   case '-':
11855                   case ' ':
11856                   case '\t':
11857                   case '\n':
11858                   case '\r':
11859                     break;
11860                   default:
11861                     initial_position[i][j++] = CharToPiece(*p);
11862                     break;
11863                 }
11864             while (*p == ' ' || *p == '\t' ||
11865                    *p == '\n' || *p == '\r') p++;
11866
11867             if (strncmp(p, "black", strlen("black"))==0)
11868               blackPlaysFirst = TRUE;
11869             else
11870               blackPlaysFirst = FALSE;
11871             startedFromSetupPosition = TRUE;
11872
11873             CopyBoard(boards[0], initial_position);
11874             if (blackPlaysFirst) {
11875                 currentMove = forwardMostMove = backwardMostMove = 1;
11876                 CopyBoard(boards[1], initial_position);
11877                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11878                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11879                 timeRemaining[0][1] = whiteTimeRemaining;
11880                 timeRemaining[1][1] = blackTimeRemaining;
11881                 if (commentList[0] != NULL) {
11882                     commentList[1] = commentList[0];
11883                     commentList[0] = NULL;
11884                 }
11885             } else {
11886                 currentMove = forwardMostMove = backwardMostMove = 0;
11887             }
11888         }
11889         yyboardindex = forwardMostMove;
11890         cm = (ChessMove) Myylex();
11891     }
11892
11893     if (first.pr == NoProc) {
11894         StartChessProgram(&first);
11895     }
11896     InitChessProgram(&first, FALSE);
11897     SendToProgram("force\n", &first);
11898     if (startedFromSetupPosition) {
11899         SendBoard(&first, forwardMostMove);
11900     if (appData.debugMode) {
11901         fprintf(debugFP, "Load Game\n");
11902     }
11903         DisplayBothClocks();
11904     }
11905
11906     /* [HGM] server: flag to write setup moves in broadcast file as one */
11907     loadFlag = appData.suppressLoadMoves;
11908
11909     while (cm == Comment) {
11910         char *p;
11911         if (appData.debugMode)
11912           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11913         p = yy_text;
11914         AppendComment(currentMove, p, FALSE);
11915         yyboardindex = forwardMostMove;
11916         cm = (ChessMove) Myylex();
11917     }
11918
11919     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11920         cm == WhiteWins || cm == BlackWins ||
11921         cm == GameIsDrawn || cm == GameUnfinished) {
11922         DisplayMessage("", _("No moves in game"));
11923         if (cmailMsgLoaded) {
11924             if (appData.debugMode)
11925               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11926             ClearHighlights();
11927             flipView = FALSE;
11928         }
11929         DrawPosition(FALSE, boards[currentMove]);
11930         DisplayBothClocks();
11931         gameMode = EditGame;
11932         ModeHighlight();
11933         gameFileFP = NULL;
11934         cmailOldMove = 0;
11935         return TRUE;
11936     }
11937
11938     // [HGM] PV info: routine tests if comment empty
11939     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11940         DisplayComment(currentMove - 1, commentList[currentMove]);
11941     }
11942     if (!matchMode && appData.timeDelay != 0)
11943       DrawPosition(FALSE, boards[currentMove]);
11944
11945     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11946       programStats.ok_to_send = 1;
11947     }
11948
11949     /* if the first token after the PGN tags is a move
11950      * and not move number 1, retrieve it from the parser
11951      */
11952     if (cm != MoveNumberOne)
11953         LoadGameOneMove(cm);
11954
11955     /* load the remaining moves from the file */
11956     while (LoadGameOneMove(EndOfFile)) {
11957       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11958       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11959     }
11960
11961     /* rewind to the start of the game */
11962     currentMove = backwardMostMove;
11963
11964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11965
11966     if (oldGameMode == AnalyzeFile ||
11967         oldGameMode == AnalyzeMode) {
11968       AnalyzeFileEvent();
11969     }
11970
11971     if (!matchMode && pos >= 0) {
11972         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11973     } else
11974     if (matchMode || appData.timeDelay == 0) {
11975       ToEndEvent();
11976     } else if (appData.timeDelay > 0) {
11977       AutoPlayGameLoop();
11978     }
11979
11980     if (appData.debugMode)
11981         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11982
11983     loadFlag = 0; /* [HGM] true game starts */
11984     return TRUE;
11985 }
11986
11987 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11988 int
11989 ReloadPosition (int offset)
11990 {
11991     int positionNumber = lastLoadPositionNumber + offset;
11992     if (lastLoadPositionFP == NULL) {
11993         DisplayError(_("No position has been loaded yet"), 0);
11994         return FALSE;
11995     }
11996     if (positionNumber <= 0) {
11997         DisplayError(_("Can't back up any further"), 0);
11998         return FALSE;
11999     }
12000     return LoadPosition(lastLoadPositionFP, positionNumber,
12001                         lastLoadPositionTitle);
12002 }
12003
12004 /* Load the nth position from the given file */
12005 int
12006 LoadPositionFromFile (char *filename, int n, char *title)
12007 {
12008     FILE *f;
12009     char buf[MSG_SIZ];
12010
12011     if (strcmp(filename, "-") == 0) {
12012         return LoadPosition(stdin, n, "stdin");
12013     } else {
12014         f = fopen(filename, "rb");
12015         if (f == NULL) {
12016             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12017             DisplayError(buf, errno);
12018             return FALSE;
12019         } else {
12020             return LoadPosition(f, n, title);
12021         }
12022     }
12023 }
12024
12025 /* Load the nth position from the given open file, and close it */
12026 int
12027 LoadPosition (FILE *f, int positionNumber, char *title)
12028 {
12029     char *p, line[MSG_SIZ];
12030     Board initial_position;
12031     int i, j, fenMode, pn;
12032
12033     if (gameMode == Training )
12034         SetTrainingModeOff();
12035
12036     if (gameMode != BeginningOfGame) {
12037         Reset(FALSE, TRUE);
12038     }
12039     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12040         fclose(lastLoadPositionFP);
12041     }
12042     if (positionNumber == 0) positionNumber = 1;
12043     lastLoadPositionFP = f;
12044     lastLoadPositionNumber = positionNumber;
12045     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12046     if (first.pr == NoProc && !appData.noChessProgram) {
12047       StartChessProgram(&first);
12048       InitChessProgram(&first, FALSE);
12049     }
12050     pn = positionNumber;
12051     if (positionNumber < 0) {
12052         /* Negative position number means to seek to that byte offset */
12053         if (fseek(f, -positionNumber, 0) == -1) {
12054             DisplayError(_("Can't seek on position file"), 0);
12055             return FALSE;
12056         };
12057         pn = 1;
12058     } else {
12059         if (fseek(f, 0, 0) == -1) {
12060             if (f == lastLoadPositionFP ?
12061                 positionNumber == lastLoadPositionNumber + 1 :
12062                 positionNumber == 1) {
12063                 pn = 1;
12064             } else {
12065                 DisplayError(_("Can't seek on position file"), 0);
12066                 return FALSE;
12067             }
12068         }
12069     }
12070     /* See if this file is FEN or old-style xboard */
12071     if (fgets(line, MSG_SIZ, f) == NULL) {
12072         DisplayError(_("Position not found in file"), 0);
12073         return FALSE;
12074     }
12075     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12076     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12077
12078     if (pn >= 2) {
12079         if (fenMode || line[0] == '#') pn--;
12080         while (pn > 0) {
12081             /* skip positions before number pn */
12082             if (fgets(line, MSG_SIZ, f) == NULL) {
12083                 Reset(TRUE, TRUE);
12084                 DisplayError(_("Position not found in file"), 0);
12085                 return FALSE;
12086             }
12087             if (fenMode || line[0] == '#') pn--;
12088         }
12089     }
12090
12091     if (fenMode) {
12092         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12093             DisplayError(_("Bad FEN position in file"), 0);
12094             return FALSE;
12095         }
12096     } else {
12097         (void) fgets(line, MSG_SIZ, f);
12098         (void) fgets(line, MSG_SIZ, f);
12099
12100         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12101             (void) fgets(line, MSG_SIZ, f);
12102             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12103                 if (*p == ' ')
12104                   continue;
12105                 initial_position[i][j++] = CharToPiece(*p);
12106             }
12107         }
12108
12109         blackPlaysFirst = FALSE;
12110         if (!feof(f)) {
12111             (void) fgets(line, MSG_SIZ, f);
12112             if (strncmp(line, "black", strlen("black"))==0)
12113               blackPlaysFirst = TRUE;
12114         }
12115     }
12116     startedFromSetupPosition = TRUE;
12117
12118     CopyBoard(boards[0], initial_position);
12119     if (blackPlaysFirst) {
12120         currentMove = forwardMostMove = backwardMostMove = 1;
12121         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12122         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12123         CopyBoard(boards[1], initial_position);
12124         DisplayMessage("", _("Black to play"));
12125     } else {
12126         currentMove = forwardMostMove = backwardMostMove = 0;
12127         DisplayMessage("", _("White to play"));
12128     }
12129     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12130     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12131         SendToProgram("force\n", &first);
12132         SendBoard(&first, forwardMostMove);
12133     }
12134     if (appData.debugMode) {
12135 int i, j;
12136   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12137   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12138         fprintf(debugFP, "Load Position\n");
12139     }
12140
12141     if (positionNumber > 1) {
12142       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12143         DisplayTitle(line);
12144     } else {
12145         DisplayTitle(title);
12146     }
12147     gameMode = EditGame;
12148     ModeHighlight();
12149     ResetClocks();
12150     timeRemaining[0][1] = whiteTimeRemaining;
12151     timeRemaining[1][1] = blackTimeRemaining;
12152     DrawPosition(FALSE, boards[currentMove]);
12153
12154     return TRUE;
12155 }
12156
12157
12158 void
12159 CopyPlayerNameIntoFileName (char **dest, char *src)
12160 {
12161     while (*src != NULLCHAR && *src != ',') {
12162         if (*src == ' ') {
12163             *(*dest)++ = '_';
12164             src++;
12165         } else {
12166             *(*dest)++ = *src++;
12167         }
12168     }
12169 }
12170
12171 char *
12172 DefaultFileName (char *ext)
12173 {
12174     static char def[MSG_SIZ];
12175     char *p;
12176
12177     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12178         p = def;
12179         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12180         *p++ = '-';
12181         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12182         *p++ = '.';
12183         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12184     } else {
12185         def[0] = NULLCHAR;
12186     }
12187     return def;
12188 }
12189
12190 /* Save the current game to the given file */
12191 int
12192 SaveGameToFile (char *filename, int append)
12193 {
12194     FILE *f;
12195     char buf[MSG_SIZ];
12196     int result, i, t,tot=0;
12197
12198     if (strcmp(filename, "-") == 0) {
12199         return SaveGame(stdout, 0, NULL);
12200     } else {
12201         for(i=0; i<10; i++) { // upto 10 tries
12202              f = fopen(filename, append ? "a" : "w");
12203              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12204              if(f || errno != 13) break;
12205              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12206              tot += t;
12207         }
12208         if (f == NULL) {
12209             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12210             DisplayError(buf, errno);
12211             return FALSE;
12212         } else {
12213             safeStrCpy(buf, lastMsg, MSG_SIZ);
12214             DisplayMessage(_("Waiting for access to save file"), "");
12215             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12216             DisplayMessage(_("Saving game"), "");
12217             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12218             result = SaveGame(f, 0, NULL);
12219             DisplayMessage(buf, "");
12220             return result;
12221         }
12222     }
12223 }
12224
12225 char *
12226 SavePart (char *str)
12227 {
12228     static char buf[MSG_SIZ];
12229     char *p;
12230
12231     p = strchr(str, ' ');
12232     if (p == NULL) return str;
12233     strncpy(buf, str, p - str);
12234     buf[p - str] = NULLCHAR;
12235     return buf;
12236 }
12237
12238 #define PGN_MAX_LINE 75
12239
12240 #define PGN_SIDE_WHITE  0
12241 #define PGN_SIDE_BLACK  1
12242
12243 static int
12244 FindFirstMoveOutOfBook (int side)
12245 {
12246     int result = -1;
12247
12248     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12249         int index = backwardMostMove;
12250         int has_book_hit = 0;
12251
12252         if( (index % 2) != side ) {
12253             index++;
12254         }
12255
12256         while( index < forwardMostMove ) {
12257             /* Check to see if engine is in book */
12258             int depth = pvInfoList[index].depth;
12259             int score = pvInfoList[index].score;
12260             int in_book = 0;
12261
12262             if( depth <= 2 ) {
12263                 in_book = 1;
12264             }
12265             else if( score == 0 && depth == 63 ) {
12266                 in_book = 1; /* Zappa */
12267             }
12268             else if( score == 2 && depth == 99 ) {
12269                 in_book = 1; /* Abrok */
12270             }
12271
12272             has_book_hit += in_book;
12273
12274             if( ! in_book ) {
12275                 result = index;
12276
12277                 break;
12278             }
12279
12280             index += 2;
12281         }
12282     }
12283
12284     return result;
12285 }
12286
12287 void
12288 GetOutOfBookInfo (char * buf)
12289 {
12290     int oob[2];
12291     int i;
12292     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12293
12294     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12295     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12296
12297     *buf = '\0';
12298
12299     if( oob[0] >= 0 || oob[1] >= 0 ) {
12300         for( i=0; i<2; i++ ) {
12301             int idx = oob[i];
12302
12303             if( idx >= 0 ) {
12304                 if( i > 0 && oob[0] >= 0 ) {
12305                     strcat( buf, "   " );
12306                 }
12307
12308                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12309                 sprintf( buf+strlen(buf), "%s%.2f",
12310                     pvInfoList[idx].score >= 0 ? "+" : "",
12311                     pvInfoList[idx].score / 100.0 );
12312             }
12313         }
12314     }
12315 }
12316
12317 /* Save game in PGN style and close the file */
12318 int
12319 SaveGamePGN (FILE *f)
12320 {
12321     int i, offset, linelen, newblock;
12322     time_t tm;
12323 //    char *movetext;
12324     char numtext[32];
12325     int movelen, numlen, blank;
12326     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12327
12328     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12329
12330     tm = time((time_t *) NULL);
12331
12332     PrintPGNTags(f, &gameInfo);
12333
12334     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12335
12336     if (backwardMostMove > 0 || startedFromSetupPosition) {
12337         char *fen = PositionToFEN(backwardMostMove, NULL);
12338         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12339         fprintf(f, "\n{--------------\n");
12340         PrintPosition(f, backwardMostMove);
12341         fprintf(f, "--------------}\n");
12342         free(fen);
12343     }
12344     else {
12345         /* [AS] Out of book annotation */
12346         if( appData.saveOutOfBookInfo ) {
12347             char buf[64];
12348
12349             GetOutOfBookInfo( buf );
12350
12351             if( buf[0] != '\0' ) {
12352                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12353             }
12354         }
12355
12356         fprintf(f, "\n");
12357     }
12358
12359     i = backwardMostMove;
12360     linelen = 0;
12361     newblock = TRUE;
12362
12363     while (i < forwardMostMove) {
12364         /* Print comments preceding this move */
12365         if (commentList[i] != NULL) {
12366             if (linelen > 0) fprintf(f, "\n");
12367             fprintf(f, "%s", commentList[i]);
12368             linelen = 0;
12369             newblock = TRUE;
12370         }
12371
12372         /* Format move number */
12373         if ((i % 2) == 0)
12374           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12375         else
12376           if (newblock)
12377             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12378           else
12379             numtext[0] = NULLCHAR;
12380
12381         numlen = strlen(numtext);
12382         newblock = FALSE;
12383
12384         /* Print move number */
12385         blank = linelen > 0 && numlen > 0;
12386         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12387             fprintf(f, "\n");
12388             linelen = 0;
12389             blank = 0;
12390         }
12391         if (blank) {
12392             fprintf(f, " ");
12393             linelen++;
12394         }
12395         fprintf(f, "%s", numtext);
12396         linelen += numlen;
12397
12398         /* Get move */
12399         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12400         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12401
12402         /* Print move */
12403         blank = linelen > 0 && movelen > 0;
12404         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12405             fprintf(f, "\n");
12406             linelen = 0;
12407             blank = 0;
12408         }
12409         if (blank) {
12410             fprintf(f, " ");
12411             linelen++;
12412         }
12413         fprintf(f, "%s", move_buffer);
12414         linelen += movelen;
12415
12416         /* [AS] Add PV info if present */
12417         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12418             /* [HGM] add time */
12419             char buf[MSG_SIZ]; int seconds;
12420
12421             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12422
12423             if( seconds <= 0)
12424               buf[0] = 0;
12425             else
12426               if( seconds < 30 )
12427                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12428               else
12429                 {
12430                   seconds = (seconds + 4)/10; // round to full seconds
12431                   if( seconds < 60 )
12432                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12433                   else
12434                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12435                 }
12436
12437             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12438                       pvInfoList[i].score >= 0 ? "+" : "",
12439                       pvInfoList[i].score / 100.0,
12440                       pvInfoList[i].depth,
12441                       buf );
12442
12443             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12444
12445             /* Print score/depth */
12446             blank = linelen > 0 && movelen > 0;
12447             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12448                 fprintf(f, "\n");
12449                 linelen = 0;
12450                 blank = 0;
12451             }
12452             if (blank) {
12453                 fprintf(f, " ");
12454                 linelen++;
12455             }
12456             fprintf(f, "%s", move_buffer);
12457             linelen += movelen;
12458         }
12459
12460         i++;
12461     }
12462
12463     /* Start a new line */
12464     if (linelen > 0) fprintf(f, "\n");
12465
12466     /* Print comments after last move */
12467     if (commentList[i] != NULL) {
12468         fprintf(f, "%s\n", commentList[i]);
12469     }
12470
12471     /* Print result */
12472     if (gameInfo.resultDetails != NULL &&
12473         gameInfo.resultDetails[0] != NULLCHAR) {
12474         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12475                 PGNResult(gameInfo.result));
12476     } else {
12477         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12478     }
12479
12480     fclose(f);
12481     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12482     return TRUE;
12483 }
12484
12485 /* Save game in old style and close the file */
12486 int
12487 SaveGameOldStyle (FILE *f)
12488 {
12489     int i, offset;
12490     time_t tm;
12491
12492     tm = time((time_t *) NULL);
12493
12494     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12495     PrintOpponents(f);
12496
12497     if (backwardMostMove > 0 || startedFromSetupPosition) {
12498         fprintf(f, "\n[--------------\n");
12499         PrintPosition(f, backwardMostMove);
12500         fprintf(f, "--------------]\n");
12501     } else {
12502         fprintf(f, "\n");
12503     }
12504
12505     i = backwardMostMove;
12506     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12507
12508     while (i < forwardMostMove) {
12509         if (commentList[i] != NULL) {
12510             fprintf(f, "[%s]\n", commentList[i]);
12511         }
12512
12513         if ((i % 2) == 1) {
12514             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12515             i++;
12516         } else {
12517             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12518             i++;
12519             if (commentList[i] != NULL) {
12520                 fprintf(f, "\n");
12521                 continue;
12522             }
12523             if (i >= forwardMostMove) {
12524                 fprintf(f, "\n");
12525                 break;
12526             }
12527             fprintf(f, "%s\n", parseList[i]);
12528             i++;
12529         }
12530     }
12531
12532     if (commentList[i] != NULL) {
12533         fprintf(f, "[%s]\n", commentList[i]);
12534     }
12535
12536     /* This isn't really the old style, but it's close enough */
12537     if (gameInfo.resultDetails != NULL &&
12538         gameInfo.resultDetails[0] != NULLCHAR) {
12539         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12540                 gameInfo.resultDetails);
12541     } else {
12542         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12543     }
12544
12545     fclose(f);
12546     return TRUE;
12547 }
12548
12549 /* Save the current game to open file f and close the file */
12550 int
12551 SaveGame (FILE *f, int dummy, char *dummy2)
12552 {
12553     if (gameMode == EditPosition) EditPositionDone(TRUE);
12554     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12555     if (appData.oldSaveStyle)
12556       return SaveGameOldStyle(f);
12557     else
12558       return SaveGamePGN(f);
12559 }
12560
12561 /* Save the current position to the given file */
12562 int
12563 SavePositionToFile (char *filename)
12564 {
12565     FILE *f;
12566     char buf[MSG_SIZ];
12567
12568     if (strcmp(filename, "-") == 0) {
12569         return SavePosition(stdout, 0, NULL);
12570     } else {
12571         f = fopen(filename, "a");
12572         if (f == NULL) {
12573             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12574             DisplayError(buf, errno);
12575             return FALSE;
12576         } else {
12577             safeStrCpy(buf, lastMsg, MSG_SIZ);
12578             DisplayMessage(_("Waiting for access to save file"), "");
12579             flock(fileno(f), LOCK_EX); // [HGM] lock
12580             DisplayMessage(_("Saving position"), "");
12581             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12582             SavePosition(f, 0, NULL);
12583             DisplayMessage(buf, "");
12584             return TRUE;
12585         }
12586     }
12587 }
12588
12589 /* Save the current position to the given open file and close the file */
12590 int
12591 SavePosition (FILE *f, int dummy, char *dummy2)
12592 {
12593     time_t tm;
12594     char *fen;
12595
12596     if (gameMode == EditPosition) EditPositionDone(TRUE);
12597     if (appData.oldSaveStyle) {
12598         tm = time((time_t *) NULL);
12599
12600         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12601         PrintOpponents(f);
12602         fprintf(f, "[--------------\n");
12603         PrintPosition(f, currentMove);
12604         fprintf(f, "--------------]\n");
12605     } else {
12606         fen = PositionToFEN(currentMove, NULL);
12607         fprintf(f, "%s\n", fen);
12608         free(fen);
12609     }
12610     fclose(f);
12611     return TRUE;
12612 }
12613
12614 void
12615 ReloadCmailMsgEvent (int unregister)
12616 {
12617 #if !WIN32
12618     static char *inFilename = NULL;
12619     static char *outFilename;
12620     int i;
12621     struct stat inbuf, outbuf;
12622     int status;
12623
12624     /* Any registered moves are unregistered if unregister is set, */
12625     /* i.e. invoked by the signal handler */
12626     if (unregister) {
12627         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12628             cmailMoveRegistered[i] = FALSE;
12629             if (cmailCommentList[i] != NULL) {
12630                 free(cmailCommentList[i]);
12631                 cmailCommentList[i] = NULL;
12632             }
12633         }
12634         nCmailMovesRegistered = 0;
12635     }
12636
12637     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12638         cmailResult[i] = CMAIL_NOT_RESULT;
12639     }
12640     nCmailResults = 0;
12641
12642     if (inFilename == NULL) {
12643         /* Because the filenames are static they only get malloced once  */
12644         /* and they never get freed                                      */
12645         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12646         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12647
12648         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12649         sprintf(outFilename, "%s.out", appData.cmailGameName);
12650     }
12651
12652     status = stat(outFilename, &outbuf);
12653     if (status < 0) {
12654         cmailMailedMove = FALSE;
12655     } else {
12656         status = stat(inFilename, &inbuf);
12657         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12658     }
12659
12660     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12661        counts the games, notes how each one terminated, etc.
12662
12663        It would be nice to remove this kludge and instead gather all
12664        the information while building the game list.  (And to keep it
12665        in the game list nodes instead of having a bunch of fixed-size
12666        parallel arrays.)  Note this will require getting each game's
12667        termination from the PGN tags, as the game list builder does
12668        not process the game moves.  --mann
12669        */
12670     cmailMsgLoaded = TRUE;
12671     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12672
12673     /* Load first game in the file or popup game menu */
12674     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12675
12676 #endif /* !WIN32 */
12677     return;
12678 }
12679
12680 int
12681 RegisterMove ()
12682 {
12683     FILE *f;
12684     char string[MSG_SIZ];
12685
12686     if (   cmailMailedMove
12687         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12688         return TRUE;            /* Allow free viewing  */
12689     }
12690
12691     /* Unregister move to ensure that we don't leave RegisterMove        */
12692     /* with the move registered when the conditions for registering no   */
12693     /* longer hold                                                       */
12694     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12695         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12696         nCmailMovesRegistered --;
12697
12698         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12699           {
12700               free(cmailCommentList[lastLoadGameNumber - 1]);
12701               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12702           }
12703     }
12704
12705     if (cmailOldMove == -1) {
12706         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12707         return FALSE;
12708     }
12709
12710     if (currentMove > cmailOldMove + 1) {
12711         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12712         return FALSE;
12713     }
12714
12715     if (currentMove < cmailOldMove) {
12716         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12717         return FALSE;
12718     }
12719
12720     if (forwardMostMove > currentMove) {
12721         /* Silently truncate extra moves */
12722         TruncateGame();
12723     }
12724
12725     if (   (currentMove == cmailOldMove + 1)
12726         || (   (currentMove == cmailOldMove)
12727             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12728                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12729         if (gameInfo.result != GameUnfinished) {
12730             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12731         }
12732
12733         if (commentList[currentMove] != NULL) {
12734             cmailCommentList[lastLoadGameNumber - 1]
12735               = StrSave(commentList[currentMove]);
12736         }
12737         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12738
12739         if (appData.debugMode)
12740           fprintf(debugFP, "Saving %s for game %d\n",
12741                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12742
12743         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12744
12745         f = fopen(string, "w");
12746         if (appData.oldSaveStyle) {
12747             SaveGameOldStyle(f); /* also closes the file */
12748
12749             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12750             f = fopen(string, "w");
12751             SavePosition(f, 0, NULL); /* also closes the file */
12752         } else {
12753             fprintf(f, "{--------------\n");
12754             PrintPosition(f, currentMove);
12755             fprintf(f, "--------------}\n\n");
12756
12757             SaveGame(f, 0, NULL); /* also closes the file*/
12758         }
12759
12760         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12761         nCmailMovesRegistered ++;
12762     } else if (nCmailGames == 1) {
12763         DisplayError(_("You have not made a move yet"), 0);
12764         return FALSE;
12765     }
12766
12767     return TRUE;
12768 }
12769
12770 void
12771 MailMoveEvent ()
12772 {
12773 #if !WIN32
12774     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12775     FILE *commandOutput;
12776     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12777     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12778     int nBuffers;
12779     int i;
12780     int archived;
12781     char *arcDir;
12782
12783     if (! cmailMsgLoaded) {
12784         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12785         return;
12786     }
12787
12788     if (nCmailGames == nCmailResults) {
12789         DisplayError(_("No unfinished games"), 0);
12790         return;
12791     }
12792
12793 #if CMAIL_PROHIBIT_REMAIL
12794     if (cmailMailedMove) {
12795       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);
12796         DisplayError(msg, 0);
12797         return;
12798     }
12799 #endif
12800
12801     if (! (cmailMailedMove || RegisterMove())) return;
12802
12803     if (   cmailMailedMove
12804         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12805       snprintf(string, MSG_SIZ, partCommandString,
12806                appData.debugMode ? " -v" : "", appData.cmailGameName);
12807         commandOutput = popen(string, "r");
12808
12809         if (commandOutput == NULL) {
12810             DisplayError(_("Failed to invoke cmail"), 0);
12811         } else {
12812             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12813                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12814             }
12815             if (nBuffers > 1) {
12816                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12817                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12818                 nBytes = MSG_SIZ - 1;
12819             } else {
12820                 (void) memcpy(msg, buffer, nBytes);
12821             }
12822             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12823
12824             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12825                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12826
12827                 archived = TRUE;
12828                 for (i = 0; i < nCmailGames; i ++) {
12829                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12830                         archived = FALSE;
12831                     }
12832                 }
12833                 if (   archived
12834                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12835                         != NULL)) {
12836                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12837                            arcDir,
12838                            appData.cmailGameName,
12839                            gameInfo.date);
12840                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12841                     cmailMsgLoaded = FALSE;
12842                 }
12843             }
12844
12845             DisplayInformation(msg);
12846             pclose(commandOutput);
12847         }
12848     } else {
12849         if ((*cmailMsg) != '\0') {
12850             DisplayInformation(cmailMsg);
12851         }
12852     }
12853
12854     return;
12855 #endif /* !WIN32 */
12856 }
12857
12858 char *
12859 CmailMsg ()
12860 {
12861 #if WIN32
12862     return NULL;
12863 #else
12864     int  prependComma = 0;
12865     char number[5];
12866     char string[MSG_SIZ];       /* Space for game-list */
12867     int  i;
12868
12869     if (!cmailMsgLoaded) return "";
12870
12871     if (cmailMailedMove) {
12872       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12873     } else {
12874         /* Create a list of games left */
12875       snprintf(string, MSG_SIZ, "[");
12876         for (i = 0; i < nCmailGames; i ++) {
12877             if (! (   cmailMoveRegistered[i]
12878                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12879                 if (prependComma) {
12880                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12881                 } else {
12882                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12883                     prependComma = 1;
12884                 }
12885
12886                 strcat(string, number);
12887             }
12888         }
12889         strcat(string, "]");
12890
12891         if (nCmailMovesRegistered + nCmailResults == 0) {
12892             switch (nCmailGames) {
12893               case 1:
12894                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12895                 break;
12896
12897               case 2:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12899                 break;
12900
12901               default:
12902                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12903                          nCmailGames);
12904                 break;
12905             }
12906         } else {
12907             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12908               case 1:
12909                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12910                          string);
12911                 break;
12912
12913               case 0:
12914                 if (nCmailResults == nCmailGames) {
12915                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12916                 } else {
12917                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12918                 }
12919                 break;
12920
12921               default:
12922                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12923                          string);
12924             }
12925         }
12926     }
12927     return cmailMsg;
12928 #endif /* WIN32 */
12929 }
12930
12931 void
12932 ResetGameEvent ()
12933 {
12934     if (gameMode == Training)
12935       SetTrainingModeOff();
12936
12937     Reset(TRUE, TRUE);
12938     cmailMsgLoaded = FALSE;
12939     if (appData.icsActive) {
12940       SendToICS(ics_prefix);
12941       SendToICS("refresh\n");
12942     }
12943 }
12944
12945 void
12946 ExitEvent (int status)
12947 {
12948     exiting++;
12949     if (exiting > 2) {
12950       /* Give up on clean exit */
12951       exit(status);
12952     }
12953     if (exiting > 1) {
12954       /* Keep trying for clean exit */
12955       return;
12956     }
12957
12958     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12959
12960     if (telnetISR != NULL) {
12961       RemoveInputSource(telnetISR);
12962     }
12963     if (icsPR != NoProc) {
12964       DestroyChildProcess(icsPR, TRUE);
12965     }
12966
12967     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12968     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12969
12970     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12971     /* make sure this other one finishes before killing it!                  */
12972     if(endingGame) { int count = 0;
12973         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12974         while(endingGame && count++ < 10) DoSleep(1);
12975         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12976     }
12977
12978     /* Kill off chess programs */
12979     if (first.pr != NoProc) {
12980         ExitAnalyzeMode();
12981
12982         DoSleep( appData.delayBeforeQuit );
12983         SendToProgram("quit\n", &first);
12984         DoSleep( appData.delayAfterQuit );
12985         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12986     }
12987     if (second.pr != NoProc) {
12988         DoSleep( appData.delayBeforeQuit );
12989         SendToProgram("quit\n", &second);
12990         DoSleep( appData.delayAfterQuit );
12991         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12992     }
12993     if (first.isr != NULL) {
12994         RemoveInputSource(first.isr);
12995     }
12996     if (second.isr != NULL) {
12997         RemoveInputSource(second.isr);
12998     }
12999
13000     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13001     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13002
13003     ShutDownFrontEnd();
13004     exit(status);
13005 }
13006
13007 void
13008 PauseEvent ()
13009 {
13010     if (appData.debugMode)
13011         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13012     if (pausing) {
13013         pausing = FALSE;
13014         ModeHighlight();
13015         if (gameMode == MachinePlaysWhite ||
13016             gameMode == MachinePlaysBlack) {
13017             StartClocks();
13018         } else {
13019             DisplayBothClocks();
13020         }
13021         if (gameMode == PlayFromGameFile) {
13022             if (appData.timeDelay >= 0)
13023                 AutoPlayGameLoop();
13024         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13025             Reset(FALSE, TRUE);
13026             SendToICS(ics_prefix);
13027             SendToICS("refresh\n");
13028         } else if (currentMove < forwardMostMove) {
13029             ForwardInner(forwardMostMove);
13030         }
13031         pauseExamInvalid = FALSE;
13032     } else {
13033         switch (gameMode) {
13034           default:
13035             return;
13036           case IcsExamining:
13037             pauseExamForwardMostMove = forwardMostMove;
13038             pauseExamInvalid = FALSE;
13039             /* fall through */
13040           case IcsObserving:
13041           case IcsPlayingWhite:
13042           case IcsPlayingBlack:
13043             pausing = TRUE;
13044             ModeHighlight();
13045             return;
13046           case PlayFromGameFile:
13047             (void) StopLoadGameTimer();
13048             pausing = TRUE;
13049             ModeHighlight();
13050             break;
13051           case BeginningOfGame:
13052             if (appData.icsActive) return;
13053             /* else fall through */
13054           case MachinePlaysWhite:
13055           case MachinePlaysBlack:
13056           case TwoMachinesPlay:
13057             if (forwardMostMove == 0)
13058               return;           /* don't pause if no one has moved */
13059             if ((gameMode == MachinePlaysWhite &&
13060                  !WhiteOnMove(forwardMostMove)) ||
13061                 (gameMode == MachinePlaysBlack &&
13062                  WhiteOnMove(forwardMostMove))) {
13063                 StopClocks();
13064             }
13065             pausing = TRUE;
13066             ModeHighlight();
13067             break;
13068         }
13069     }
13070 }
13071
13072 void
13073 EditCommentEvent ()
13074 {
13075     char title[MSG_SIZ];
13076
13077     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13078       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13079     } else {
13080       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13081                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13082                parseList[currentMove - 1]);
13083     }
13084
13085     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13086 }
13087
13088
13089 void
13090 EditTagsEvent ()
13091 {
13092     char *tags = PGNTags(&gameInfo);
13093     bookUp = FALSE;
13094     EditTagsPopUp(tags, NULL);
13095     free(tags);
13096 }
13097
13098 void
13099 AnalyzeModeEvent ()
13100 {
13101     if (appData.noChessProgram || gameMode == AnalyzeMode)
13102       return;
13103
13104     if (gameMode != AnalyzeFile) {
13105         if (!appData.icsEngineAnalyze) {
13106                EditGameEvent();
13107                if (gameMode != EditGame) return;
13108         }
13109         ResurrectChessProgram();
13110         SendToProgram("analyze\n", &first);
13111         first.analyzing = TRUE;
13112         /*first.maybeThinking = TRUE;*/
13113         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13114         EngineOutputPopUp();
13115     }
13116     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13117     pausing = FALSE;
13118     ModeHighlight();
13119     SetGameInfo();
13120
13121     StartAnalysisClock();
13122     GetTimeMark(&lastNodeCountTime);
13123     lastNodeCount = 0;
13124 }
13125
13126 void
13127 AnalyzeFileEvent ()
13128 {
13129     if (appData.noChessProgram || gameMode == AnalyzeFile)
13130       return;
13131
13132     if (gameMode != AnalyzeMode) {
13133         EditGameEvent();
13134         if (gameMode != EditGame) return;
13135         ResurrectChessProgram();
13136         SendToProgram("analyze\n", &first);
13137         first.analyzing = TRUE;
13138         /*first.maybeThinking = TRUE;*/
13139         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13140         EngineOutputPopUp();
13141     }
13142     gameMode = AnalyzeFile;
13143     pausing = FALSE;
13144     ModeHighlight();
13145     SetGameInfo();
13146
13147     StartAnalysisClock();
13148     GetTimeMark(&lastNodeCountTime);
13149     lastNodeCount = 0;
13150     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13151 }
13152
13153 void
13154 MachineWhiteEvent ()
13155 {
13156     char buf[MSG_SIZ];
13157     char *bookHit = NULL;
13158
13159     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13160       return;
13161
13162
13163     if (gameMode == PlayFromGameFile ||
13164         gameMode == TwoMachinesPlay  ||
13165         gameMode == Training         ||
13166         gameMode == AnalyzeMode      ||
13167         gameMode == EndOfGame)
13168         EditGameEvent();
13169
13170     if (gameMode == EditPosition)
13171         EditPositionDone(TRUE);
13172
13173     if (!WhiteOnMove(currentMove)) {
13174         DisplayError(_("It is not White's turn"), 0);
13175         return;
13176     }
13177
13178     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13179       ExitAnalyzeMode();
13180
13181     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13182         gameMode == AnalyzeFile)
13183         TruncateGame();
13184
13185     ResurrectChessProgram();    /* in case it isn't running */
13186     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13187         gameMode = MachinePlaysWhite;
13188         ResetClocks();
13189     } else
13190     gameMode = MachinePlaysWhite;
13191     pausing = FALSE;
13192     ModeHighlight();
13193     SetGameInfo();
13194     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13195     DisplayTitle(buf);
13196     if (first.sendName) {
13197       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13198       SendToProgram(buf, &first);
13199     }
13200     if (first.sendTime) {
13201       if (first.useColors) {
13202         SendToProgram("black\n", &first); /*gnu kludge*/
13203       }
13204       SendTimeRemaining(&first, TRUE);
13205     }
13206     if (first.useColors) {
13207       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13208     }
13209     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13210     SetMachineThinkingEnables();
13211     first.maybeThinking = TRUE;
13212     StartClocks();
13213     firstMove = FALSE;
13214
13215     if (appData.autoFlipView && !flipView) {
13216       flipView = !flipView;
13217       DrawPosition(FALSE, NULL);
13218       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13219     }
13220
13221     if(bookHit) { // [HGM] book: simulate book reply
13222         static char bookMove[MSG_SIZ]; // a bit generous?
13223
13224         programStats.nodes = programStats.depth = programStats.time =
13225         programStats.score = programStats.got_only_move = 0;
13226         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13227
13228         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13229         strcat(bookMove, bookHit);
13230         HandleMachineMove(bookMove, &first);
13231     }
13232 }
13233
13234 void
13235 MachineBlackEvent ()
13236 {
13237   char buf[MSG_SIZ];
13238   char *bookHit = NULL;
13239
13240     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13241         return;
13242
13243
13244     if (gameMode == PlayFromGameFile ||
13245         gameMode == TwoMachinesPlay  ||
13246         gameMode == Training         ||
13247         gameMode == AnalyzeMode      ||
13248         gameMode == EndOfGame)
13249         EditGameEvent();
13250
13251     if (gameMode == EditPosition)
13252         EditPositionDone(TRUE);
13253
13254     if (WhiteOnMove(currentMove)) {
13255         DisplayError(_("It is not Black's turn"), 0);
13256         return;
13257     }
13258
13259     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13260       ExitAnalyzeMode();
13261
13262     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13263         gameMode == AnalyzeFile)
13264         TruncateGame();
13265
13266     ResurrectChessProgram();    /* in case it isn't running */
13267     gameMode = MachinePlaysBlack;
13268     pausing = FALSE;
13269     ModeHighlight();
13270     SetGameInfo();
13271     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13272     DisplayTitle(buf);
13273     if (first.sendName) {
13274       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13275       SendToProgram(buf, &first);
13276     }
13277     if (first.sendTime) {
13278       if (first.useColors) {
13279         SendToProgram("white\n", &first); /*gnu kludge*/
13280       }
13281       SendTimeRemaining(&first, FALSE);
13282     }
13283     if (first.useColors) {
13284       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13285     }
13286     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13287     SetMachineThinkingEnables();
13288     first.maybeThinking = TRUE;
13289     StartClocks();
13290
13291     if (appData.autoFlipView && flipView) {
13292       flipView = !flipView;
13293       DrawPosition(FALSE, NULL);
13294       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13295     }
13296     if(bookHit) { // [HGM] book: simulate book reply
13297         static char bookMove[MSG_SIZ]; // a bit generous?
13298
13299         programStats.nodes = programStats.depth = programStats.time =
13300         programStats.score = programStats.got_only_move = 0;
13301         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13302
13303         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13304         strcat(bookMove, bookHit);
13305         HandleMachineMove(bookMove, &first);
13306     }
13307 }
13308
13309
13310 void
13311 DisplayTwoMachinesTitle ()
13312 {
13313     char buf[MSG_SIZ];
13314     if (appData.matchGames > 0) {
13315         if(appData.tourneyFile[0]) {
13316           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13317                    gameInfo.white, _("vs."), gameInfo.black,
13318                    nextGame+1, appData.matchGames+1,
13319                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13320         } else 
13321         if (first.twoMachinesColor[0] == 'w') {
13322           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13323                    gameInfo.white, _("vs."),  gameInfo.black,
13324                    first.matchWins, second.matchWins,
13325                    matchGame - 1 - (first.matchWins + second.matchWins));
13326         } else {
13327           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13328                    gameInfo.white, _("vs."), gameInfo.black,
13329                    second.matchWins, first.matchWins,
13330                    matchGame - 1 - (first.matchWins + second.matchWins));
13331         }
13332     } else {
13333       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13334     }
13335     DisplayTitle(buf);
13336 }
13337
13338 void
13339 SettingsMenuIfReady ()
13340 {
13341   if (second.lastPing != second.lastPong) {
13342     DisplayMessage("", _("Waiting for second chess program"));
13343     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13344     return;
13345   }
13346   ThawUI();
13347   DisplayMessage("", "");
13348   SettingsPopUp(&second);
13349 }
13350
13351 int
13352 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13353 {
13354     char buf[MSG_SIZ];
13355     if (cps->pr == NoProc) {
13356         StartChessProgram(cps);
13357         if (cps->protocolVersion == 1) {
13358           retry();
13359         } else {
13360           /* kludge: allow timeout for initial "feature" command */
13361           FreezeUI();
13362           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13363           DisplayMessage("", buf);
13364           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13365         }
13366         return 1;
13367     }
13368     return 0;
13369 }
13370
13371 void
13372 TwoMachinesEvent P((void))
13373 {
13374     int i;
13375     char buf[MSG_SIZ];
13376     ChessProgramState *onmove;
13377     char *bookHit = NULL;
13378     static int stalling = 0;
13379     TimeMark now;
13380     long wait;
13381
13382     if (appData.noChessProgram) return;
13383
13384     switch (gameMode) {
13385       case TwoMachinesPlay:
13386         return;
13387       case MachinePlaysWhite:
13388       case MachinePlaysBlack:
13389         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13390             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13391             return;
13392         }
13393         /* fall through */
13394       case BeginningOfGame:
13395       case PlayFromGameFile:
13396       case EndOfGame:
13397         EditGameEvent();
13398         if (gameMode != EditGame) return;
13399         break;
13400       case EditPosition:
13401         EditPositionDone(TRUE);
13402         break;
13403       case AnalyzeMode:
13404       case AnalyzeFile:
13405         ExitAnalyzeMode();
13406         break;
13407       case EditGame:
13408       default:
13409         break;
13410     }
13411
13412 //    forwardMostMove = currentMove;
13413     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13414
13415     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13416
13417     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13418     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13419       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13420       return;
13421     }
13422     if(!stalling) {
13423       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13424       SendToProgram("force\n", &second);
13425       stalling = 1;
13426       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13427       return;
13428     }
13429     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13430     if(appData.matchPause>10000 || appData.matchPause<10)
13431                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13432     wait = SubtractTimeMarks(&now, &pauseStart);
13433     if(wait < appData.matchPause) {
13434         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13435         return;
13436     }
13437     // we are now committed to starting the game
13438     stalling = 0;
13439     DisplayMessage("", "");
13440     if (startedFromSetupPosition) {
13441         SendBoard(&second, backwardMostMove);
13442     if (appData.debugMode) {
13443         fprintf(debugFP, "Two Machines\n");
13444     }
13445     }
13446     for (i = backwardMostMove; i < forwardMostMove; i++) {
13447         SendMoveToProgram(i, &second);
13448     }
13449
13450     gameMode = TwoMachinesPlay;
13451     pausing = FALSE;
13452     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13453     SetGameInfo();
13454     DisplayTwoMachinesTitle();
13455     firstMove = TRUE;
13456     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13457         onmove = &first;
13458     } else {
13459         onmove = &second;
13460     }
13461     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13462     SendToProgram(first.computerString, &first);
13463     if (first.sendName) {
13464       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13465       SendToProgram(buf, &first);
13466     }
13467     SendToProgram(second.computerString, &second);
13468     if (second.sendName) {
13469       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13470       SendToProgram(buf, &second);
13471     }
13472
13473     ResetClocks();
13474     if (!first.sendTime || !second.sendTime) {
13475         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13476         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13477     }
13478     if (onmove->sendTime) {
13479       if (onmove->useColors) {
13480         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13481       }
13482       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13483     }
13484     if (onmove->useColors) {
13485       SendToProgram(onmove->twoMachinesColor, onmove);
13486     }
13487     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13488 //    SendToProgram("go\n", onmove);
13489     onmove->maybeThinking = TRUE;
13490     SetMachineThinkingEnables();
13491
13492     StartClocks();
13493
13494     if(bookHit) { // [HGM] book: simulate book reply
13495         static char bookMove[MSG_SIZ]; // a bit generous?
13496
13497         programStats.nodes = programStats.depth = programStats.time =
13498         programStats.score = programStats.got_only_move = 0;
13499         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13500
13501         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13502         strcat(bookMove, bookHit);
13503         savedMessage = bookMove; // args for deferred call
13504         savedState = onmove;
13505         ScheduleDelayedEvent(DeferredBookMove, 1);
13506     }
13507 }
13508
13509 void
13510 TrainingEvent ()
13511 {
13512     if (gameMode == Training) {
13513       SetTrainingModeOff();
13514       gameMode = PlayFromGameFile;
13515       DisplayMessage("", _("Training mode off"));
13516     } else {
13517       gameMode = Training;
13518       animateTraining = appData.animate;
13519
13520       /* make sure we are not already at the end of the game */
13521       if (currentMove < forwardMostMove) {
13522         SetTrainingModeOn();
13523         DisplayMessage("", _("Training mode on"));
13524       } else {
13525         gameMode = PlayFromGameFile;
13526         DisplayError(_("Already at end of game"), 0);
13527       }
13528     }
13529     ModeHighlight();
13530 }
13531
13532 void
13533 IcsClientEvent ()
13534 {
13535     if (!appData.icsActive) return;
13536     switch (gameMode) {
13537       case IcsPlayingWhite:
13538       case IcsPlayingBlack:
13539       case IcsObserving:
13540       case IcsIdle:
13541       case BeginningOfGame:
13542       case IcsExamining:
13543         return;
13544
13545       case EditGame:
13546         break;
13547
13548       case EditPosition:
13549         EditPositionDone(TRUE);
13550         break;
13551
13552       case AnalyzeMode:
13553       case AnalyzeFile:
13554         ExitAnalyzeMode();
13555         break;
13556
13557       default:
13558         EditGameEvent();
13559         break;
13560     }
13561
13562     gameMode = IcsIdle;
13563     ModeHighlight();
13564     return;
13565 }
13566
13567 void
13568 EditGameEvent ()
13569 {
13570     int i;
13571
13572     switch (gameMode) {
13573       case Training:
13574         SetTrainingModeOff();
13575         break;
13576       case MachinePlaysWhite:
13577       case MachinePlaysBlack:
13578       case BeginningOfGame:
13579         SendToProgram("force\n", &first);
13580         SetUserThinkingEnables();
13581         break;
13582       case PlayFromGameFile:
13583         (void) StopLoadGameTimer();
13584         if (gameFileFP != NULL) {
13585             gameFileFP = NULL;
13586         }
13587         break;
13588       case EditPosition:
13589         EditPositionDone(TRUE);
13590         break;
13591       case AnalyzeMode:
13592       case AnalyzeFile:
13593         ExitAnalyzeMode();
13594         SendToProgram("force\n", &first);
13595         break;
13596       case TwoMachinesPlay:
13597         GameEnds(EndOfFile, NULL, GE_PLAYER);
13598         ResurrectChessProgram();
13599         SetUserThinkingEnables();
13600         break;
13601       case EndOfGame:
13602         ResurrectChessProgram();
13603         break;
13604       case IcsPlayingBlack:
13605       case IcsPlayingWhite:
13606         DisplayError(_("Warning: You are still playing a game"), 0);
13607         break;
13608       case IcsObserving:
13609         DisplayError(_("Warning: You are still observing a game"), 0);
13610         break;
13611       case IcsExamining:
13612         DisplayError(_("Warning: You are still examining a game"), 0);
13613         break;
13614       case IcsIdle:
13615         break;
13616       case EditGame:
13617       default:
13618         return;
13619     }
13620
13621     pausing = FALSE;
13622     StopClocks();
13623     first.offeredDraw = second.offeredDraw = 0;
13624
13625     if (gameMode == PlayFromGameFile) {
13626         whiteTimeRemaining = timeRemaining[0][currentMove];
13627         blackTimeRemaining = timeRemaining[1][currentMove];
13628         DisplayTitle("");
13629     }
13630
13631     if (gameMode == MachinePlaysWhite ||
13632         gameMode == MachinePlaysBlack ||
13633         gameMode == TwoMachinesPlay ||
13634         gameMode == EndOfGame) {
13635         i = forwardMostMove;
13636         while (i > currentMove) {
13637             SendToProgram("undo\n", &first);
13638             i--;
13639         }
13640         if(!adjustedClock) {
13641         whiteTimeRemaining = timeRemaining[0][currentMove];
13642         blackTimeRemaining = timeRemaining[1][currentMove];
13643         DisplayBothClocks();
13644         }
13645         if (whiteFlag || blackFlag) {
13646             whiteFlag = blackFlag = 0;
13647         }
13648         DisplayTitle("");
13649     }
13650
13651     gameMode = EditGame;
13652     ModeHighlight();
13653     SetGameInfo();
13654 }
13655
13656
13657 void
13658 EditPositionEvent ()
13659 {
13660     if (gameMode == EditPosition) {
13661         EditGameEvent();
13662         return;
13663     }
13664
13665     EditGameEvent();
13666     if (gameMode != EditGame) return;
13667
13668     gameMode = EditPosition;
13669     ModeHighlight();
13670     SetGameInfo();
13671     if (currentMove > 0)
13672       CopyBoard(boards[0], boards[currentMove]);
13673
13674     blackPlaysFirst = !WhiteOnMove(currentMove);
13675     ResetClocks();
13676     currentMove = forwardMostMove = backwardMostMove = 0;
13677     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13678     DisplayMove(-1);
13679 }
13680
13681 void
13682 ExitAnalyzeMode ()
13683 {
13684     /* [DM] icsEngineAnalyze - possible call from other functions */
13685     if (appData.icsEngineAnalyze) {
13686         appData.icsEngineAnalyze = FALSE;
13687
13688         DisplayMessage("",_("Close ICS engine analyze..."));
13689     }
13690     if (first.analysisSupport && first.analyzing) {
13691       SendToProgram("exit\n", &first);
13692       first.analyzing = FALSE;
13693     }
13694     thinkOutput[0] = NULLCHAR;
13695 }
13696
13697 void
13698 EditPositionDone (Boolean fakeRights)
13699 {
13700     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13701
13702     startedFromSetupPosition = TRUE;
13703     InitChessProgram(&first, FALSE);
13704     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13705       boards[0][EP_STATUS] = EP_NONE;
13706       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13707     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13708         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13709         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13710       } else boards[0][CASTLING][2] = NoRights;
13711     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13712         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13713         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13714       } else boards[0][CASTLING][5] = NoRights;
13715     }
13716     SendToProgram("force\n", &first);
13717     if (blackPlaysFirst) {
13718         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13719         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13720         currentMove = forwardMostMove = backwardMostMove = 1;
13721         CopyBoard(boards[1], boards[0]);
13722     } else {
13723         currentMove = forwardMostMove = backwardMostMove = 0;
13724     }
13725     SendBoard(&first, forwardMostMove);
13726     if (appData.debugMode) {
13727         fprintf(debugFP, "EditPosDone\n");
13728     }
13729     DisplayTitle("");
13730     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13731     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13732     gameMode = EditGame;
13733     ModeHighlight();
13734     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13735     ClearHighlights(); /* [AS] */
13736 }
13737
13738 /* Pause for `ms' milliseconds */
13739 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13740 void
13741 TimeDelay (long ms)
13742 {
13743     TimeMark m1, m2;
13744
13745     GetTimeMark(&m1);
13746     do {
13747         GetTimeMark(&m2);
13748     } while (SubtractTimeMarks(&m2, &m1) < ms);
13749 }
13750
13751 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13752 void
13753 SendMultiLineToICS (char *buf)
13754 {
13755     char temp[MSG_SIZ+1], *p;
13756     int len;
13757
13758     len = strlen(buf);
13759     if (len > MSG_SIZ)
13760       len = MSG_SIZ;
13761
13762     strncpy(temp, buf, len);
13763     temp[len] = 0;
13764
13765     p = temp;
13766     while (*p) {
13767         if (*p == '\n' || *p == '\r')
13768           *p = ' ';
13769         ++p;
13770     }
13771
13772     strcat(temp, "\n");
13773     SendToICS(temp);
13774     SendToPlayer(temp, strlen(temp));
13775 }
13776
13777 void
13778 SetWhiteToPlayEvent ()
13779 {
13780     if (gameMode == EditPosition) {
13781         blackPlaysFirst = FALSE;
13782         DisplayBothClocks();    /* works because currentMove is 0 */
13783     } else if (gameMode == IcsExamining) {
13784         SendToICS(ics_prefix);
13785         SendToICS("tomove white\n");
13786     }
13787 }
13788
13789 void
13790 SetBlackToPlayEvent ()
13791 {
13792     if (gameMode == EditPosition) {
13793         blackPlaysFirst = TRUE;
13794         currentMove = 1;        /* kludge */
13795         DisplayBothClocks();
13796         currentMove = 0;
13797     } else if (gameMode == IcsExamining) {
13798         SendToICS(ics_prefix);
13799         SendToICS("tomove black\n");
13800     }
13801 }
13802
13803 void
13804 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13805 {
13806     char buf[MSG_SIZ];
13807     ChessSquare piece = boards[0][y][x];
13808
13809     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13810
13811     switch (selection) {
13812       case ClearBoard:
13813         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13814             SendToICS(ics_prefix);
13815             SendToICS("bsetup clear\n");
13816         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13817             SendToICS(ics_prefix);
13818             SendToICS("clearboard\n");
13819         } else {
13820             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13821                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13822                 for (y = 0; y < BOARD_HEIGHT; y++) {
13823                     if (gameMode == IcsExamining) {
13824                         if (boards[currentMove][y][x] != EmptySquare) {
13825                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13826                                     AAA + x, ONE + y);
13827                             SendToICS(buf);
13828                         }
13829                     } else {
13830                         boards[0][y][x] = p;
13831                     }
13832                 }
13833             }
13834         }
13835         if (gameMode == EditPosition) {
13836             DrawPosition(FALSE, boards[0]);
13837         }
13838         break;
13839
13840       case WhitePlay:
13841         SetWhiteToPlayEvent();
13842         break;
13843
13844       case BlackPlay:
13845         SetBlackToPlayEvent();
13846         break;
13847
13848       case EmptySquare:
13849         if (gameMode == IcsExamining) {
13850             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13851             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13852             SendToICS(buf);
13853         } else {
13854             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13855                 if(x == BOARD_LEFT-2) {
13856                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13857                     boards[0][y][1] = 0;
13858                 } else
13859                 if(x == BOARD_RGHT+1) {
13860                     if(y >= gameInfo.holdingsSize) break;
13861                     boards[0][y][BOARD_WIDTH-2] = 0;
13862                 } else break;
13863             }
13864             boards[0][y][x] = EmptySquare;
13865             DrawPosition(FALSE, boards[0]);
13866         }
13867         break;
13868
13869       case PromotePiece:
13870         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13871            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13872             selection = (ChessSquare) (PROMOTED piece);
13873         } else if(piece == EmptySquare) selection = WhiteSilver;
13874         else selection = (ChessSquare)((int)piece - 1);
13875         goto defaultlabel;
13876
13877       case DemotePiece:
13878         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13879            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13880             selection = (ChessSquare) (DEMOTED piece);
13881         } else if(piece == EmptySquare) selection = BlackSilver;
13882         else selection = (ChessSquare)((int)piece + 1);
13883         goto defaultlabel;
13884
13885       case WhiteQueen:
13886       case BlackQueen:
13887         if(gameInfo.variant == VariantShatranj ||
13888            gameInfo.variant == VariantXiangqi  ||
13889            gameInfo.variant == VariantCourier  ||
13890            gameInfo.variant == VariantMakruk     )
13891             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13892         goto defaultlabel;
13893
13894       case WhiteKing:
13895       case BlackKing:
13896         if(gameInfo.variant == VariantXiangqi)
13897             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13898         if(gameInfo.variant == VariantKnightmate)
13899             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13900       default:
13901         defaultlabel:
13902         if (gameMode == IcsExamining) {
13903             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13904             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13905                      PieceToChar(selection), AAA + x, ONE + y);
13906             SendToICS(buf);
13907         } else {
13908             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13909                 int n;
13910                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13911                     n = PieceToNumber(selection - BlackPawn);
13912                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13913                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13914                     boards[0][BOARD_HEIGHT-1-n][1]++;
13915                 } else
13916                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13917                     n = PieceToNumber(selection);
13918                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13919                     boards[0][n][BOARD_WIDTH-1] = selection;
13920                     boards[0][n][BOARD_WIDTH-2]++;
13921                 }
13922             } else
13923             boards[0][y][x] = selection;
13924             DrawPosition(TRUE, boards[0]);
13925             ClearHighlights();
13926             fromX = fromY = -1;
13927         }
13928         break;
13929     }
13930 }
13931
13932
13933 void
13934 DropMenuEvent (ChessSquare selection, int x, int y)
13935 {
13936     ChessMove moveType;
13937
13938     switch (gameMode) {
13939       case IcsPlayingWhite:
13940       case MachinePlaysBlack:
13941         if (!WhiteOnMove(currentMove)) {
13942             DisplayMoveError(_("It is Black's turn"));
13943             return;
13944         }
13945         moveType = WhiteDrop;
13946         break;
13947       case IcsPlayingBlack:
13948       case MachinePlaysWhite:
13949         if (WhiteOnMove(currentMove)) {
13950             DisplayMoveError(_("It is White's turn"));
13951             return;
13952         }
13953         moveType = BlackDrop;
13954         break;
13955       case EditGame:
13956         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13957         break;
13958       default:
13959         return;
13960     }
13961
13962     if (moveType == BlackDrop && selection < BlackPawn) {
13963       selection = (ChessSquare) ((int) selection
13964                                  + (int) BlackPawn - (int) WhitePawn);
13965     }
13966     if (boards[currentMove][y][x] != EmptySquare) {
13967         DisplayMoveError(_("That square is occupied"));
13968         return;
13969     }
13970
13971     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13972 }
13973
13974 void
13975 AcceptEvent ()
13976 {
13977     /* Accept a pending offer of any kind from opponent */
13978
13979     if (appData.icsActive) {
13980         SendToICS(ics_prefix);
13981         SendToICS("accept\n");
13982     } else if (cmailMsgLoaded) {
13983         if (currentMove == cmailOldMove &&
13984             commentList[cmailOldMove] != NULL &&
13985             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13986                    "Black offers a draw" : "White offers a draw")) {
13987             TruncateGame();
13988             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13990         } else {
13991             DisplayError(_("There is no pending offer on this move"), 0);
13992             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13993         }
13994     } else {
13995         /* Not used for offers from chess program */
13996     }
13997 }
13998
13999 void
14000 DeclineEvent ()
14001 {
14002     /* Decline a pending offer of any kind from opponent */
14003
14004     if (appData.icsActive) {
14005         SendToICS(ics_prefix);
14006         SendToICS("decline\n");
14007     } else if (cmailMsgLoaded) {
14008         if (currentMove == cmailOldMove &&
14009             commentList[cmailOldMove] != NULL &&
14010             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14011                    "Black offers a draw" : "White offers a draw")) {
14012 #ifdef NOTDEF
14013             AppendComment(cmailOldMove, "Draw declined", TRUE);
14014             DisplayComment(cmailOldMove - 1, "Draw declined");
14015 #endif /*NOTDEF*/
14016         } else {
14017             DisplayError(_("There is no pending offer on this move"), 0);
14018         }
14019     } else {
14020         /* Not used for offers from chess program */
14021     }
14022 }
14023
14024 void
14025 RematchEvent ()
14026 {
14027     /* Issue ICS rematch command */
14028     if (appData.icsActive) {
14029         SendToICS(ics_prefix);
14030         SendToICS("rematch\n");
14031     }
14032 }
14033
14034 void
14035 CallFlagEvent ()
14036 {
14037     /* Call your opponent's flag (claim a win on time) */
14038     if (appData.icsActive) {
14039         SendToICS(ics_prefix);
14040         SendToICS("flag\n");
14041     } else {
14042         switch (gameMode) {
14043           default:
14044             return;
14045           case MachinePlaysWhite:
14046             if (whiteFlag) {
14047                 if (blackFlag)
14048                   GameEnds(GameIsDrawn, "Both players ran out of time",
14049                            GE_PLAYER);
14050                 else
14051                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14052             } else {
14053                 DisplayError(_("Your opponent is not out of time"), 0);
14054             }
14055             break;
14056           case MachinePlaysBlack:
14057             if (blackFlag) {
14058                 if (whiteFlag)
14059                   GameEnds(GameIsDrawn, "Both players ran out of time",
14060                            GE_PLAYER);
14061                 else
14062                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14063             } else {
14064                 DisplayError(_("Your opponent is not out of time"), 0);
14065             }
14066             break;
14067         }
14068     }
14069 }
14070
14071 void
14072 ClockClick (int which)
14073 {       // [HGM] code moved to back-end from winboard.c
14074         if(which) { // black clock
14075           if (gameMode == EditPosition || gameMode == IcsExamining) {
14076             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14077             SetBlackToPlayEvent();
14078           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14079           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14080           } else if (shiftKey) {
14081             AdjustClock(which, -1);
14082           } else if (gameMode == IcsPlayingWhite ||
14083                      gameMode == MachinePlaysBlack) {
14084             CallFlagEvent();
14085           }
14086         } else { // white clock
14087           if (gameMode == EditPosition || gameMode == IcsExamining) {
14088             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14089             SetWhiteToPlayEvent();
14090           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14091           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14092           } else if (shiftKey) {
14093             AdjustClock(which, -1);
14094           } else if (gameMode == IcsPlayingBlack ||
14095                    gameMode == MachinePlaysWhite) {
14096             CallFlagEvent();
14097           }
14098         }
14099 }
14100
14101 void
14102 DrawEvent ()
14103 {
14104     /* Offer draw or accept pending draw offer from opponent */
14105
14106     if (appData.icsActive) {
14107         /* Note: tournament rules require draw offers to be
14108            made after you make your move but before you punch
14109            your clock.  Currently ICS doesn't let you do that;
14110            instead, you immediately punch your clock after making
14111            a move, but you can offer a draw at any time. */
14112
14113         SendToICS(ics_prefix);
14114         SendToICS("draw\n");
14115         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14116     } else if (cmailMsgLoaded) {
14117         if (currentMove == cmailOldMove &&
14118             commentList[cmailOldMove] != NULL &&
14119             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14120                    "Black offers a draw" : "White offers a draw")) {
14121             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14123         } else if (currentMove == cmailOldMove + 1) {
14124             char *offer = WhiteOnMove(cmailOldMove) ?
14125               "White offers a draw" : "Black offers a draw";
14126             AppendComment(currentMove, offer, TRUE);
14127             DisplayComment(currentMove - 1, offer);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14129         } else {
14130             DisplayError(_("You must make your move before offering a draw"), 0);
14131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14132         }
14133     } else if (first.offeredDraw) {
14134         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14135     } else {
14136         if (first.sendDrawOffers) {
14137             SendToProgram("draw\n", &first);
14138             userOfferedDraw = TRUE;
14139         }
14140     }
14141 }
14142
14143 void
14144 AdjournEvent ()
14145 {
14146     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14147
14148     if (appData.icsActive) {
14149         SendToICS(ics_prefix);
14150         SendToICS("adjourn\n");
14151     } else {
14152         /* Currently GNU Chess doesn't offer or accept Adjourns */
14153     }
14154 }
14155
14156
14157 void
14158 AbortEvent ()
14159 {
14160     /* Offer Abort or accept pending Abort offer from opponent */
14161
14162     if (appData.icsActive) {
14163         SendToICS(ics_prefix);
14164         SendToICS("abort\n");
14165     } else {
14166         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14167     }
14168 }
14169
14170 void
14171 ResignEvent ()
14172 {
14173     /* Resign.  You can do this even if it's not your turn. */
14174
14175     if (appData.icsActive) {
14176         SendToICS(ics_prefix);
14177         SendToICS("resign\n");
14178     } else {
14179         switch (gameMode) {
14180           case MachinePlaysWhite:
14181             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14182             break;
14183           case MachinePlaysBlack:
14184             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14185             break;
14186           case EditGame:
14187             if (cmailMsgLoaded) {
14188                 TruncateGame();
14189                 if (WhiteOnMove(cmailOldMove)) {
14190                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14191                 } else {
14192                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14193                 }
14194                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14195             }
14196             break;
14197           default:
14198             break;
14199         }
14200     }
14201 }
14202
14203
14204 void
14205 StopObservingEvent ()
14206 {
14207     /* Stop observing current games */
14208     SendToICS(ics_prefix);
14209     SendToICS("unobserve\n");
14210 }
14211
14212 void
14213 StopExaminingEvent ()
14214 {
14215     /* Stop observing current game */
14216     SendToICS(ics_prefix);
14217     SendToICS("unexamine\n");
14218 }
14219
14220 void
14221 ForwardInner (int target)
14222 {
14223     int limit; int oldSeekGraphUp = seekGraphUp;
14224
14225     if (appData.debugMode)
14226         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14227                 target, currentMove, forwardMostMove);
14228
14229     if (gameMode == EditPosition)
14230       return;
14231
14232     seekGraphUp = FALSE;
14233     MarkTargetSquares(1);
14234
14235     if (gameMode == PlayFromGameFile && !pausing)
14236       PauseEvent();
14237
14238     if (gameMode == IcsExamining && pausing)
14239       limit = pauseExamForwardMostMove;
14240     else
14241       limit = forwardMostMove;
14242
14243     if (target > limit) target = limit;
14244
14245     if (target > 0 && moveList[target - 1][0]) {
14246         int fromX, fromY, toX, toY;
14247         toX = moveList[target - 1][2] - AAA;
14248         toY = moveList[target - 1][3] - ONE;
14249         if (moveList[target - 1][1] == '@') {
14250             if (appData.highlightLastMove) {
14251                 SetHighlights(-1, -1, toX, toY);
14252             }
14253         } else {
14254             fromX = moveList[target - 1][0] - AAA;
14255             fromY = moveList[target - 1][1] - ONE;
14256             if (target == currentMove + 1) {
14257                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14258             }
14259             if (appData.highlightLastMove) {
14260                 SetHighlights(fromX, fromY, toX, toY);
14261             }
14262         }
14263     }
14264     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14265         gameMode == Training || gameMode == PlayFromGameFile ||
14266         gameMode == AnalyzeFile) {
14267         while (currentMove < target) {
14268             SendMoveToProgram(currentMove++, &first);
14269         }
14270     } else {
14271         currentMove = target;
14272     }
14273
14274     if (gameMode == EditGame || gameMode == EndOfGame) {
14275         whiteTimeRemaining = timeRemaining[0][currentMove];
14276         blackTimeRemaining = timeRemaining[1][currentMove];
14277     }
14278     DisplayBothClocks();
14279     DisplayMove(currentMove - 1);
14280     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14281     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14282     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14283         DisplayComment(currentMove - 1, commentList[currentMove]);
14284     }
14285 }
14286
14287
14288 void
14289 ForwardEvent ()
14290 {
14291     if (gameMode == IcsExamining && !pausing) {
14292         SendToICS(ics_prefix);
14293         SendToICS("forward\n");
14294     } else {
14295         ForwardInner(currentMove + 1);
14296     }
14297 }
14298
14299 void
14300 ToEndEvent ()
14301 {
14302     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14303         /* to optimze, we temporarily turn off analysis mode while we feed
14304          * the remaining moves to the engine. Otherwise we get analysis output
14305          * after each move.
14306          */
14307         if (first.analysisSupport) {
14308           SendToProgram("exit\nforce\n", &first);
14309           first.analyzing = FALSE;
14310         }
14311     }
14312
14313     if (gameMode == IcsExamining && !pausing) {
14314         SendToICS(ics_prefix);
14315         SendToICS("forward 999999\n");
14316     } else {
14317         ForwardInner(forwardMostMove);
14318     }
14319
14320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14321         /* we have fed all the moves, so reactivate analysis mode */
14322         SendToProgram("analyze\n", &first);
14323         first.analyzing = TRUE;
14324         /*first.maybeThinking = TRUE;*/
14325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14326     }
14327 }
14328
14329 void
14330 BackwardInner (int target)
14331 {
14332     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14333
14334     if (appData.debugMode)
14335         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14336                 target, currentMove, forwardMostMove);
14337
14338     if (gameMode == EditPosition) return;
14339     seekGraphUp = FALSE;
14340     MarkTargetSquares(1);
14341     if (currentMove <= backwardMostMove) {
14342         ClearHighlights();
14343         DrawPosition(full_redraw, boards[currentMove]);
14344         return;
14345     }
14346     if (gameMode == PlayFromGameFile && !pausing)
14347       PauseEvent();
14348
14349     if (moveList[target][0]) {
14350         int fromX, fromY, toX, toY;
14351         toX = moveList[target][2] - AAA;
14352         toY = moveList[target][3] - ONE;
14353         if (moveList[target][1] == '@') {
14354             if (appData.highlightLastMove) {
14355                 SetHighlights(-1, -1, toX, toY);
14356             }
14357         } else {
14358             fromX = moveList[target][0] - AAA;
14359             fromY = moveList[target][1] - ONE;
14360             if (target == currentMove - 1) {
14361                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14362             }
14363             if (appData.highlightLastMove) {
14364                 SetHighlights(fromX, fromY, toX, toY);
14365             }
14366         }
14367     }
14368     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14369         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14370         while (currentMove > target) {
14371             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14372                 // null move cannot be undone. Reload program with move history before it.
14373                 int i;
14374                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14375                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14376                 }
14377                 SendBoard(&first, i); 
14378                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14379                 break;
14380             }
14381             SendToProgram("undo\n", &first);
14382             currentMove--;
14383         }
14384     } else {
14385         currentMove = target;
14386     }
14387
14388     if (gameMode == EditGame || gameMode == EndOfGame) {
14389         whiteTimeRemaining = timeRemaining[0][currentMove];
14390         blackTimeRemaining = timeRemaining[1][currentMove];
14391     }
14392     DisplayBothClocks();
14393     DisplayMove(currentMove - 1);
14394     DrawPosition(full_redraw, boards[currentMove]);
14395     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14396     // [HGM] PV info: routine tests if comment empty
14397     DisplayComment(currentMove - 1, commentList[currentMove]);
14398 }
14399
14400 void
14401 BackwardEvent ()
14402 {
14403     if (gameMode == IcsExamining && !pausing) {
14404         SendToICS(ics_prefix);
14405         SendToICS("backward\n");
14406     } else {
14407         BackwardInner(currentMove - 1);
14408     }
14409 }
14410
14411 void
14412 ToStartEvent ()
14413 {
14414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14415         /* to optimize, we temporarily turn off analysis mode while we undo
14416          * all the moves. Otherwise we get analysis output after each undo.
14417          */
14418         if (first.analysisSupport) {
14419           SendToProgram("exit\nforce\n", &first);
14420           first.analyzing = FALSE;
14421         }
14422     }
14423
14424     if (gameMode == IcsExamining && !pausing) {
14425         SendToICS(ics_prefix);
14426         SendToICS("backward 999999\n");
14427     } else {
14428         BackwardInner(backwardMostMove);
14429     }
14430
14431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14432         /* we have fed all the moves, so reactivate analysis mode */
14433         SendToProgram("analyze\n", &first);
14434         first.analyzing = TRUE;
14435         /*first.maybeThinking = TRUE;*/
14436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14437     }
14438 }
14439
14440 void
14441 ToNrEvent (int to)
14442 {
14443   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14444   if (to >= forwardMostMove) to = forwardMostMove;
14445   if (to <= backwardMostMove) to = backwardMostMove;
14446   if (to < currentMove) {
14447     BackwardInner(to);
14448   } else {
14449     ForwardInner(to);
14450   }
14451 }
14452
14453 void
14454 RevertEvent (Boolean annotate)
14455 {
14456     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14457         return;
14458     }
14459     if (gameMode != IcsExamining) {
14460         DisplayError(_("You are not examining a game"), 0);
14461         return;
14462     }
14463     if (pausing) {
14464         DisplayError(_("You can't revert while pausing"), 0);
14465         return;
14466     }
14467     SendToICS(ics_prefix);
14468     SendToICS("revert\n");
14469 }
14470
14471 void
14472 RetractMoveEvent ()
14473 {
14474     switch (gameMode) {
14475       case MachinePlaysWhite:
14476       case MachinePlaysBlack:
14477         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14478             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14479             return;
14480         }
14481         if (forwardMostMove < 2) return;
14482         currentMove = forwardMostMove = forwardMostMove - 2;
14483         whiteTimeRemaining = timeRemaining[0][currentMove];
14484         blackTimeRemaining = timeRemaining[1][currentMove];
14485         DisplayBothClocks();
14486         DisplayMove(currentMove - 1);
14487         ClearHighlights();/*!! could figure this out*/
14488         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14489         SendToProgram("remove\n", &first);
14490         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14491         break;
14492
14493       case BeginningOfGame:
14494       default:
14495         break;
14496
14497       case IcsPlayingWhite:
14498       case IcsPlayingBlack:
14499         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14500             SendToICS(ics_prefix);
14501             SendToICS("takeback 2\n");
14502         } else {
14503             SendToICS(ics_prefix);
14504             SendToICS("takeback 1\n");
14505         }
14506         break;
14507     }
14508 }
14509
14510 void
14511 MoveNowEvent ()
14512 {
14513     ChessProgramState *cps;
14514
14515     switch (gameMode) {
14516       case MachinePlaysWhite:
14517         if (!WhiteOnMove(forwardMostMove)) {
14518             DisplayError(_("It is your turn"), 0);
14519             return;
14520         }
14521         cps = &first;
14522         break;
14523       case MachinePlaysBlack:
14524         if (WhiteOnMove(forwardMostMove)) {
14525             DisplayError(_("It is your turn"), 0);
14526             return;
14527         }
14528         cps = &first;
14529         break;
14530       case TwoMachinesPlay:
14531         if (WhiteOnMove(forwardMostMove) ==
14532             (first.twoMachinesColor[0] == 'w')) {
14533             cps = &first;
14534         } else {
14535             cps = &second;
14536         }
14537         break;
14538       case BeginningOfGame:
14539       default:
14540         return;
14541     }
14542     SendToProgram("?\n", cps);
14543 }
14544
14545 void
14546 TruncateGameEvent ()
14547 {
14548     EditGameEvent();
14549     if (gameMode != EditGame) return;
14550     TruncateGame();
14551 }
14552
14553 void
14554 TruncateGame ()
14555 {
14556     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14557     if (forwardMostMove > currentMove) {
14558         if (gameInfo.resultDetails != NULL) {
14559             free(gameInfo.resultDetails);
14560             gameInfo.resultDetails = NULL;
14561             gameInfo.result = GameUnfinished;
14562         }
14563         forwardMostMove = currentMove;
14564         HistorySet(parseList, backwardMostMove, forwardMostMove,
14565                    currentMove-1);
14566     }
14567 }
14568
14569 void
14570 HintEvent ()
14571 {
14572     if (appData.noChessProgram) return;
14573     switch (gameMode) {
14574       case MachinePlaysWhite:
14575         if (WhiteOnMove(forwardMostMove)) {
14576             DisplayError(_("Wait until your turn"), 0);
14577             return;
14578         }
14579         break;
14580       case BeginningOfGame:
14581       case MachinePlaysBlack:
14582         if (!WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       default:
14588         DisplayError(_("No hint available"), 0);
14589         return;
14590     }
14591     SendToProgram("hint\n", &first);
14592     hintRequested = TRUE;
14593 }
14594
14595 void
14596 BookEvent ()
14597 {
14598     if (appData.noChessProgram) return;
14599     switch (gameMode) {
14600       case MachinePlaysWhite:
14601         if (WhiteOnMove(forwardMostMove)) {
14602             DisplayError(_("Wait until your turn"), 0);
14603             return;
14604         }
14605         break;
14606       case BeginningOfGame:
14607       case MachinePlaysBlack:
14608         if (!WhiteOnMove(forwardMostMove)) {
14609             DisplayError(_("Wait until your turn"), 0);
14610             return;
14611         }
14612         break;
14613       case EditPosition:
14614         EditPositionDone(TRUE);
14615         break;
14616       case TwoMachinesPlay:
14617         return;
14618       default:
14619         break;
14620     }
14621     SendToProgram("bk\n", &first);
14622     bookOutput[0] = NULLCHAR;
14623     bookRequested = TRUE;
14624 }
14625
14626 void
14627 AboutGameEvent ()
14628 {
14629     char *tags = PGNTags(&gameInfo);
14630     TagsPopUp(tags, CmailMsg());
14631     free(tags);
14632 }
14633
14634 /* end button procedures */
14635
14636 void
14637 PrintPosition (FILE *fp, int move)
14638 {
14639     int i, j;
14640
14641     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14642         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14643             char c = PieceToChar(boards[move][i][j]);
14644             fputc(c == 'x' ? '.' : c, fp);
14645             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14646         }
14647     }
14648     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14649       fprintf(fp, "white to play\n");
14650     else
14651       fprintf(fp, "black to play\n");
14652 }
14653
14654 void
14655 PrintOpponents (FILE *fp)
14656 {
14657     if (gameInfo.white != NULL) {
14658         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14659     } else {
14660         fprintf(fp, "\n");
14661     }
14662 }
14663
14664 /* Find last component of program's own name, using some heuristics */
14665 void
14666 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14667 {
14668     char *p, *q, c;
14669     int local = (strcmp(host, "localhost") == 0);
14670     while (!local && (p = strchr(prog, ';')) != NULL) {
14671         p++;
14672         while (*p == ' ') p++;
14673         prog = p;
14674     }
14675     if (*prog == '"' || *prog == '\'') {
14676         q = strchr(prog + 1, *prog);
14677     } else {
14678         q = strchr(prog, ' ');
14679     }
14680     if (q == NULL) q = prog + strlen(prog);
14681     p = q;
14682     while (p >= prog && *p != '/' && *p != '\\') p--;
14683     p++;
14684     if(p == prog && *p == '"') p++;
14685     c = *q; *q = 0;
14686     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14687     memcpy(buf, p, q - p);
14688     buf[q - p] = NULLCHAR;
14689     if (!local) {
14690         strcat(buf, "@");
14691         strcat(buf, host);
14692     }
14693 }
14694
14695 char *
14696 TimeControlTagValue ()
14697 {
14698     char buf[MSG_SIZ];
14699     if (!appData.clockMode) {
14700       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14701     } else if (movesPerSession > 0) {
14702       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14703     } else if (timeIncrement == 0) {
14704       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14705     } else {
14706       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14707     }
14708     return StrSave(buf);
14709 }
14710
14711 void
14712 SetGameInfo ()
14713 {
14714     /* This routine is used only for certain modes */
14715     VariantClass v = gameInfo.variant;
14716     ChessMove r = GameUnfinished;
14717     char *p = NULL;
14718
14719     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14720         r = gameInfo.result;
14721         p = gameInfo.resultDetails;
14722         gameInfo.resultDetails = NULL;
14723     }
14724     ClearGameInfo(&gameInfo);
14725     gameInfo.variant = v;
14726
14727     switch (gameMode) {
14728       case MachinePlaysWhite:
14729         gameInfo.event = StrSave( appData.pgnEventHeader );
14730         gameInfo.site = StrSave(HostName());
14731         gameInfo.date = PGNDate();
14732         gameInfo.round = StrSave("-");
14733         gameInfo.white = StrSave(first.tidy);
14734         gameInfo.black = StrSave(UserName());
14735         gameInfo.timeControl = TimeControlTagValue();
14736         break;
14737
14738       case MachinePlaysBlack:
14739         gameInfo.event = StrSave( appData.pgnEventHeader );
14740         gameInfo.site = StrSave(HostName());
14741         gameInfo.date = PGNDate();
14742         gameInfo.round = StrSave("-");
14743         gameInfo.white = StrSave(UserName());
14744         gameInfo.black = StrSave(first.tidy);
14745         gameInfo.timeControl = TimeControlTagValue();
14746         break;
14747
14748       case TwoMachinesPlay:
14749         gameInfo.event = StrSave( appData.pgnEventHeader );
14750         gameInfo.site = StrSave(HostName());
14751         gameInfo.date = PGNDate();
14752         if (roundNr > 0) {
14753             char buf[MSG_SIZ];
14754             snprintf(buf, MSG_SIZ, "%d", roundNr);
14755             gameInfo.round = StrSave(buf);
14756         } else {
14757             gameInfo.round = StrSave("-");
14758         }
14759         if (first.twoMachinesColor[0] == 'w') {
14760             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14761             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14762         } else {
14763             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14764             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14765         }
14766         gameInfo.timeControl = TimeControlTagValue();
14767         break;
14768
14769       case EditGame:
14770         gameInfo.event = StrSave("Edited game");
14771         gameInfo.site = StrSave(HostName());
14772         gameInfo.date = PGNDate();
14773         gameInfo.round = StrSave("-");
14774         gameInfo.white = StrSave("-");
14775         gameInfo.black = StrSave("-");
14776         gameInfo.result = r;
14777         gameInfo.resultDetails = p;
14778         break;
14779
14780       case EditPosition:
14781         gameInfo.event = StrSave("Edited position");
14782         gameInfo.site = StrSave(HostName());
14783         gameInfo.date = PGNDate();
14784         gameInfo.round = StrSave("-");
14785         gameInfo.white = StrSave("-");
14786         gameInfo.black = StrSave("-");
14787         break;
14788
14789       case IcsPlayingWhite:
14790       case IcsPlayingBlack:
14791       case IcsObserving:
14792       case IcsExamining:
14793         break;
14794
14795       case PlayFromGameFile:
14796         gameInfo.event = StrSave("Game from non-PGN file");
14797         gameInfo.site = StrSave(HostName());
14798         gameInfo.date = PGNDate();
14799         gameInfo.round = StrSave("-");
14800         gameInfo.white = StrSave("?");
14801         gameInfo.black = StrSave("?");
14802         break;
14803
14804       default:
14805         break;
14806     }
14807 }
14808
14809 void
14810 ReplaceComment (int index, char *text)
14811 {
14812     int len;
14813     char *p;
14814     float score;
14815
14816     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14817        pvInfoList[index-1].depth == len &&
14818        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14819        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14820     while (*text == '\n') text++;
14821     len = strlen(text);
14822     while (len > 0 && text[len - 1] == '\n') len--;
14823
14824     if (commentList[index] != NULL)
14825       free(commentList[index]);
14826
14827     if (len == 0) {
14828         commentList[index] = NULL;
14829         return;
14830     }
14831   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14832       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14833       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14834     commentList[index] = (char *) malloc(len + 2);
14835     strncpy(commentList[index], text, len);
14836     commentList[index][len] = '\n';
14837     commentList[index][len + 1] = NULLCHAR;
14838   } else {
14839     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14840     char *p;
14841     commentList[index] = (char *) malloc(len + 7);
14842     safeStrCpy(commentList[index], "{\n", 3);
14843     safeStrCpy(commentList[index]+2, text, len+1);
14844     commentList[index][len+2] = NULLCHAR;
14845     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14846     strcat(commentList[index], "\n}\n");
14847   }
14848 }
14849
14850 void
14851 CrushCRs (char *text)
14852 {
14853   char *p = text;
14854   char *q = text;
14855   char ch;
14856
14857   do {
14858     ch = *p++;
14859     if (ch == '\r') continue;
14860     *q++ = ch;
14861   } while (ch != '\0');
14862 }
14863
14864 void
14865 AppendComment (int index, char *text, Boolean addBraces)
14866 /* addBraces  tells if we should add {} */
14867 {
14868     int oldlen, len;
14869     char *old;
14870
14871 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14872     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14873
14874     CrushCRs(text);
14875     while (*text == '\n') text++;
14876     len = strlen(text);
14877     while (len > 0 && text[len - 1] == '\n') len--;
14878     text[len] = NULLCHAR;
14879
14880     if (len == 0) return;
14881
14882     if (commentList[index] != NULL) {
14883       Boolean addClosingBrace = addBraces;
14884         old = commentList[index];
14885         oldlen = strlen(old);
14886         while(commentList[index][oldlen-1] ==  '\n')
14887           commentList[index][--oldlen] = NULLCHAR;
14888         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14889         safeStrCpy(commentList[index], old, oldlen + len + 6);
14890         free(old);
14891         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14892         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14893           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14894           while (*text == '\n') { text++; len--; }
14895           commentList[index][--oldlen] = NULLCHAR;
14896       }
14897         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14898         else          strcat(commentList[index], "\n");
14899         strcat(commentList[index], text);
14900         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14901         else          strcat(commentList[index], "\n");
14902     } else {
14903         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14904         if(addBraces)
14905           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14906         else commentList[index][0] = NULLCHAR;
14907         strcat(commentList[index], text);
14908         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14909         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14910     }
14911 }
14912
14913 static char *
14914 FindStr (char * text, char * sub_text)
14915 {
14916     char * result = strstr( text, sub_text );
14917
14918     if( result != NULL ) {
14919         result += strlen( sub_text );
14920     }
14921
14922     return result;
14923 }
14924
14925 /* [AS] Try to extract PV info from PGN comment */
14926 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14927 char *
14928 GetInfoFromComment (int index, char * text)
14929 {
14930     char * sep = text, *p;
14931
14932     if( text != NULL && index > 0 ) {
14933         int score = 0;
14934         int depth = 0;
14935         int time = -1, sec = 0, deci;
14936         char * s_eval = FindStr( text, "[%eval " );
14937         char * s_emt = FindStr( text, "[%emt " );
14938
14939         if( s_eval != NULL || s_emt != NULL ) {
14940             /* New style */
14941             char delim;
14942
14943             if( s_eval != NULL ) {
14944                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14945                     return text;
14946                 }
14947
14948                 if( delim != ']' ) {
14949                     return text;
14950                 }
14951             }
14952
14953             if( s_emt != NULL ) {
14954             }
14955                 return text;
14956         }
14957         else {
14958             /* We expect something like: [+|-]nnn.nn/dd */
14959             int score_lo = 0;
14960
14961             if(*text != '{') return text; // [HGM] braces: must be normal comment
14962
14963             sep = strchr( text, '/' );
14964             if( sep == NULL || sep < (text+4) ) {
14965                 return text;
14966             }
14967
14968             p = text;
14969             if(p[1] == '(') { // comment starts with PV
14970                p = strchr(p, ')'); // locate end of PV
14971                if(p == NULL || sep < p+5) return text;
14972                // at this point we have something like "{(.*) +0.23/6 ..."
14973                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14974                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14975                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14976             }
14977             time = -1; sec = -1; deci = -1;
14978             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14979                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14980                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14981                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14982                 return text;
14983             }
14984
14985             if( score_lo < 0 || score_lo >= 100 ) {
14986                 return text;
14987             }
14988
14989             if(sec >= 0) time = 600*time + 10*sec; else
14990             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14991
14992             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14993
14994             /* [HGM] PV time: now locate end of PV info */
14995             while( *++sep >= '0' && *sep <= '9'); // strip depth
14996             if(time >= 0)
14997             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14998             if(sec >= 0)
14999             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15000             if(deci >= 0)
15001             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15002             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15003         }
15004
15005         if( depth <= 0 ) {
15006             return text;
15007         }
15008
15009         if( time < 0 ) {
15010             time = -1;
15011         }
15012
15013         pvInfoList[index-1].depth = depth;
15014         pvInfoList[index-1].score = score;
15015         pvInfoList[index-1].time  = 10*time; // centi-sec
15016         if(*sep == '}') *sep = 0; else *--sep = '{';
15017         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15018     }
15019     return sep;
15020 }
15021
15022 void
15023 SendToProgram (char *message, ChessProgramState *cps)
15024 {
15025     int count, outCount, error;
15026     char buf[MSG_SIZ];
15027
15028     if (cps->pr == NoProc) return;
15029     Attention(cps);
15030
15031     if (appData.debugMode) {
15032         TimeMark now;
15033         GetTimeMark(&now);
15034         fprintf(debugFP, "%ld >%-6s: %s",
15035                 SubtractTimeMarks(&now, &programStartTime),
15036                 cps->which, message);
15037         if(serverFP)
15038             fprintf(serverFP, "%ld >%-6s: %s",
15039                 SubtractTimeMarks(&now, &programStartTime),
15040                 cps->which, message), fflush(serverFP);
15041     }
15042
15043     count = strlen(message);
15044     outCount = OutputToProcess(cps->pr, message, count, &error);
15045     if (outCount < count && !exiting
15046                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15047       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15048       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15049         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15050             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15051                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15052                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15053                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15054             } else {
15055                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15056                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15057                 gameInfo.result = res;
15058             }
15059             gameInfo.resultDetails = StrSave(buf);
15060         }
15061         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15062         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15063     }
15064 }
15065
15066 void
15067 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15068 {
15069     char *end_str;
15070     char buf[MSG_SIZ];
15071     ChessProgramState *cps = (ChessProgramState *)closure;
15072
15073     if (isr != cps->isr) return; /* Killed intentionally */
15074     if (count <= 0) {
15075         if (count == 0) {
15076             RemoveInputSource(cps->isr);
15077             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15078             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15079                     _(cps->which), cps->program);
15080         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15081                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15082                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15083                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15084                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15085                 } else {
15086                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15087                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15088                     gameInfo.result = res;
15089                 }
15090                 gameInfo.resultDetails = StrSave(buf);
15091             }
15092             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15093             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15094         } else {
15095             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15096                     _(cps->which), cps->program);
15097             RemoveInputSource(cps->isr);
15098
15099             /* [AS] Program is misbehaving badly... kill it */
15100             if( count == -2 ) {
15101                 DestroyChildProcess( cps->pr, 9 );
15102                 cps->pr = NoProc;
15103             }
15104
15105             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15106         }
15107         return;
15108     }
15109
15110     if ((end_str = strchr(message, '\r')) != NULL)
15111       *end_str = NULLCHAR;
15112     if ((end_str = strchr(message, '\n')) != NULL)
15113       *end_str = NULLCHAR;
15114
15115     if (appData.debugMode) {
15116         TimeMark now; int print = 1;
15117         char *quote = ""; char c; int i;
15118
15119         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15120                 char start = message[0];
15121                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15122                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15123                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15124                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15125                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15126                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15127                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15128                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15129                    sscanf(message, "hint: %c", &c)!=1 && 
15130                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15131                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15132                     print = (appData.engineComments >= 2);
15133                 }
15134                 message[0] = start; // restore original message
15135         }
15136         if(print) {
15137                 GetTimeMark(&now);
15138                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15139                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15140                         quote,
15141                         message);
15142                 if(serverFP)
15143                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15144                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15145                         quote,
15146                         message), fflush(serverFP);
15147         }
15148     }
15149
15150     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15151     if (appData.icsEngineAnalyze) {
15152         if (strstr(message, "whisper") != NULL ||
15153              strstr(message, "kibitz") != NULL ||
15154             strstr(message, "tellics") != NULL) return;
15155     }
15156
15157     HandleMachineMove(message, cps);
15158 }
15159
15160
15161 void
15162 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15163 {
15164     char buf[MSG_SIZ];
15165     int seconds;
15166
15167     if( timeControl_2 > 0 ) {
15168         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15169             tc = timeControl_2;
15170         }
15171     }
15172     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15173     inc /= cps->timeOdds;
15174     st  /= cps->timeOdds;
15175
15176     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15177
15178     if (st > 0) {
15179       /* Set exact time per move, normally using st command */
15180       if (cps->stKludge) {
15181         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15182         seconds = st % 60;
15183         if (seconds == 0) {
15184           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15185         } else {
15186           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15187         }
15188       } else {
15189         snprintf(buf, MSG_SIZ, "st %d\n", st);
15190       }
15191     } else {
15192       /* Set conventional or incremental time control, using level command */
15193       if (seconds == 0) {
15194         /* Note old gnuchess bug -- minutes:seconds used to not work.
15195            Fixed in later versions, but still avoid :seconds
15196            when seconds is 0. */
15197         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15198       } else {
15199         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15200                  seconds, inc/1000.);
15201       }
15202     }
15203     SendToProgram(buf, cps);
15204
15205     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15206     /* Orthogonally, limit search to given depth */
15207     if (sd > 0) {
15208       if (cps->sdKludge) {
15209         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15210       } else {
15211         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15212       }
15213       SendToProgram(buf, cps);
15214     }
15215
15216     if(cps->nps >= 0) { /* [HGM] nps */
15217         if(cps->supportsNPS == FALSE)
15218           cps->nps = -1; // don't use if engine explicitly says not supported!
15219         else {
15220           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15221           SendToProgram(buf, cps);
15222         }
15223     }
15224 }
15225
15226 ChessProgramState *
15227 WhitePlayer ()
15228 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15229 {
15230     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15231        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15232         return &second;
15233     return &first;
15234 }
15235
15236 void
15237 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15238 {
15239     char message[MSG_SIZ];
15240     long time, otime;
15241
15242     /* Note: this routine must be called when the clocks are stopped
15243        or when they have *just* been set or switched; otherwise
15244        it will be off by the time since the current tick started.
15245     */
15246     if (machineWhite) {
15247         time = whiteTimeRemaining / 10;
15248         otime = blackTimeRemaining / 10;
15249     } else {
15250         time = blackTimeRemaining / 10;
15251         otime = whiteTimeRemaining / 10;
15252     }
15253     /* [HGM] translate opponent's time by time-odds factor */
15254     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15255
15256     if (time <= 0) time = 1;
15257     if (otime <= 0) otime = 1;
15258
15259     snprintf(message, MSG_SIZ, "time %ld\n", time);
15260     SendToProgram(message, cps);
15261
15262     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15263     SendToProgram(message, cps);
15264 }
15265
15266 int
15267 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15268 {
15269   char buf[MSG_SIZ];
15270   int len = strlen(name);
15271   int val;
15272
15273   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15274     (*p) += len + 1;
15275     sscanf(*p, "%d", &val);
15276     *loc = (val != 0);
15277     while (**p && **p != ' ')
15278       (*p)++;
15279     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15280     SendToProgram(buf, cps);
15281     return TRUE;
15282   }
15283   return FALSE;
15284 }
15285
15286 int
15287 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15288 {
15289   char buf[MSG_SIZ];
15290   int len = strlen(name);
15291   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15292     (*p) += len + 1;
15293     sscanf(*p, "%d", loc);
15294     while (**p && **p != ' ') (*p)++;
15295     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15296     SendToProgram(buf, cps);
15297     return TRUE;
15298   }
15299   return FALSE;
15300 }
15301
15302 int
15303 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15304 {
15305   char buf[MSG_SIZ];
15306   int len = strlen(name);
15307   if (strncmp((*p), name, len) == 0
15308       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15309     (*p) += len + 2;
15310     sscanf(*p, "%[^\"]", loc);
15311     while (**p && **p != '\"') (*p)++;
15312     if (**p == '\"') (*p)++;
15313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15314     SendToProgram(buf, cps);
15315     return TRUE;
15316   }
15317   return FALSE;
15318 }
15319
15320 int
15321 ParseOption (Option *opt, ChessProgramState *cps)
15322 // [HGM] options: process the string that defines an engine option, and determine
15323 // name, type, default value, and allowed value range
15324 {
15325         char *p, *q, buf[MSG_SIZ];
15326         int n, min = (-1)<<31, max = 1<<31, def;
15327
15328         if(p = strstr(opt->name, " -spin ")) {
15329             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15330             if(max < min) max = min; // enforce consistency
15331             if(def < min) def = min;
15332             if(def > max) def = max;
15333             opt->value = def;
15334             opt->min = min;
15335             opt->max = max;
15336             opt->type = Spin;
15337         } else if((p = strstr(opt->name, " -slider "))) {
15338             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15339             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15340             if(max < min) max = min; // enforce consistency
15341             if(def < min) def = min;
15342             if(def > max) def = max;
15343             opt->value = def;
15344             opt->min = min;
15345             opt->max = max;
15346             opt->type = Spin; // Slider;
15347         } else if((p = strstr(opt->name, " -string "))) {
15348             opt->textValue = p+9;
15349             opt->type = TextBox;
15350         } else if((p = strstr(opt->name, " -file "))) {
15351             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15352             opt->textValue = p+7;
15353             opt->type = FileName; // FileName;
15354         } else if((p = strstr(opt->name, " -path "))) {
15355             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15356             opt->textValue = p+7;
15357             opt->type = PathName; // PathName;
15358         } else if(p = strstr(opt->name, " -check ")) {
15359             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15360             opt->value = (def != 0);
15361             opt->type = CheckBox;
15362         } else if(p = strstr(opt->name, " -combo ")) {
15363             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15364             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15365             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15366             opt->value = n = 0;
15367             while(q = StrStr(q, " /// ")) {
15368                 n++; *q = 0;    // count choices, and null-terminate each of them
15369                 q += 5;
15370                 if(*q == '*') { // remember default, which is marked with * prefix
15371                     q++;
15372                     opt->value = n;
15373                 }
15374                 cps->comboList[cps->comboCnt++] = q;
15375             }
15376             cps->comboList[cps->comboCnt++] = NULL;
15377             opt->max = n + 1;
15378             opt->type = ComboBox;
15379         } else if(p = strstr(opt->name, " -button")) {
15380             opt->type = Button;
15381         } else if(p = strstr(opt->name, " -save")) {
15382             opt->type = SaveButton;
15383         } else return FALSE;
15384         *p = 0; // terminate option name
15385         // now look if the command-line options define a setting for this engine option.
15386         if(cps->optionSettings && cps->optionSettings[0])
15387             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15388         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15389           snprintf(buf, MSG_SIZ, "option %s", p);
15390                 if(p = strstr(buf, ",")) *p = 0;
15391                 if(q = strchr(buf, '=')) switch(opt->type) {
15392                     case ComboBox:
15393                         for(n=0; n<opt->max; n++)
15394                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15395                         break;
15396                     case TextBox:
15397                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15398                         break;
15399                     case Spin:
15400                     case CheckBox:
15401                         opt->value = atoi(q+1);
15402                     default:
15403                         break;
15404                 }
15405                 strcat(buf, "\n");
15406                 SendToProgram(buf, cps);
15407         }
15408         return TRUE;
15409 }
15410
15411 void
15412 FeatureDone (ChessProgramState *cps, int val)
15413 {
15414   DelayedEventCallback cb = GetDelayedEvent();
15415   if ((cb == InitBackEnd3 && cps == &first) ||
15416       (cb == SettingsMenuIfReady && cps == &second) ||
15417       (cb == LoadEngine) ||
15418       (cb == TwoMachinesEventIfReady)) {
15419     CancelDelayedEvent();
15420     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15421   }
15422   cps->initDone = val;
15423 }
15424
15425 /* Parse feature command from engine */
15426 void
15427 ParseFeatures (char *args, ChessProgramState *cps)
15428 {
15429   char *p = args;
15430   char *q;
15431   int val;
15432   char buf[MSG_SIZ];
15433
15434   for (;;) {
15435     while (*p == ' ') p++;
15436     if (*p == NULLCHAR) return;
15437
15438     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15439     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15440     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15441     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15442     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15443     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15444     if (BoolFeature(&p, "reuse", &val, cps)) {
15445       /* Engine can disable reuse, but can't enable it if user said no */
15446       if (!val) cps->reuse = FALSE;
15447       continue;
15448     }
15449     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15450     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15451       if (gameMode == TwoMachinesPlay) {
15452         DisplayTwoMachinesTitle();
15453       } else {
15454         DisplayTitle("");
15455       }
15456       continue;
15457     }
15458     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15459     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15460     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15461     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15462     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15463     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15464     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15465     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15466     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15467     if (IntFeature(&p, "done", &val, cps)) {
15468       FeatureDone(cps, val);
15469       continue;
15470     }
15471     /* Added by Tord: */
15472     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15473     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15474     /* End of additions by Tord */
15475
15476     /* [HGM] added features: */
15477     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15478     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15479     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15480     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15481     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15482     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15483     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15484         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15485           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15486             SendToProgram(buf, cps);
15487             continue;
15488         }
15489         if(cps->nrOptions >= MAX_OPTIONS) {
15490             cps->nrOptions--;
15491             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15492             DisplayError(buf, 0);
15493         }
15494         continue;
15495     }
15496     /* End of additions by HGM */
15497
15498     /* unknown feature: complain and skip */
15499     q = p;
15500     while (*q && *q != '=') q++;
15501     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15502     SendToProgram(buf, cps);
15503     p = q;
15504     if (*p == '=') {
15505       p++;
15506       if (*p == '\"') {
15507         p++;
15508         while (*p && *p != '\"') p++;
15509         if (*p == '\"') p++;
15510       } else {
15511         while (*p && *p != ' ') p++;
15512       }
15513     }
15514   }
15515
15516 }
15517
15518 void
15519 PeriodicUpdatesEvent (int newState)
15520 {
15521     if (newState == appData.periodicUpdates)
15522       return;
15523
15524     appData.periodicUpdates=newState;
15525
15526     /* Display type changes, so update it now */
15527 //    DisplayAnalysis();
15528
15529     /* Get the ball rolling again... */
15530     if (newState) {
15531         AnalysisPeriodicEvent(1);
15532         StartAnalysisClock();
15533     }
15534 }
15535
15536 void
15537 PonderNextMoveEvent (int newState)
15538 {
15539     if (newState == appData.ponderNextMove) return;
15540     if (gameMode == EditPosition) EditPositionDone(TRUE);
15541     if (newState) {
15542         SendToProgram("hard\n", &first);
15543         if (gameMode == TwoMachinesPlay) {
15544             SendToProgram("hard\n", &second);
15545         }
15546     } else {
15547         SendToProgram("easy\n", &first);
15548         thinkOutput[0] = NULLCHAR;
15549         if (gameMode == TwoMachinesPlay) {
15550             SendToProgram("easy\n", &second);
15551         }
15552     }
15553     appData.ponderNextMove = newState;
15554 }
15555
15556 void
15557 NewSettingEvent (int option, int *feature, char *command, int value)
15558 {
15559     char buf[MSG_SIZ];
15560
15561     if (gameMode == EditPosition) EditPositionDone(TRUE);
15562     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15563     if(feature == NULL || *feature) SendToProgram(buf, &first);
15564     if (gameMode == TwoMachinesPlay) {
15565         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15566     }
15567 }
15568
15569 void
15570 ShowThinkingEvent ()
15571 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15572 {
15573     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15574     int newState = appData.showThinking
15575         // [HGM] thinking: other features now need thinking output as well
15576         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15577
15578     if (oldState == newState) return;
15579     oldState = newState;
15580     if (gameMode == EditPosition) EditPositionDone(TRUE);
15581     if (oldState) {
15582         SendToProgram("post\n", &first);
15583         if (gameMode == TwoMachinesPlay) {
15584             SendToProgram("post\n", &second);
15585         }
15586     } else {
15587         SendToProgram("nopost\n", &first);
15588         thinkOutput[0] = NULLCHAR;
15589         if (gameMode == TwoMachinesPlay) {
15590             SendToProgram("nopost\n", &second);
15591         }
15592     }
15593 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15594 }
15595
15596 void
15597 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15598 {
15599   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15600   if (pr == NoProc) return;
15601   AskQuestion(title, question, replyPrefix, pr);
15602 }
15603
15604 void
15605 TypeInEvent (char firstChar)
15606 {
15607     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15608         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15609         gameMode == AnalyzeMode || gameMode == EditGame || 
15610         gameMode == EditPosition || gameMode == IcsExamining ||
15611         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15612         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15613                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15614                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15615         gameMode == Training) PopUpMoveDialog(firstChar);
15616 }
15617
15618 void
15619 TypeInDoneEvent (char *move)
15620 {
15621         Board board;
15622         int n, fromX, fromY, toX, toY;
15623         char promoChar;
15624         ChessMove moveType;
15625
15626         // [HGM] FENedit
15627         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15628                 EditPositionPasteFEN(move);
15629                 return;
15630         }
15631         // [HGM] movenum: allow move number to be typed in any mode
15632         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15633           ToNrEvent(2*n-1);
15634           return;
15635         }
15636         // undocumented kludge: allow command-line option to be typed in!
15637         // (potentially fatal, and does not implement the effect of the option.)
15638         // should only be used for options that are values on which future decisions will be made,
15639         // and definitely not on options that would be used during initialization.
15640         if(strstr(move, "!!! -") == move) {
15641             ParseArgsFromString(move+4);
15642             return;
15643         }
15644
15645       if (gameMode != EditGame && currentMove != forwardMostMove && 
15646         gameMode != Training) {
15647         DisplayMoveError(_("Displayed move is not current"));
15648       } else {
15649         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15650           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15651         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15652         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15653           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15654           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15655         } else {
15656           DisplayMoveError(_("Could not parse move"));
15657         }
15658       }
15659 }
15660
15661 void
15662 DisplayMove (int moveNumber)
15663 {
15664     char message[MSG_SIZ];
15665     char res[MSG_SIZ];
15666     char cpThinkOutput[MSG_SIZ];
15667
15668     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15669
15670     if (moveNumber == forwardMostMove - 1 ||
15671         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15672
15673         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15674
15675         if (strchr(cpThinkOutput, '\n')) {
15676             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15677         }
15678     } else {
15679         *cpThinkOutput = NULLCHAR;
15680     }
15681
15682     /* [AS] Hide thinking from human user */
15683     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15684         *cpThinkOutput = NULLCHAR;
15685         if( thinkOutput[0] != NULLCHAR ) {
15686             int i;
15687
15688             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15689                 cpThinkOutput[i] = '.';
15690             }
15691             cpThinkOutput[i] = NULLCHAR;
15692             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15693         }
15694     }
15695
15696     if (moveNumber == forwardMostMove - 1 &&
15697         gameInfo.resultDetails != NULL) {
15698         if (gameInfo.resultDetails[0] == NULLCHAR) {
15699           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15700         } else {
15701           snprintf(res, MSG_SIZ, " {%s} %s",
15702                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15703         }
15704     } else {
15705         res[0] = NULLCHAR;
15706     }
15707
15708     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15709         DisplayMessage(res, cpThinkOutput);
15710     } else {
15711       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15712                 WhiteOnMove(moveNumber) ? " " : ".. ",
15713                 parseList[moveNumber], res);
15714         DisplayMessage(message, cpThinkOutput);
15715     }
15716 }
15717
15718 void
15719 DisplayComment (int moveNumber, char *text)
15720 {
15721     char title[MSG_SIZ];
15722
15723     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15724       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15725     } else {
15726       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15727               WhiteOnMove(moveNumber) ? " " : ".. ",
15728               parseList[moveNumber]);
15729     }
15730     if (text != NULL && (appData.autoDisplayComment || commentUp))
15731         CommentPopUp(title, text);
15732 }
15733
15734 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15735  * might be busy thinking or pondering.  It can be omitted if your
15736  * gnuchess is configured to stop thinking immediately on any user
15737  * input.  However, that gnuchess feature depends on the FIONREAD
15738  * ioctl, which does not work properly on some flavors of Unix.
15739  */
15740 void
15741 Attention (ChessProgramState *cps)
15742 {
15743 #if ATTENTION
15744     if (!cps->useSigint) return;
15745     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15746     switch (gameMode) {
15747       case MachinePlaysWhite:
15748       case MachinePlaysBlack:
15749       case TwoMachinesPlay:
15750       case IcsPlayingWhite:
15751       case IcsPlayingBlack:
15752       case AnalyzeMode:
15753       case AnalyzeFile:
15754         /* Skip if we know it isn't thinking */
15755         if (!cps->maybeThinking) return;
15756         if (appData.debugMode)
15757           fprintf(debugFP, "Interrupting %s\n", cps->which);
15758         InterruptChildProcess(cps->pr);
15759         cps->maybeThinking = FALSE;
15760         break;
15761       default:
15762         break;
15763     }
15764 #endif /*ATTENTION*/
15765 }
15766
15767 int
15768 CheckFlags ()
15769 {
15770     if (whiteTimeRemaining <= 0) {
15771         if (!whiteFlag) {
15772             whiteFlag = TRUE;
15773             if (appData.icsActive) {
15774                 if (appData.autoCallFlag &&
15775                     gameMode == IcsPlayingBlack && !blackFlag) {
15776                   SendToICS(ics_prefix);
15777                   SendToICS("flag\n");
15778                 }
15779             } else {
15780                 if (blackFlag) {
15781                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15782                 } else {
15783                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15784                     if (appData.autoCallFlag) {
15785                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15786                         return TRUE;
15787                     }
15788                 }
15789             }
15790         }
15791     }
15792     if (blackTimeRemaining <= 0) {
15793         if (!blackFlag) {
15794             blackFlag = TRUE;
15795             if (appData.icsActive) {
15796                 if (appData.autoCallFlag &&
15797                     gameMode == IcsPlayingWhite && !whiteFlag) {
15798                   SendToICS(ics_prefix);
15799                   SendToICS("flag\n");
15800                 }
15801             } else {
15802                 if (whiteFlag) {
15803                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15804                 } else {
15805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15806                     if (appData.autoCallFlag) {
15807                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15808                         return TRUE;
15809                     }
15810                 }
15811             }
15812         }
15813     }
15814     return FALSE;
15815 }
15816
15817 void
15818 CheckTimeControl ()
15819 {
15820     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15821         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15822
15823     /*
15824      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15825      */
15826     if ( !WhiteOnMove(forwardMostMove) ) {
15827         /* White made time control */
15828         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15829         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15830         /* [HGM] time odds: correct new time quota for time odds! */
15831                                             / WhitePlayer()->timeOdds;
15832         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15833     } else {
15834         lastBlack -= blackTimeRemaining;
15835         /* Black made time control */
15836         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15837                                             / WhitePlayer()->other->timeOdds;
15838         lastWhite = whiteTimeRemaining;
15839     }
15840 }
15841
15842 void
15843 DisplayBothClocks ()
15844 {
15845     int wom = gameMode == EditPosition ?
15846       !blackPlaysFirst : WhiteOnMove(currentMove);
15847     DisplayWhiteClock(whiteTimeRemaining, wom);
15848     DisplayBlackClock(blackTimeRemaining, !wom);
15849 }
15850
15851
15852 /* Timekeeping seems to be a portability nightmare.  I think everyone
15853    has ftime(), but I'm really not sure, so I'm including some ifdefs
15854    to use other calls if you don't.  Clocks will be less accurate if
15855    you have neither ftime nor gettimeofday.
15856 */
15857
15858 /* VS 2008 requires the #include outside of the function */
15859 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15860 #include <sys/timeb.h>
15861 #endif
15862
15863 /* Get the current time as a TimeMark */
15864 void
15865 GetTimeMark (TimeMark *tm)
15866 {
15867 #if HAVE_GETTIMEOFDAY
15868
15869     struct timeval timeVal;
15870     struct timezone timeZone;
15871
15872     gettimeofday(&timeVal, &timeZone);
15873     tm->sec = (long) timeVal.tv_sec;
15874     tm->ms = (int) (timeVal.tv_usec / 1000L);
15875
15876 #else /*!HAVE_GETTIMEOFDAY*/
15877 #if HAVE_FTIME
15878
15879 // include <sys/timeb.h> / moved to just above start of function
15880     struct timeb timeB;
15881
15882     ftime(&timeB);
15883     tm->sec = (long) timeB.time;
15884     tm->ms = (int) timeB.millitm;
15885
15886 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15887     tm->sec = (long) time(NULL);
15888     tm->ms = 0;
15889 #endif
15890 #endif
15891 }
15892
15893 /* Return the difference in milliseconds between two
15894    time marks.  We assume the difference will fit in a long!
15895 */
15896 long
15897 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15898 {
15899     return 1000L*(tm2->sec - tm1->sec) +
15900            (long) (tm2->ms - tm1->ms);
15901 }
15902
15903
15904 /*
15905  * Code to manage the game clocks.
15906  *
15907  * In tournament play, black starts the clock and then white makes a move.
15908  * We give the human user a slight advantage if he is playing white---the
15909  * clocks don't run until he makes his first move, so it takes zero time.
15910  * Also, we don't account for network lag, so we could get out of sync
15911  * with GNU Chess's clock -- but then, referees are always right.
15912  */
15913
15914 static TimeMark tickStartTM;
15915 static long intendedTickLength;
15916
15917 long
15918 NextTickLength (long timeRemaining)
15919 {
15920     long nominalTickLength, nextTickLength;
15921
15922     if (timeRemaining > 0L && timeRemaining <= 10000L)
15923       nominalTickLength = 100L;
15924     else
15925       nominalTickLength = 1000L;
15926     nextTickLength = timeRemaining % nominalTickLength;
15927     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15928
15929     return nextTickLength;
15930 }
15931
15932 /* Adjust clock one minute up or down */
15933 void
15934 AdjustClock (Boolean which, int dir)
15935 {
15936     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15937     if(which) blackTimeRemaining += 60000*dir;
15938     else      whiteTimeRemaining += 60000*dir;
15939     DisplayBothClocks();
15940     adjustedClock = TRUE;
15941 }
15942
15943 /* Stop clocks and reset to a fresh time control */
15944 void
15945 ResetClocks ()
15946 {
15947     (void) StopClockTimer();
15948     if (appData.icsActive) {
15949         whiteTimeRemaining = blackTimeRemaining = 0;
15950     } else if (searchTime) {
15951         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15952         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15953     } else { /* [HGM] correct new time quote for time odds */
15954         whiteTC = blackTC = fullTimeControlString;
15955         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15956         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15957     }
15958     if (whiteFlag || blackFlag) {
15959         DisplayTitle("");
15960         whiteFlag = blackFlag = FALSE;
15961     }
15962     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15963     DisplayBothClocks();
15964     adjustedClock = FALSE;
15965 }
15966
15967 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15968
15969 /* Decrement running clock by amount of time that has passed */
15970 void
15971 DecrementClocks ()
15972 {
15973     long timeRemaining;
15974     long lastTickLength, fudge;
15975     TimeMark now;
15976
15977     if (!appData.clockMode) return;
15978     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15979
15980     GetTimeMark(&now);
15981
15982     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15983
15984     /* Fudge if we woke up a little too soon */
15985     fudge = intendedTickLength - lastTickLength;
15986     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15987
15988     if (WhiteOnMove(forwardMostMove)) {
15989         if(whiteNPS >= 0) lastTickLength = 0;
15990         timeRemaining = whiteTimeRemaining -= lastTickLength;
15991         if(timeRemaining < 0 && !appData.icsActive) {
15992             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15993             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15994                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15995                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15996             }
15997         }
15998         DisplayWhiteClock(whiteTimeRemaining - fudge,
15999                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16000     } else {
16001         if(blackNPS >= 0) lastTickLength = 0;
16002         timeRemaining = blackTimeRemaining -= lastTickLength;
16003         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16004             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16005             if(suddenDeath) {
16006                 blackStartMove = forwardMostMove;
16007                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16008             }
16009         }
16010         DisplayBlackClock(blackTimeRemaining - fudge,
16011                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16012     }
16013     if (CheckFlags()) return;
16014
16015     tickStartTM = now;
16016     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16017     StartClockTimer(intendedTickLength);
16018
16019     /* if the time remaining has fallen below the alarm threshold, sound the
16020      * alarm. if the alarm has sounded and (due to a takeback or time control
16021      * with increment) the time remaining has increased to a level above the
16022      * threshold, reset the alarm so it can sound again.
16023      */
16024
16025     if (appData.icsActive && appData.icsAlarm) {
16026
16027         /* make sure we are dealing with the user's clock */
16028         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16029                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16030            )) return;
16031
16032         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16033             alarmSounded = FALSE;
16034         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16035             PlayAlarmSound();
16036             alarmSounded = TRUE;
16037         }
16038     }
16039 }
16040
16041
16042 /* A player has just moved, so stop the previously running
16043    clock and (if in clock mode) start the other one.
16044    We redisplay both clocks in case we're in ICS mode, because
16045    ICS gives us an update to both clocks after every move.
16046    Note that this routine is called *after* forwardMostMove
16047    is updated, so the last fractional tick must be subtracted
16048    from the color that is *not* on move now.
16049 */
16050 void
16051 SwitchClocks (int newMoveNr)
16052 {
16053     long lastTickLength;
16054     TimeMark now;
16055     int flagged = FALSE;
16056
16057     GetTimeMark(&now);
16058
16059     if (StopClockTimer() && appData.clockMode) {
16060         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16061         if (!WhiteOnMove(forwardMostMove)) {
16062             if(blackNPS >= 0) lastTickLength = 0;
16063             blackTimeRemaining -= lastTickLength;
16064            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16065 //         if(pvInfoList[forwardMostMove].time == -1)
16066                  pvInfoList[forwardMostMove].time =               // use GUI time
16067                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16068         } else {
16069            if(whiteNPS >= 0) lastTickLength = 0;
16070            whiteTimeRemaining -= lastTickLength;
16071            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16072 //         if(pvInfoList[forwardMostMove].time == -1)
16073                  pvInfoList[forwardMostMove].time =
16074                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16075         }
16076         flagged = CheckFlags();
16077     }
16078     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16079     CheckTimeControl();
16080
16081     if (flagged || !appData.clockMode) return;
16082
16083     switch (gameMode) {
16084       case MachinePlaysBlack:
16085       case MachinePlaysWhite:
16086       case BeginningOfGame:
16087         if (pausing) return;
16088         break;
16089
16090       case EditGame:
16091       case PlayFromGameFile:
16092       case IcsExamining:
16093         return;
16094
16095       default:
16096         break;
16097     }
16098
16099     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16100         if(WhiteOnMove(forwardMostMove))
16101              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16102         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16103     }
16104
16105     tickStartTM = now;
16106     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16107       whiteTimeRemaining : blackTimeRemaining);
16108     StartClockTimer(intendedTickLength);
16109 }
16110
16111
16112 /* Stop both clocks */
16113 void
16114 StopClocks ()
16115 {
16116     long lastTickLength;
16117     TimeMark now;
16118
16119     if (!StopClockTimer()) return;
16120     if (!appData.clockMode) return;
16121
16122     GetTimeMark(&now);
16123
16124     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16125     if (WhiteOnMove(forwardMostMove)) {
16126         if(whiteNPS >= 0) lastTickLength = 0;
16127         whiteTimeRemaining -= lastTickLength;
16128         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16129     } else {
16130         if(blackNPS >= 0) lastTickLength = 0;
16131         blackTimeRemaining -= lastTickLength;
16132         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16133     }
16134     CheckFlags();
16135 }
16136
16137 /* Start clock of player on move.  Time may have been reset, so
16138    if clock is already running, stop and restart it. */
16139 void
16140 StartClocks ()
16141 {
16142     (void) StopClockTimer(); /* in case it was running already */
16143     DisplayBothClocks();
16144     if (CheckFlags()) return;
16145
16146     if (!appData.clockMode) return;
16147     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16148
16149     GetTimeMark(&tickStartTM);
16150     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16151       whiteTimeRemaining : blackTimeRemaining);
16152
16153    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16154     whiteNPS = blackNPS = -1;
16155     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16156        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16157         whiteNPS = first.nps;
16158     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16159        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16160         blackNPS = first.nps;
16161     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16162         whiteNPS = second.nps;
16163     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16164         blackNPS = second.nps;
16165     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16166
16167     StartClockTimer(intendedTickLength);
16168 }
16169
16170 char *
16171 TimeString (long ms)
16172 {
16173     long second, minute, hour, day;
16174     char *sign = "";
16175     static char buf[32];
16176
16177     if (ms > 0 && ms <= 9900) {
16178       /* convert milliseconds to tenths, rounding up */
16179       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16180
16181       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16182       return buf;
16183     }
16184
16185     /* convert milliseconds to seconds, rounding up */
16186     /* use floating point to avoid strangeness of integer division
16187        with negative dividends on many machines */
16188     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16189
16190     if (second < 0) {
16191         sign = "-";
16192         second = -second;
16193     }
16194
16195     day = second / (60 * 60 * 24);
16196     second = second % (60 * 60 * 24);
16197     hour = second / (60 * 60);
16198     second = second % (60 * 60);
16199     minute = second / 60;
16200     second = second % 60;
16201
16202     if (day > 0)
16203       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16204               sign, day, hour, minute, second);
16205     else if (hour > 0)
16206       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16207     else
16208       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16209
16210     return buf;
16211 }
16212
16213
16214 /*
16215  * This is necessary because some C libraries aren't ANSI C compliant yet.
16216  */
16217 char *
16218 StrStr (char *string, char *match)
16219 {
16220     int i, length;
16221
16222     length = strlen(match);
16223
16224     for (i = strlen(string) - length; i >= 0; i--, string++)
16225       if (!strncmp(match, string, length))
16226         return string;
16227
16228     return NULL;
16229 }
16230
16231 char *
16232 StrCaseStr (char *string, char *match)
16233 {
16234     int i, j, length;
16235
16236     length = strlen(match);
16237
16238     for (i = strlen(string) - length; i >= 0; i--, string++) {
16239         for (j = 0; j < length; j++) {
16240             if (ToLower(match[j]) != ToLower(string[j]))
16241               break;
16242         }
16243         if (j == length) return string;
16244     }
16245
16246     return NULL;
16247 }
16248
16249 #ifndef _amigados
16250 int
16251 StrCaseCmp (char *s1, char *s2)
16252 {
16253     char c1, c2;
16254
16255     for (;;) {
16256         c1 = ToLower(*s1++);
16257         c2 = ToLower(*s2++);
16258         if (c1 > c2) return 1;
16259         if (c1 < c2) return -1;
16260         if (c1 == NULLCHAR) return 0;
16261     }
16262 }
16263
16264
16265 int
16266 ToLower (int c)
16267 {
16268     return isupper(c) ? tolower(c) : c;
16269 }
16270
16271
16272 int
16273 ToUpper (int c)
16274 {
16275     return islower(c) ? toupper(c) : c;
16276 }
16277 #endif /* !_amigados    */
16278
16279 char *
16280 StrSave (char *s)
16281 {
16282   char *ret;
16283
16284   if ((ret = (char *) malloc(strlen(s) + 1)))
16285     {
16286       safeStrCpy(ret, s, strlen(s)+1);
16287     }
16288   return ret;
16289 }
16290
16291 char *
16292 StrSavePtr (char *s, char **savePtr)
16293 {
16294     if (*savePtr) {
16295         free(*savePtr);
16296     }
16297     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16298       safeStrCpy(*savePtr, s, strlen(s)+1);
16299     }
16300     return(*savePtr);
16301 }
16302
16303 char *
16304 PGNDate ()
16305 {
16306     time_t clock;
16307     struct tm *tm;
16308     char buf[MSG_SIZ];
16309
16310     clock = time((time_t *)NULL);
16311     tm = localtime(&clock);
16312     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16313             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16314     return StrSave(buf);
16315 }
16316
16317
16318 char *
16319 PositionToFEN (int move, char *overrideCastling)
16320 {
16321     int i, j, fromX, fromY, toX, toY;
16322     int whiteToPlay;
16323     char buf[MSG_SIZ];
16324     char *p, *q;
16325     int emptycount;
16326     ChessSquare piece;
16327
16328     whiteToPlay = (gameMode == EditPosition) ?
16329       !blackPlaysFirst : (move % 2 == 0);
16330     p = buf;
16331
16332     /* Piece placement data */
16333     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16334         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16335         emptycount = 0;
16336         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16337             if (boards[move][i][j] == EmptySquare) {
16338                 emptycount++;
16339             } else { ChessSquare piece = boards[move][i][j];
16340                 if (emptycount > 0) {
16341                     if(emptycount<10) /* [HGM] can be >= 10 */
16342                         *p++ = '0' + emptycount;
16343                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16344                     emptycount = 0;
16345                 }
16346                 if(PieceToChar(piece) == '+') {
16347                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16348                     *p++ = '+';
16349                     piece = (ChessSquare)(DEMOTED piece);
16350                 }
16351                 *p++ = PieceToChar(piece);
16352                 if(p[-1] == '~') {
16353                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16354                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16355                     *p++ = '~';
16356                 }
16357             }
16358         }
16359         if (emptycount > 0) {
16360             if(emptycount<10) /* [HGM] can be >= 10 */
16361                 *p++ = '0' + emptycount;
16362             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16363             emptycount = 0;
16364         }
16365         *p++ = '/';
16366     }
16367     *(p - 1) = ' ';
16368
16369     /* [HGM] print Crazyhouse or Shogi holdings */
16370     if( gameInfo.holdingsWidth ) {
16371         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16372         q = p;
16373         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16374             piece = boards[move][i][BOARD_WIDTH-1];
16375             if( piece != EmptySquare )
16376               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16377                   *p++ = PieceToChar(piece);
16378         }
16379         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16380             piece = boards[move][BOARD_HEIGHT-i-1][0];
16381             if( piece != EmptySquare )
16382               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16383                   *p++ = PieceToChar(piece);
16384         }
16385
16386         if( q == p ) *p++ = '-';
16387         *p++ = ']';
16388         *p++ = ' ';
16389     }
16390
16391     /* Active color */
16392     *p++ = whiteToPlay ? 'w' : 'b';
16393     *p++ = ' ';
16394
16395   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16396     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16397   } else {
16398   if(nrCastlingRights) {
16399      q = p;
16400      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16401        /* [HGM] write directly from rights */
16402            if(boards[move][CASTLING][2] != NoRights &&
16403               boards[move][CASTLING][0] != NoRights   )
16404                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16405            if(boards[move][CASTLING][2] != NoRights &&
16406               boards[move][CASTLING][1] != NoRights   )
16407                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16408            if(boards[move][CASTLING][5] != NoRights &&
16409               boards[move][CASTLING][3] != NoRights   )
16410                 *p++ = boards[move][CASTLING][3] + AAA;
16411            if(boards[move][CASTLING][5] != NoRights &&
16412               boards[move][CASTLING][4] != NoRights   )
16413                 *p++ = boards[move][CASTLING][4] + AAA;
16414      } else {
16415
16416         /* [HGM] write true castling rights */
16417         if( nrCastlingRights == 6 ) {
16418             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16419                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16420             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16421                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16422             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16423                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16424             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16425                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16426         }
16427      }
16428      if (q == p) *p++ = '-'; /* No castling rights */
16429      *p++ = ' ';
16430   }
16431
16432   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16433      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16434     /* En passant target square */
16435     if (move > backwardMostMove) {
16436         fromX = moveList[move - 1][0] - AAA;
16437         fromY = moveList[move - 1][1] - ONE;
16438         toX = moveList[move - 1][2] - AAA;
16439         toY = moveList[move - 1][3] - ONE;
16440         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16441             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16442             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16443             fromX == toX) {
16444             /* 2-square pawn move just happened */
16445             *p++ = toX + AAA;
16446             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16447         } else {
16448             *p++ = '-';
16449         }
16450     } else if(move == backwardMostMove) {
16451         // [HGM] perhaps we should always do it like this, and forget the above?
16452         if((signed char)boards[move][EP_STATUS] >= 0) {
16453             *p++ = boards[move][EP_STATUS] + AAA;
16454             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16455         } else {
16456             *p++ = '-';
16457         }
16458     } else {
16459         *p++ = '-';
16460     }
16461     *p++ = ' ';
16462   }
16463   }
16464
16465     /* [HGM] find reversible plies */
16466     {   int i = 0, j=move;
16467
16468         if (appData.debugMode) { int k;
16469             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16470             for(k=backwardMostMove; k<=forwardMostMove; k++)
16471                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16472
16473         }
16474
16475         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16476         if( j == backwardMostMove ) i += initialRulePlies;
16477         sprintf(p, "%d ", i);
16478         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16479     }
16480     /* Fullmove number */
16481     sprintf(p, "%d", (move / 2) + 1);
16482
16483     return StrSave(buf);
16484 }
16485
16486 Boolean
16487 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16488 {
16489     int i, j;
16490     char *p, c;
16491     int emptycount;
16492     ChessSquare piece;
16493
16494     p = fen;
16495
16496     /* [HGM] by default clear Crazyhouse holdings, if present */
16497     if(gameInfo.holdingsWidth) {
16498        for(i=0; i<BOARD_HEIGHT; i++) {
16499            board[i][0]             = EmptySquare; /* black holdings */
16500            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16501            board[i][1]             = (ChessSquare) 0; /* black counts */
16502            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16503        }
16504     }
16505
16506     /* Piece placement data */
16507     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16508         j = 0;
16509         for (;;) {
16510             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16511                 if (*p == '/') p++;
16512                 emptycount = gameInfo.boardWidth - j;
16513                 while (emptycount--)
16514                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16515                 break;
16516 #if(BOARD_FILES >= 10)
16517             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16518                 p++; emptycount=10;
16519                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16520                 while (emptycount--)
16521                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16522 #endif
16523             } else if (isdigit(*p)) {
16524                 emptycount = *p++ - '0';
16525                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16526                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16527                 while (emptycount--)
16528                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16529             } else if (*p == '+' || isalpha(*p)) {
16530                 if (j >= gameInfo.boardWidth) return FALSE;
16531                 if(*p=='+') {
16532                     piece = CharToPiece(*++p);
16533                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16534                     piece = (ChessSquare) (PROMOTED piece ); p++;
16535                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16536                 } else piece = CharToPiece(*p++);
16537
16538                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16539                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16540                     piece = (ChessSquare) (PROMOTED piece);
16541                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16542                     p++;
16543                 }
16544                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16545             } else {
16546                 return FALSE;
16547             }
16548         }
16549     }
16550     while (*p == '/' || *p == ' ') p++;
16551
16552     /* [HGM] look for Crazyhouse holdings here */
16553     while(*p==' ') p++;
16554     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16555         if(*p == '[') p++;
16556         if(*p == '-' ) p++; /* empty holdings */ else {
16557             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16558             /* if we would allow FEN reading to set board size, we would   */
16559             /* have to add holdings and shift the board read so far here   */
16560             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16561                 p++;
16562                 if((int) piece >= (int) BlackPawn ) {
16563                     i = (int)piece - (int)BlackPawn;
16564                     i = PieceToNumber((ChessSquare)i);
16565                     if( i >= gameInfo.holdingsSize ) return FALSE;
16566                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16567                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16568                 } else {
16569                     i = (int)piece - (int)WhitePawn;
16570                     i = PieceToNumber((ChessSquare)i);
16571                     if( i >= gameInfo.holdingsSize ) return FALSE;
16572                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16573                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16574                 }
16575             }
16576         }
16577         if(*p == ']') p++;
16578     }
16579
16580     while(*p == ' ') p++;
16581
16582     /* Active color */
16583     c = *p++;
16584     if(appData.colorNickNames) {
16585       if( c == appData.colorNickNames[0] ) c = 'w'; else
16586       if( c == appData.colorNickNames[1] ) c = 'b';
16587     }
16588     switch (c) {
16589       case 'w':
16590         *blackPlaysFirst = FALSE;
16591         break;
16592       case 'b':
16593         *blackPlaysFirst = TRUE;
16594         break;
16595       default:
16596         return FALSE;
16597     }
16598
16599     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16600     /* return the extra info in global variiables             */
16601
16602     /* set defaults in case FEN is incomplete */
16603     board[EP_STATUS] = EP_UNKNOWN;
16604     for(i=0; i<nrCastlingRights; i++ ) {
16605         board[CASTLING][i] =
16606             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16607     }   /* assume possible unless obviously impossible */
16608     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16609     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16610     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16611                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16612     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16613     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16614     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16615                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16616     FENrulePlies = 0;
16617
16618     while(*p==' ') p++;
16619     if(nrCastlingRights) {
16620       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16621           /* castling indicator present, so default becomes no castlings */
16622           for(i=0; i<nrCastlingRights; i++ ) {
16623                  board[CASTLING][i] = NoRights;
16624           }
16625       }
16626       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16627              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16628              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16629              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16630         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16631
16632         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16633             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16634             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16635         }
16636         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16637             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16638         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16639                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16640         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16641                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16642         switch(c) {
16643           case'K':
16644               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16645               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16646               board[CASTLING][2] = whiteKingFile;
16647               break;
16648           case'Q':
16649               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16650               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16651               board[CASTLING][2] = whiteKingFile;
16652               break;
16653           case'k':
16654               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16655               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16656               board[CASTLING][5] = blackKingFile;
16657               break;
16658           case'q':
16659               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16660               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16661               board[CASTLING][5] = blackKingFile;
16662           case '-':
16663               break;
16664           default: /* FRC castlings */
16665               if(c >= 'a') { /* black rights */
16666                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16667                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16668                   if(i == BOARD_RGHT) break;
16669                   board[CASTLING][5] = i;
16670                   c -= AAA;
16671                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16672                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16673                   if(c > i)
16674                       board[CASTLING][3] = c;
16675                   else
16676                       board[CASTLING][4] = c;
16677               } else { /* white rights */
16678                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16679                     if(board[0][i] == WhiteKing) break;
16680                   if(i == BOARD_RGHT) break;
16681                   board[CASTLING][2] = i;
16682                   c -= AAA - 'a' + 'A';
16683                   if(board[0][c] >= WhiteKing) break;
16684                   if(c > i)
16685                       board[CASTLING][0] = c;
16686                   else
16687                       board[CASTLING][1] = c;
16688               }
16689         }
16690       }
16691       for(i=0; i<nrCastlingRights; i++)
16692         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16693     if (appData.debugMode) {
16694         fprintf(debugFP, "FEN castling rights:");
16695         for(i=0; i<nrCastlingRights; i++)
16696         fprintf(debugFP, " %d", board[CASTLING][i]);
16697         fprintf(debugFP, "\n");
16698     }
16699
16700       while(*p==' ') p++;
16701     }
16702
16703     /* read e.p. field in games that know e.p. capture */
16704     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16705        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16706       if(*p=='-') {
16707         p++; board[EP_STATUS] = EP_NONE;
16708       } else {
16709          char c = *p++ - AAA;
16710
16711          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16712          if(*p >= '0' && *p <='9') p++;
16713          board[EP_STATUS] = c;
16714       }
16715     }
16716
16717
16718     if(sscanf(p, "%d", &i) == 1) {
16719         FENrulePlies = i; /* 50-move ply counter */
16720         /* (The move number is still ignored)    */
16721     }
16722
16723     return TRUE;
16724 }
16725
16726 void
16727 EditPositionPasteFEN (char *fen)
16728 {
16729   if (fen != NULL) {
16730     Board initial_position;
16731
16732     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16733       DisplayError(_("Bad FEN position in clipboard"), 0);
16734       return ;
16735     } else {
16736       int savedBlackPlaysFirst = blackPlaysFirst;
16737       EditPositionEvent();
16738       blackPlaysFirst = savedBlackPlaysFirst;
16739       CopyBoard(boards[0], initial_position);
16740       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16741       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16742       DisplayBothClocks();
16743       DrawPosition(FALSE, boards[currentMove]);
16744     }
16745   }
16746 }
16747
16748 static char cseq[12] = "\\   ";
16749
16750 Boolean
16751 set_cont_sequence (char *new_seq)
16752 {
16753     int len;
16754     Boolean ret;
16755
16756     // handle bad attempts to set the sequence
16757         if (!new_seq)
16758                 return 0; // acceptable error - no debug
16759
16760     len = strlen(new_seq);
16761     ret = (len > 0) && (len < sizeof(cseq));
16762     if (ret)
16763       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16764     else if (appData.debugMode)
16765       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16766     return ret;
16767 }
16768
16769 /*
16770     reformat a source message so words don't cross the width boundary.  internal
16771     newlines are not removed.  returns the wrapped size (no null character unless
16772     included in source message).  If dest is NULL, only calculate the size required
16773     for the dest buffer.  lp argument indicats line position upon entry, and it's
16774     passed back upon exit.
16775 */
16776 int
16777 wrap (char *dest, char *src, int count, int width, int *lp)
16778 {
16779     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16780
16781     cseq_len = strlen(cseq);
16782     old_line = line = *lp;
16783     ansi = len = clen = 0;
16784
16785     for (i=0; i < count; i++)
16786     {
16787         if (src[i] == '\033')
16788             ansi = 1;
16789
16790         // if we hit the width, back up
16791         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16792         {
16793             // store i & len in case the word is too long
16794             old_i = i, old_len = len;
16795
16796             // find the end of the last word
16797             while (i && src[i] != ' ' && src[i] != '\n')
16798             {
16799                 i--;
16800                 len--;
16801             }
16802
16803             // word too long?  restore i & len before splitting it
16804             if ((old_i-i+clen) >= width)
16805             {
16806                 i = old_i;
16807                 len = old_len;
16808             }
16809
16810             // extra space?
16811             if (i && src[i-1] == ' ')
16812                 len--;
16813
16814             if (src[i] != ' ' && src[i] != '\n')
16815             {
16816                 i--;
16817                 if (len)
16818                     len--;
16819             }
16820
16821             // now append the newline and continuation sequence
16822             if (dest)
16823                 dest[len] = '\n';
16824             len++;
16825             if (dest)
16826                 strncpy(dest+len, cseq, cseq_len);
16827             len += cseq_len;
16828             line = cseq_len;
16829             clen = cseq_len;
16830             continue;
16831         }
16832
16833         if (dest)
16834             dest[len] = src[i];
16835         len++;
16836         if (!ansi)
16837             line++;
16838         if (src[i] == '\n')
16839             line = 0;
16840         if (src[i] == 'm')
16841             ansi = 0;
16842     }
16843     if (dest && appData.debugMode)
16844     {
16845         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16846             count, width, line, len, *lp);
16847         show_bytes(debugFP, src, count);
16848         fprintf(debugFP, "\ndest: ");
16849         show_bytes(debugFP, dest, len);
16850         fprintf(debugFP, "\n");
16851     }
16852     *lp = dest ? line : old_line;
16853
16854     return len;
16855 }
16856
16857 // [HGM] vari: routines for shelving variations
16858 Boolean modeRestore = FALSE;
16859
16860 void
16861 PushInner (int firstMove, int lastMove)
16862 {
16863         int i, j, nrMoves = lastMove - firstMove;
16864
16865         // push current tail of game on stack
16866         savedResult[storedGames] = gameInfo.result;
16867         savedDetails[storedGames] = gameInfo.resultDetails;
16868         gameInfo.resultDetails = NULL;
16869         savedFirst[storedGames] = firstMove;
16870         savedLast [storedGames] = lastMove;
16871         savedFramePtr[storedGames] = framePtr;
16872         framePtr -= nrMoves; // reserve space for the boards
16873         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16874             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16875             for(j=0; j<MOVE_LEN; j++)
16876                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16877             for(j=0; j<2*MOVE_LEN; j++)
16878                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16879             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16880             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16881             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16882             pvInfoList[firstMove+i-1].depth = 0;
16883             commentList[framePtr+i] = commentList[firstMove+i];
16884             commentList[firstMove+i] = NULL;
16885         }
16886
16887         storedGames++;
16888         forwardMostMove = firstMove; // truncate game so we can start variation
16889 }
16890
16891 void
16892 PushTail (int firstMove, int lastMove)
16893 {
16894         if(appData.icsActive) { // only in local mode
16895                 forwardMostMove = currentMove; // mimic old ICS behavior
16896                 return;
16897         }
16898         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16899
16900         PushInner(firstMove, lastMove);
16901         if(storedGames == 1) GreyRevert(FALSE);
16902         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16903 }
16904
16905 void
16906 PopInner (Boolean annotate)
16907 {
16908         int i, j, nrMoves;
16909         char buf[8000], moveBuf[20];
16910
16911         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16912         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16913         nrMoves = savedLast[storedGames] - currentMove;
16914         if(annotate) {
16915                 int cnt = 10;
16916                 if(!WhiteOnMove(currentMove))
16917                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16918                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16919                 for(i=currentMove; i<forwardMostMove; i++) {
16920                         if(WhiteOnMove(i))
16921                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16922                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16923                         strcat(buf, moveBuf);
16924                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16925                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16926                 }
16927                 strcat(buf, ")");
16928         }
16929         for(i=1; i<=nrMoves; i++) { // copy last variation back
16930             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16931             for(j=0; j<MOVE_LEN; j++)
16932                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16933             for(j=0; j<2*MOVE_LEN; j++)
16934                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16935             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16936             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16937             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16938             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16939             commentList[currentMove+i] = commentList[framePtr+i];
16940             commentList[framePtr+i] = NULL;
16941         }
16942         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16943         framePtr = savedFramePtr[storedGames];
16944         gameInfo.result = savedResult[storedGames];
16945         if(gameInfo.resultDetails != NULL) {
16946             free(gameInfo.resultDetails);
16947       }
16948         gameInfo.resultDetails = savedDetails[storedGames];
16949         forwardMostMove = currentMove + nrMoves;
16950 }
16951
16952 Boolean
16953 PopTail (Boolean annotate)
16954 {
16955         if(appData.icsActive) return FALSE; // only in local mode
16956         if(!storedGames) return FALSE; // sanity
16957         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16958
16959         PopInner(annotate);
16960         if(currentMove < forwardMostMove) ForwardEvent(); else
16961         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16962
16963         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16964         return TRUE;
16965 }
16966
16967 void
16968 CleanupTail ()
16969 {       // remove all shelved variations
16970         int i;
16971         for(i=0; i<storedGames; i++) {
16972             if(savedDetails[i])
16973                 free(savedDetails[i]);
16974             savedDetails[i] = NULL;
16975         }
16976         for(i=framePtr; i<MAX_MOVES; i++) {
16977                 if(commentList[i]) free(commentList[i]);
16978                 commentList[i] = NULL;
16979         }
16980         framePtr = MAX_MOVES-1;
16981         storedGames = 0;
16982 }
16983
16984 void
16985 LoadVariation (int index, char *text)
16986 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16987         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16988         int level = 0, move;
16989
16990         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16991         // first find outermost bracketing variation
16992         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16993             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16994                 if(*p == '{') wait = '}'; else
16995                 if(*p == '[') wait = ']'; else
16996                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16997                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16998             }
16999             if(*p == wait) wait = NULLCHAR; // closing ]} found
17000             p++;
17001         }
17002         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17003         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17004         end[1] = NULLCHAR; // clip off comment beyond variation
17005         ToNrEvent(currentMove-1);
17006         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17007         // kludge: use ParsePV() to append variation to game
17008         move = currentMove;
17009         ParsePV(start, TRUE, TRUE);
17010         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17011         ClearPremoveHighlights();
17012         CommentPopDown();
17013         ToNrEvent(currentMove+1);
17014 }
17015