db52e9c857131e7f0c9f6fd4ea3eb2a9b1e338f0
[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         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
924     } else appData.directory[i] = ".";
925     if(params[0]) {
926         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
927         snprintf(command, MSG_SIZ, "%s %s", p, params);
928         p = command;
929     }
930     appData.chessProgram[i] = strdup(p);
931     appData.isUCI[i] = isUCI;
932     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
933     appData.hasOwnBookUCI[i] = hasBook;
934     if(!nickName[0]) useNick = FALSE;
935     if(useNick) ASSIGN(appData.pgnName[i], nickName);
936     if(addToList) {
937         int len;
938         char quote;
939         q = firstChessProgramNames;
940         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
941         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
943                         quote, p, quote, appData.directory[i], 
944                         useNick ? " -fn \"" : "",
945                         useNick ? nickName : "",
946                         useNick ? "\"" : "",
947                         v1 ? " -firstProtocolVersion 1" : "",
948                         hasBook ? "" : " -fNoOwnBookUCI",
949                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
950                         storeVariant ? " -variant " : "",
951                         storeVariant ? VariantName(gameInfo.variant) : "");
952         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
953         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
954         if(q)   free(q);
955         FloatToFront(&appData.recentEngineList, buf);
956     }
957     ReplaceEngine(cps, i);
958 }
959
960 void
961 InitTimeControls ()
962 {
963     int matched, min, sec;
964     /*
965      * Parse timeControl resource
966      */
967     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
968                           appData.movesPerSession)) {
969         char buf[MSG_SIZ];
970         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
971         DisplayFatalError(buf, 0, 2);
972     }
973
974     /*
975      * Parse searchTime resource
976      */
977     if (*appData.searchTime != NULLCHAR) {
978         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
979         if (matched == 1) {
980             searchTime = min * 60;
981         } else if (matched == 2) {
982             searchTime = min * 60 + sec;
983         } else {
984             char buf[MSG_SIZ];
985             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
986             DisplayFatalError(buf, 0, 2);
987         }
988     }
989 }
990
991 void
992 InitBackEnd1 ()
993 {
994
995     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
996     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
997
998     GetTimeMark(&programStartTime);
999     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1000     appData.seedBase = random() + (random()<<15);
1001     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1002
1003     ClearProgramStats();
1004     programStats.ok_to_send = 1;
1005     programStats.seen_stat = 0;
1006
1007     /*
1008      * Initialize game list
1009      */
1010     ListNew(&gameList);
1011
1012
1013     /*
1014      * Internet chess server status
1015      */
1016     if (appData.icsActive) {
1017         appData.matchMode = FALSE;
1018         appData.matchGames = 0;
1019 #if ZIPPY
1020         appData.noChessProgram = !appData.zippyPlay;
1021 #else
1022         appData.zippyPlay = FALSE;
1023         appData.zippyTalk = FALSE;
1024         appData.noChessProgram = TRUE;
1025 #endif
1026         if (*appData.icsHelper != NULLCHAR) {
1027             appData.useTelnet = TRUE;
1028             appData.telnetProgram = appData.icsHelper;
1029         }
1030     } else {
1031         appData.zippyTalk = appData.zippyPlay = FALSE;
1032     }
1033
1034     /* [AS] Initialize pv info list [HGM] and game state */
1035     {
1036         int i, j;
1037
1038         for( i=0; i<=framePtr; i++ ) {
1039             pvInfoList[i].depth = -1;
1040             boards[i][EP_STATUS] = EP_NONE;
1041             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1042         }
1043     }
1044
1045     InitTimeControls();
1046
1047     /* [AS] Adjudication threshold */
1048     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1049
1050     InitEngine(&first, 0);
1051     InitEngine(&second, 1);
1052     CommonEngineInit();
1053
1054     pairing.which = "pairing"; // pairing engine
1055     pairing.pr = NoProc;
1056     pairing.isr = NULL;
1057     pairing.program = appData.pairingEngine;
1058     pairing.host = "localhost";
1059     pairing.dir = ".";
1060
1061     if (appData.icsActive) {
1062         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1063     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1064         appData.clockMode = FALSE;
1065         first.sendTime = second.sendTime = 0;
1066     }
1067
1068 #if ZIPPY
1069     /* Override some settings from environment variables, for backward
1070        compatibility.  Unfortunately it's not feasible to have the env
1071        vars just set defaults, at least in xboard.  Ugh.
1072     */
1073     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1074       ZippyInit();
1075     }
1076 #endif
1077
1078     if (!appData.icsActive) {
1079       char buf[MSG_SIZ];
1080       int len;
1081
1082       /* Check for variants that are supported only in ICS mode,
1083          or not at all.  Some that are accepted here nevertheless
1084          have bugs; see comments below.
1085       */
1086       VariantClass variant = StringToVariant(appData.variant);
1087       switch (variant) {
1088       case VariantBughouse:     /* need four players and two boards */
1089       case VariantKriegspiel:   /* need to hide pieces and move details */
1090         /* case VariantFischeRandom: (Fabien: moved below) */
1091         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1092         if( (len >= MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantUnknown:
1099       case VariantLoadable:
1100       case Variant29:
1101       case Variant30:
1102       case Variant31:
1103       case Variant32:
1104       case Variant33:
1105       case Variant34:
1106       case Variant35:
1107       case Variant36:
1108       default:
1109         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1110         if( (len >= MSG_SIZ) && appData.debugMode )
1111           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112
1113         DisplayFatalError(buf, 0, 2);
1114         return;
1115
1116       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1117       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1118       case VariantGothic:     /* [HGM] should work */
1119       case VariantCapablanca: /* [HGM] should work */
1120       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1121       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1122       case VariantKnightmate: /* [HGM] should work */
1123       case VariantCylinder:   /* [HGM] untested */
1124       case VariantFalcon:     /* [HGM] untested */
1125       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1126                                  offboard interposition not understood */
1127       case VariantNormal:     /* definitely works! */
1128       case VariantWildCastle: /* pieces not automatically shuffled */
1129       case VariantNoCastle:   /* pieces not automatically shuffled */
1130       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1131       case VariantLosers:     /* should work except for win condition,
1132                                  and doesn't know captures are mandatory */
1133       case VariantSuicide:    /* should work except for win condition,
1134                                  and doesn't know captures are mandatory */
1135       case VariantGiveaway:   /* should work except for win condition,
1136                                  and doesn't know captures are mandatory */
1137       case VariantTwoKings:   /* should work */
1138       case VariantAtomic:     /* should work except for win condition */
1139       case Variant3Check:     /* should work except for win condition */
1140       case VariantShatranj:   /* should work except for all win conditions */
1141       case VariantMakruk:     /* should work except for draw countdown */
1142       case VariantBerolina:   /* might work if TestLegality is off */
1143       case VariantCapaRandom: /* should work */
1144       case VariantJanus:      /* should work */
1145       case VariantSuper:      /* experimental */
1146       case VariantGreat:      /* experimental, requires legality testing to be off */
1147       case VariantSChess:     /* S-Chess, should work */
1148       case VariantGrand:      /* should work */
1149       case VariantSpartan:    /* should work */
1150         break;
1151       }
1152     }
1153
1154 }
1155
1156 int
1157 NextIntegerFromString (char ** str, long * value)
1158 {
1159     int result = -1;
1160     char * s = *str;
1161
1162     while( *s == ' ' || *s == '\t' ) {
1163         s++;
1164     }
1165
1166     *value = 0;
1167
1168     if( *s >= '0' && *s <= '9' ) {
1169         while( *s >= '0' && *s <= '9' ) {
1170             *value = *value * 10 + (*s - '0');
1171             s++;
1172         }
1173
1174         result = 0;
1175     }
1176
1177     *str = s;
1178
1179     return result;
1180 }
1181
1182 int
1183 NextTimeControlFromString (char ** str, long * value)
1184 {
1185     long temp;
1186     int result = NextIntegerFromString( str, &temp );
1187
1188     if( result == 0 ) {
1189         *value = temp * 60; /* Minutes */
1190         if( **str == ':' ) {
1191             (*str)++;
1192             result = NextIntegerFromString( str, &temp );
1193             *value += temp; /* Seconds */
1194         }
1195     }
1196
1197     return result;
1198 }
1199
1200 int
1201 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1202 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1203     int result = -1, type = 0; long temp, temp2;
1204
1205     if(**str != ':') return -1; // old params remain in force!
1206     (*str)++;
1207     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1208     if( NextIntegerFromString( str, &temp ) ) return -1;
1209     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1210
1211     if(**str != '/') {
1212         /* time only: incremental or sudden-death time control */
1213         if(**str == '+') { /* increment follows; read it */
1214             (*str)++;
1215             if(**str == '!') type = *(*str)++; // Bronstein TC
1216             if(result = NextIntegerFromString( str, &temp2)) return -1;
1217             *inc = temp2 * 1000;
1218             if(**str == '.') { // read fraction of increment
1219                 char *start = ++(*str);
1220                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1221                 temp2 *= 1000;
1222                 while(start++ < *str) temp2 /= 10;
1223                 *inc += temp2;
1224             }
1225         } else *inc = 0;
1226         *moves = 0; *tc = temp * 1000; *incType = type;
1227         return 0;
1228     }
1229
1230     (*str)++; /* classical time control */
1231     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1232
1233     if(result == 0) {
1234         *moves = temp;
1235         *tc    = temp2 * 1000;
1236         *inc   = 0;
1237         *incType = type;
1238     }
1239     return result;
1240 }
1241
1242 int
1243 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1244 {   /* [HGM] get time to add from the multi-session time-control string */
1245     int incType, moves=1; /* kludge to force reading of first session */
1246     long time, increment;
1247     char *s = tcString;
1248
1249     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1250     do {
1251         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1252         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1253         if(movenr == -1) return time;    /* last move before new session     */
1254         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1255         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1256         if(!moves) return increment;     /* current session is incremental   */
1257         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1258     } while(movenr >= -1);               /* try again for next session       */
1259
1260     return 0; // no new time quota on this move
1261 }
1262
1263 int
1264 ParseTimeControl (char *tc, float ti, int mps)
1265 {
1266   long tc1;
1267   long tc2;
1268   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1269   int min, sec=0;
1270
1271   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1272   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1273       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1274   if(ti > 0) {
1275
1276     if(mps)
1277       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1278     else 
1279       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1280   } else {
1281     if(mps)
1282       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1283     else 
1284       snprintf(buf, MSG_SIZ, ":%s", mytc);
1285   }
1286   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1287   
1288   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1289     return FALSE;
1290   }
1291
1292   if( *tc == '/' ) {
1293     /* Parse second time control */
1294     tc++;
1295
1296     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1297       return FALSE;
1298     }
1299
1300     if( tc2 == 0 ) {
1301       return FALSE;
1302     }
1303
1304     timeControl_2 = tc2 * 1000;
1305   }
1306   else {
1307     timeControl_2 = 0;
1308   }
1309
1310   if( tc1 == 0 ) {
1311     return FALSE;
1312   }
1313
1314   timeControl = tc1 * 1000;
1315
1316   if (ti >= 0) {
1317     timeIncrement = ti * 1000;  /* convert to ms */
1318     movesPerSession = 0;
1319   } else {
1320     timeIncrement = 0;
1321     movesPerSession = mps;
1322   }
1323   return TRUE;
1324 }
1325
1326 void
1327 InitBackEnd2 ()
1328 {
1329     if (appData.debugMode) {
1330         fprintf(debugFP, "%s\n", programVersion);
1331     }
1332     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1333
1334     set_cont_sequence(appData.wrapContSeq);
1335     if (appData.matchGames > 0) {
1336         appData.matchMode = TRUE;
1337     } else if (appData.matchMode) {
1338         appData.matchGames = 1;
1339     }
1340     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1341         appData.matchGames = appData.sameColorGames;
1342     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1343         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1344         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1345     }
1346     Reset(TRUE, FALSE);
1347     if (appData.noChessProgram || first.protocolVersion == 1) {
1348       InitBackEnd3();
1349     } else {
1350       /* kludge: allow timeout for initial "feature" commands */
1351       FreezeUI();
1352       DisplayMessage("", _("Starting chess program"));
1353       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1354     }
1355 }
1356
1357 int
1358 CalculateIndex (int index, int gameNr)
1359 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1360     int res;
1361     if(index > 0) return index; // fixed nmber
1362     if(index == 0) return 1;
1363     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1364     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1365     return res;
1366 }
1367
1368 int
1369 LoadGameOrPosition (int gameNr)
1370 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1371     if (*appData.loadGameFile != NULLCHAR) {
1372         if (!LoadGameFromFile(appData.loadGameFile,
1373                 CalculateIndex(appData.loadGameIndex, gameNr),
1374                               appData.loadGameFile, FALSE)) {
1375             DisplayFatalError(_("Bad game file"), 0, 1);
1376             return 0;
1377         }
1378     } else if (*appData.loadPositionFile != NULLCHAR) {
1379         if (!LoadPositionFromFile(appData.loadPositionFile,
1380                 CalculateIndex(appData.loadPositionIndex, gameNr),
1381                                   appData.loadPositionFile)) {
1382             DisplayFatalError(_("Bad position file"), 0, 1);
1383             return 0;
1384         }
1385     }
1386     return 1;
1387 }
1388
1389 void
1390 ReserveGame (int gameNr, char resChar)
1391 {
1392     FILE *tf = fopen(appData.tourneyFile, "r+");
1393     char *p, *q, c, buf[MSG_SIZ];
1394     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1395     safeStrCpy(buf, lastMsg, MSG_SIZ);
1396     DisplayMessage(_("Pick new game"), "");
1397     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1398     ParseArgsFromFile(tf);
1399     p = q = appData.results;
1400     if(appData.debugMode) {
1401       char *r = appData.participants;
1402       fprintf(debugFP, "results = '%s'\n", p);
1403       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1404       fprintf(debugFP, "\n");
1405     }
1406     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1407     nextGame = q - p;
1408     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1409     safeStrCpy(q, p, strlen(p) + 2);
1410     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1411     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1412     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1413         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1414         q[nextGame] = '*';
1415     }
1416     fseek(tf, -(strlen(p)+4), SEEK_END);
1417     c = fgetc(tf);
1418     if(c != '"') // depending on DOS or Unix line endings we can be one off
1419          fseek(tf, -(strlen(p)+2), SEEK_END);
1420     else fseek(tf, -(strlen(p)+3), SEEK_END);
1421     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1422     DisplayMessage(buf, "");
1423     free(p); appData.results = q;
1424     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1425        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1426       int round = appData.defaultMatchGames * appData.tourneyType;
1427       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1428          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1429         UnloadEngine(&first);  // next game belongs to other pairing;
1430         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1431     }
1432     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1433 }
1434
1435 void
1436 MatchEvent (int mode)
1437 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1438         int dummy;
1439         if(matchMode) { // already in match mode: switch it off
1440             abortMatch = TRUE;
1441             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1442             return;
1443         }
1444 //      if(gameMode != BeginningOfGame) {
1445 //          DisplayError(_("You can only start a match from the initial position."), 0);
1446 //          return;
1447 //      }
1448         abortMatch = FALSE;
1449         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1450         /* Set up machine vs. machine match */
1451         nextGame = 0;
1452         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1453         if(appData.tourneyFile[0]) {
1454             ReserveGame(-1, 0);
1455             if(nextGame > appData.matchGames) {
1456                 char buf[MSG_SIZ];
1457                 if(strchr(appData.results, '*') == NULL) {
1458                     FILE *f;
1459                     appData.tourneyCycles++;
1460                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1461                         fclose(f);
1462                         NextTourneyGame(-1, &dummy);
1463                         ReserveGame(-1, 0);
1464                         if(nextGame <= appData.matchGames) {
1465                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1466                             matchMode = mode;
1467                             ScheduleDelayedEvent(NextMatchGame, 10000);
1468                             return;
1469                         }
1470                     }
1471                 }
1472                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1473                 DisplayError(buf, 0);
1474                 appData.tourneyFile[0] = 0;
1475                 return;
1476             }
1477         } else
1478         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1479             DisplayFatalError(_("Can't have a match with no chess programs"),
1480                               0, 2);
1481             return;
1482         }
1483         matchMode = mode;
1484         matchGame = roundNr = 1;
1485         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1486         NextMatchGame();
1487 }
1488
1489 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1490
1491 void
1492 InitBackEnd3 P((void))
1493 {
1494     GameMode initialMode;
1495     char buf[MSG_SIZ];
1496     int err, len;
1497
1498     InitChessProgram(&first, startedFromSetupPosition);
1499
1500     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1501         free(programVersion);
1502         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1503         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1504         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1505     }
1506
1507     if (appData.icsActive) {
1508 #ifdef WIN32
1509         /* [DM] Make a console window if needed [HGM] merged ifs */
1510         ConsoleCreate();
1511 #endif
1512         err = establish();
1513         if (err != 0)
1514           {
1515             if (*appData.icsCommPort != NULLCHAR)
1516               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1517                              appData.icsCommPort);
1518             else
1519               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1520                         appData.icsHost, appData.icsPort);
1521
1522             if( (len >= MSG_SIZ) && appData.debugMode )
1523               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1524
1525             DisplayFatalError(buf, err, 1);
1526             return;
1527         }
1528         SetICSMode();
1529         telnetISR =
1530           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1531         fromUserISR =
1532           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1533         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1534             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1535     } else if (appData.noChessProgram) {
1536         SetNCPMode();
1537     } else {
1538         SetGNUMode();
1539     }
1540
1541     if (*appData.cmailGameName != NULLCHAR) {
1542         SetCmailMode();
1543         OpenLoopback(&cmailPR);
1544         cmailISR =
1545           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1546     }
1547
1548     ThawUI();
1549     DisplayMessage("", "");
1550     if (StrCaseCmp(appData.initialMode, "") == 0) {
1551       initialMode = BeginningOfGame;
1552       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1553         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1554         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1555         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1556         ModeHighlight();
1557       }
1558     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1559       initialMode = TwoMachinesPlay;
1560     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1561       initialMode = AnalyzeFile;
1562     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1563       initialMode = AnalyzeMode;
1564     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1565       initialMode = MachinePlaysWhite;
1566     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1567       initialMode = MachinePlaysBlack;
1568     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1569       initialMode = EditGame;
1570     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1571       initialMode = EditPosition;
1572     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1573       initialMode = Training;
1574     } else {
1575       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1576       if( (len >= MSG_SIZ) && appData.debugMode )
1577         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1578
1579       DisplayFatalError(buf, 0, 2);
1580       return;
1581     }
1582
1583     if (appData.matchMode) {
1584         if(appData.tourneyFile[0]) { // start tourney from command line
1585             FILE *f;
1586             if(f = fopen(appData.tourneyFile, "r")) {
1587                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1588                 fclose(f);
1589                 appData.clockMode = TRUE;
1590                 SetGNUMode();
1591             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1592         }
1593         MatchEvent(TRUE);
1594     } else if (*appData.cmailGameName != NULLCHAR) {
1595         /* Set up cmail mode */
1596         ReloadCmailMsgEvent(TRUE);
1597     } else {
1598         /* Set up other modes */
1599         if (initialMode == AnalyzeFile) {
1600           if (*appData.loadGameFile == NULLCHAR) {
1601             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1602             return;
1603           }
1604         }
1605         if (*appData.loadGameFile != NULLCHAR) {
1606             (void) LoadGameFromFile(appData.loadGameFile,
1607                                     appData.loadGameIndex,
1608                                     appData.loadGameFile, TRUE);
1609         } else if (*appData.loadPositionFile != NULLCHAR) {
1610             (void) LoadPositionFromFile(appData.loadPositionFile,
1611                                         appData.loadPositionIndex,
1612                                         appData.loadPositionFile);
1613             /* [HGM] try to make self-starting even after FEN load */
1614             /* to allow automatic setup of fairy variants with wtm */
1615             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1616                 gameMode = BeginningOfGame;
1617                 setboardSpoiledMachineBlack = 1;
1618             }
1619             /* [HGM] loadPos: make that every new game uses the setup */
1620             /* from file as long as we do not switch variant          */
1621             if(!blackPlaysFirst) {
1622                 startedFromPositionFile = TRUE;
1623                 CopyBoard(filePosition, boards[0]);
1624             }
1625         }
1626         if (initialMode == AnalyzeMode) {
1627           if (appData.noChessProgram) {
1628             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1629             return;
1630           }
1631           if (appData.icsActive) {
1632             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1633             return;
1634           }
1635           AnalyzeModeEvent();
1636         } else if (initialMode == AnalyzeFile) {
1637           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1638           ShowThinkingEvent();
1639           AnalyzeFileEvent();
1640           AnalysisPeriodicEvent(1);
1641         } else if (initialMode == MachinePlaysWhite) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           MachineWhiteEvent();
1653         } else if (initialMode == MachinePlaysBlack) {
1654           if (appData.noChessProgram) {
1655             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1656                               0, 2);
1657             return;
1658           }
1659           if (appData.icsActive) {
1660             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1661                               0, 2);
1662             return;
1663           }
1664           MachineBlackEvent();
1665         } else if (initialMode == TwoMachinesPlay) {
1666           if (appData.noChessProgram) {
1667             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1668                               0, 2);
1669             return;
1670           }
1671           if (appData.icsActive) {
1672             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1673                               0, 2);
1674             return;
1675           }
1676           TwoMachinesEvent();
1677         } else if (initialMode == EditGame) {
1678           EditGameEvent();
1679         } else if (initialMode == EditPosition) {
1680           EditPositionEvent();
1681         } else if (initialMode == Training) {
1682           if (*appData.loadGameFile == NULLCHAR) {
1683             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1684             return;
1685           }
1686           TrainingEvent();
1687         }
1688     }
1689 }
1690
1691 void
1692 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1693 {
1694     DisplayBook(current+1);
1695
1696     MoveHistorySet( movelist, first, last, current, pvInfoList );
1697
1698     EvalGraphSet( first, last, current, pvInfoList );
1699
1700     MakeEngineOutputTitle();
1701 }
1702
1703 /*
1704  * Establish will establish a contact to a remote host.port.
1705  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1706  *  used to talk to the host.
1707  * Returns 0 if okay, error code if not.
1708  */
1709 int
1710 establish ()
1711 {
1712     char buf[MSG_SIZ];
1713
1714     if (*appData.icsCommPort != NULLCHAR) {
1715         /* Talk to the host through a serial comm port */
1716         return OpenCommPort(appData.icsCommPort, &icsPR);
1717
1718     } else if (*appData.gateway != NULLCHAR) {
1719         if (*appData.remoteShell == NULLCHAR) {
1720             /* Use the rcmd protocol to run telnet program on a gateway host */
1721             snprintf(buf, sizeof(buf), "%s %s %s",
1722                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1723             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1724
1725         } else {
1726             /* Use the rsh program to run telnet program on a gateway host */
1727             if (*appData.remoteUser == NULLCHAR) {
1728                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1729                         appData.gateway, appData.telnetProgram,
1730                         appData.icsHost, appData.icsPort);
1731             } else {
1732                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1733                         appData.remoteShell, appData.gateway,
1734                         appData.remoteUser, appData.telnetProgram,
1735                         appData.icsHost, appData.icsPort);
1736             }
1737             return StartChildProcess(buf, "", &icsPR);
1738
1739         }
1740     } else if (appData.useTelnet) {
1741         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1742
1743     } else {
1744         /* TCP socket interface differs somewhat between
1745            Unix and NT; handle details in the front end.
1746            */
1747         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1748     }
1749 }
1750
1751 void
1752 EscapeExpand (char *p, char *q)
1753 {       // [HGM] initstring: routine to shape up string arguments
1754         while(*p++ = *q++) if(p[-1] == '\\')
1755             switch(*q++) {
1756                 case 'n': p[-1] = '\n'; break;
1757                 case 'r': p[-1] = '\r'; break;
1758                 case 't': p[-1] = '\t'; break;
1759                 case '\\': p[-1] = '\\'; break;
1760                 case 0: *p = 0; return;
1761                 default: p[-1] = q[-1]; break;
1762             }
1763 }
1764
1765 void
1766 show_bytes (FILE *fp, char *buf, int count)
1767 {
1768     while (count--) {
1769         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1770             fprintf(fp, "\\%03o", *buf & 0xff);
1771         } else {
1772             putc(*buf, fp);
1773         }
1774         buf++;
1775     }
1776     fflush(fp);
1777 }
1778
1779 /* Returns an errno value */
1780 int
1781 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1782 {
1783     char buf[8192], *p, *q, *buflim;
1784     int left, newcount, outcount;
1785
1786     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1787         *appData.gateway != NULLCHAR) {
1788         if (appData.debugMode) {
1789             fprintf(debugFP, ">ICS: ");
1790             show_bytes(debugFP, message, count);
1791             fprintf(debugFP, "\n");
1792         }
1793         return OutputToProcess(pr, message, count, outError);
1794     }
1795
1796     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1797     p = message;
1798     q = buf;
1799     left = count;
1800     newcount = 0;
1801     while (left) {
1802         if (q >= buflim) {
1803             if (appData.debugMode) {
1804                 fprintf(debugFP, ">ICS: ");
1805                 show_bytes(debugFP, buf, newcount);
1806                 fprintf(debugFP, "\n");
1807             }
1808             outcount = OutputToProcess(pr, buf, newcount, outError);
1809             if (outcount < newcount) return -1; /* to be sure */
1810             q = buf;
1811             newcount = 0;
1812         }
1813         if (*p == '\n') {
1814             *q++ = '\r';
1815             newcount++;
1816         } else if (((unsigned char) *p) == TN_IAC) {
1817             *q++ = (char) TN_IAC;
1818             newcount ++;
1819         }
1820         *q++ = *p++;
1821         newcount++;
1822         left--;
1823     }
1824     if (appData.debugMode) {
1825         fprintf(debugFP, ">ICS: ");
1826         show_bytes(debugFP, buf, newcount);
1827         fprintf(debugFP, "\n");
1828     }
1829     outcount = OutputToProcess(pr, buf, newcount, outError);
1830     if (outcount < newcount) return -1; /* to be sure */
1831     return count;
1832 }
1833
1834 void
1835 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1836 {
1837     int outError, outCount;
1838     static int gotEof = 0;
1839
1840     /* Pass data read from player on to ICS */
1841     if (count > 0) {
1842         gotEof = 0;
1843         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1844         if (outCount < count) {
1845             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846         }
1847     } else if (count < 0) {
1848         RemoveInputSource(isr);
1849         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1850     } else if (gotEof++ > 0) {
1851         RemoveInputSource(isr);
1852         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1853     }
1854 }
1855
1856 void
1857 KeepAlive ()
1858 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1859     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1860     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1861     SendToICS("date\n");
1862     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1863 }
1864
1865 /* added routine for printf style output to ics */
1866 void
1867 ics_printf (char *format, ...)
1868 {
1869     char buffer[MSG_SIZ];
1870     va_list args;
1871
1872     va_start(args, format);
1873     vsnprintf(buffer, sizeof(buffer), format, args);
1874     buffer[sizeof(buffer)-1] = '\0';
1875     SendToICS(buffer);
1876     va_end(args);
1877 }
1878
1879 void
1880 SendToICS (char *s)
1881 {
1882     int count, outCount, outError;
1883
1884     if (icsPR == NoProc) return;
1885
1886     count = strlen(s);
1887     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893 /* This is used for sending logon scripts to the ICS. Sending
1894    without a delay causes problems when using timestamp on ICC
1895    (at least on my machine). */
1896 void
1897 SendToICSDelayed (char *s, long msdelay)
1898 {
1899     int count, outCount, outError;
1900
1901     if (icsPR == NoProc) return;
1902
1903     count = strlen(s);
1904     if (appData.debugMode) {
1905         fprintf(debugFP, ">ICS: ");
1906         show_bytes(debugFP, s, count);
1907         fprintf(debugFP, "\n");
1908     }
1909     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1910                                       msdelay);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916
1917 /* Remove all highlighting escape sequences in s
1918    Also deletes any suffix starting with '('
1919    */
1920 char *
1921 StripHighlightAndTitle (char *s)
1922 {
1923     static char retbuf[MSG_SIZ];
1924     char *p = retbuf;
1925
1926     while (*s != NULLCHAR) {
1927         while (*s == '\033') {
1928             while (*s != NULLCHAR && !isalpha(*s)) s++;
1929             if (*s != NULLCHAR) s++;
1930         }
1931         while (*s != NULLCHAR && *s != '\033') {
1932             if (*s == '(' || *s == '[') {
1933                 *p = NULLCHAR;
1934                 return retbuf;
1935             }
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 /* Remove all highlighting escape sequences in s */
1944 char *
1945 StripHighlight (char *s)
1946 {
1947     static char retbuf[MSG_SIZ];
1948     char *p = retbuf;
1949
1950     while (*s != NULLCHAR) {
1951         while (*s == '\033') {
1952             while (*s != NULLCHAR && !isalpha(*s)) s++;
1953             if (*s != NULLCHAR) s++;
1954         }
1955         while (*s != NULLCHAR && *s != '\033') {
1956             *p++ = *s++;
1957         }
1958     }
1959     *p = NULLCHAR;
1960     return retbuf;
1961 }
1962
1963 char *variantNames[] = VARIANT_NAMES;
1964 char *
1965 VariantName (VariantClass v)
1966 {
1967     return variantNames[v];
1968 }
1969
1970
1971 /* Identify a variant from the strings the chess servers use or the
1972    PGN Variant tag names we use. */
1973 VariantClass
1974 StringToVariant (char *e)
1975 {
1976     char *p;
1977     int wnum = -1;
1978     VariantClass v = VariantNormal;
1979     int i, found = FALSE;
1980     char buf[MSG_SIZ];
1981     int len;
1982
1983     if (!e) return v;
1984
1985     /* [HGM] skip over optional board-size prefixes */
1986     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1987         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1988         while( *e++ != '_');
1989     }
1990
1991     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1992         v = VariantNormal;
1993         found = TRUE;
1994     } else
1995     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1996       if (StrCaseStr(e, variantNames[i])) {
1997         v = (VariantClass) i;
1998         found = TRUE;
1999         break;
2000       }
2001     }
2002
2003     if (!found) {
2004       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2005           || StrCaseStr(e, "wild/fr")
2006           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2007         v = VariantFischeRandom;
2008       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2009                  (i = 1, p = StrCaseStr(e, "w"))) {
2010         p += i;
2011         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2012         if (isdigit(*p)) {
2013           wnum = atoi(p);
2014         } else {
2015           wnum = -1;
2016         }
2017         switch (wnum) {
2018         case 0: /* FICS only, actually */
2019         case 1:
2020           /* Castling legal even if K starts on d-file */
2021           v = VariantWildCastle;
2022           break;
2023         case 2:
2024         case 3:
2025         case 4:
2026           /* Castling illegal even if K & R happen to start in
2027              normal positions. */
2028           v = VariantNoCastle;
2029           break;
2030         case 5:
2031         case 7:
2032         case 8:
2033         case 10:
2034         case 11:
2035         case 12:
2036         case 13:
2037         case 14:
2038         case 15:
2039         case 18:
2040         case 19:
2041           /* Castling legal iff K & R start in normal positions */
2042           v = VariantNormal;
2043           break;
2044         case 6:
2045         case 20:
2046         case 21:
2047           /* Special wilds for position setup; unclear what to do here */
2048           v = VariantLoadable;
2049           break;
2050         case 9:
2051           /* Bizarre ICC game */
2052           v = VariantTwoKings;
2053           break;
2054         case 16:
2055           v = VariantKriegspiel;
2056           break;
2057         case 17:
2058           v = VariantLosers;
2059           break;
2060         case 22:
2061           v = VariantFischeRandom;
2062           break;
2063         case 23:
2064           v = VariantCrazyhouse;
2065           break;
2066         case 24:
2067           v = VariantBughouse;
2068           break;
2069         case 25:
2070           v = Variant3Check;
2071           break;
2072         case 26:
2073           /* Not quite the same as FICS suicide! */
2074           v = VariantGiveaway;
2075           break;
2076         case 27:
2077           v = VariantAtomic;
2078           break;
2079         case 28:
2080           v = VariantShatranj;
2081           break;
2082
2083         /* Temporary names for future ICC types.  The name *will* change in
2084            the next xboard/WinBoard release after ICC defines it. */
2085         case 29:
2086           v = Variant29;
2087           break;
2088         case 30:
2089           v = Variant30;
2090           break;
2091         case 31:
2092           v = Variant31;
2093           break;
2094         case 32:
2095           v = Variant32;
2096           break;
2097         case 33:
2098           v = Variant33;
2099           break;
2100         case 34:
2101           v = Variant34;
2102           break;
2103         case 35:
2104           v = Variant35;
2105           break;
2106         case 36:
2107           v = Variant36;
2108           break;
2109         case 37:
2110           v = VariantShogi;
2111           break;
2112         case 38:
2113           v = VariantXiangqi;
2114           break;
2115         case 39:
2116           v = VariantCourier;
2117           break;
2118         case 40:
2119           v = VariantGothic;
2120           break;
2121         case 41:
2122           v = VariantCapablanca;
2123           break;
2124         case 42:
2125           v = VariantKnightmate;
2126           break;
2127         case 43:
2128           v = VariantFairy;
2129           break;
2130         case 44:
2131           v = VariantCylinder;
2132           break;
2133         case 45:
2134           v = VariantFalcon;
2135           break;
2136         case 46:
2137           v = VariantCapaRandom;
2138           break;
2139         case 47:
2140           v = VariantBerolina;
2141           break;
2142         case 48:
2143           v = VariantJanus;
2144           break;
2145         case 49:
2146           v = VariantSuper;
2147           break;
2148         case 50:
2149           v = VariantGreat;
2150           break;
2151         case -1:
2152           /* Found "wild" or "w" in the string but no number;
2153              must assume it's normal chess. */
2154           v = VariantNormal;
2155           break;
2156         default:
2157           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2158           if( (len >= MSG_SIZ) && appData.debugMode )
2159             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2160
2161           DisplayError(buf, 0);
2162           v = VariantUnknown;
2163           break;
2164         }
2165       }
2166     }
2167     if (appData.debugMode) {
2168       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2169               e, wnum, VariantName(v));
2170     }
2171     return v;
2172 }
2173
2174 static int leftover_start = 0, leftover_len = 0;
2175 char star_match[STAR_MATCH_N][MSG_SIZ];
2176
2177 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2178    advance *index beyond it, and set leftover_start to the new value of
2179    *index; else return FALSE.  If pattern contains the character '*', it
2180    matches any sequence of characters not containing '\r', '\n', or the
2181    character following the '*' (if any), and the matched sequence(s) are
2182    copied into star_match.
2183    */
2184 int
2185 looking_at ( char *buf, int *index, char *pattern)
2186 {
2187     char *bufp = &buf[*index], *patternp = pattern;
2188     int star_count = 0;
2189     char *matchp = star_match[0];
2190
2191     for (;;) {
2192         if (*patternp == NULLCHAR) {
2193             *index = leftover_start = bufp - buf;
2194             *matchp = NULLCHAR;
2195             return TRUE;
2196         }
2197         if (*bufp == NULLCHAR) return FALSE;
2198         if (*patternp == '*') {
2199             if (*bufp == *(patternp + 1)) {
2200                 *matchp = NULLCHAR;
2201                 matchp = star_match[++star_count];
2202                 patternp += 2;
2203                 bufp++;
2204                 continue;
2205             } else if (*bufp == '\n' || *bufp == '\r') {
2206                 patternp++;
2207                 if (*patternp == NULLCHAR)
2208                   continue;
2209                 else
2210                   return FALSE;
2211             } else {
2212                 *matchp++ = *bufp++;
2213                 continue;
2214             }
2215         }
2216         if (*patternp != *bufp) return FALSE;
2217         patternp++;
2218         bufp++;
2219     }
2220 }
2221
2222 void
2223 SendToPlayer (char *data, int length)
2224 {
2225     int error, outCount;
2226     outCount = OutputToProcess(NoProc, data, length, &error);
2227     if (outCount < length) {
2228         DisplayFatalError(_("Error writing to display"), error, 1);
2229     }
2230 }
2231
2232 void
2233 PackHolding (char packed[], char *holding)
2234 {
2235     char *p = holding;
2236     char *q = packed;
2237     int runlength = 0;
2238     int curr = 9999;
2239     do {
2240         if (*p == curr) {
2241             runlength++;
2242         } else {
2243             switch (runlength) {
2244               case 0:
2245                 break;
2246               case 1:
2247                 *q++ = curr;
2248                 break;
2249               case 2:
2250                 *q++ = curr;
2251                 *q++ = curr;
2252                 break;
2253               default:
2254                 sprintf(q, "%d", runlength);
2255                 while (*q) q++;
2256                 *q++ = curr;
2257                 break;
2258             }
2259             runlength = 1;
2260             curr = *p;
2261         }
2262     } while (*p++);
2263     *q = NULLCHAR;
2264 }
2265
2266 /* Telnet protocol requests from the front end */
2267 void
2268 TelnetRequest (unsigned char ddww, unsigned char option)
2269 {
2270     unsigned char msg[3];
2271     int outCount, outError;
2272
2273     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2274
2275     if (appData.debugMode) {
2276         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2277         switch (ddww) {
2278           case TN_DO:
2279             ddwwStr = "DO";
2280             break;
2281           case TN_DONT:
2282             ddwwStr = "DONT";
2283             break;
2284           case TN_WILL:
2285             ddwwStr = "WILL";
2286             break;
2287           case TN_WONT:
2288             ddwwStr = "WONT";
2289             break;
2290           default:
2291             ddwwStr = buf1;
2292             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2293             break;
2294         }
2295         switch (option) {
2296           case TN_ECHO:
2297             optionStr = "ECHO";
2298             break;
2299           default:
2300             optionStr = buf2;
2301             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2302             break;
2303         }
2304         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2305     }
2306     msg[0] = TN_IAC;
2307     msg[1] = ddww;
2308     msg[2] = option;
2309     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2310     if (outCount < 3) {
2311         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2312     }
2313 }
2314
2315 void
2316 DoEcho ()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DO, TN_ECHO);
2320 }
2321
2322 void
2323 DontEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DONT, TN_ECHO);
2327 }
2328
2329 void
2330 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2331 {
2332     /* put the holdings sent to us by the server on the board holdings area */
2333     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2334     char p;
2335     ChessSquare piece;
2336
2337     if(gameInfo.holdingsWidth < 2)  return;
2338     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2339         return; // prevent overwriting by pre-board holdings
2340
2341     if( (int)lowestPiece >= BlackPawn ) {
2342         holdingsColumn = 0;
2343         countsColumn = 1;
2344         holdingsStartRow = BOARD_HEIGHT-1;
2345         direction = -1;
2346     } else {
2347         holdingsColumn = BOARD_WIDTH-1;
2348         countsColumn = BOARD_WIDTH-2;
2349         holdingsStartRow = 0;
2350         direction = 1;
2351     }
2352
2353     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2354         board[i][holdingsColumn] = EmptySquare;
2355         board[i][countsColumn]   = (ChessSquare) 0;
2356     }
2357     while( (p=*holdings++) != NULLCHAR ) {
2358         piece = CharToPiece( ToUpper(p) );
2359         if(piece == EmptySquare) continue;
2360         /*j = (int) piece - (int) WhitePawn;*/
2361         j = PieceToNumber(piece);
2362         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2363         if(j < 0) continue;               /* should not happen */
2364         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2365         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2366         board[holdingsStartRow+j*direction][countsColumn]++;
2367     }
2368 }
2369
2370
2371 void
2372 VariantSwitch (Board board, VariantClass newVariant)
2373 {
2374    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2375    static Board oldBoard;
2376
2377    startedFromPositionFile = FALSE;
2378    if(gameInfo.variant == newVariant) return;
2379
2380    /* [HGM] This routine is called each time an assignment is made to
2381     * gameInfo.variant during a game, to make sure the board sizes
2382     * are set to match the new variant. If that means adding or deleting
2383     * holdings, we shift the playing board accordingly
2384     * This kludge is needed because in ICS observe mode, we get boards
2385     * of an ongoing game without knowing the variant, and learn about the
2386     * latter only later. This can be because of the move list we requested,
2387     * in which case the game history is refilled from the beginning anyway,
2388     * but also when receiving holdings of a crazyhouse game. In the latter
2389     * case we want to add those holdings to the already received position.
2390     */
2391
2392
2393    if (appData.debugMode) {
2394      fprintf(debugFP, "Switch board from %s to %s\n",
2395              VariantName(gameInfo.variant), VariantName(newVariant));
2396      setbuf(debugFP, NULL);
2397    }
2398    shuffleOpenings = 0;       /* [HGM] shuffle */
2399    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2400    switch(newVariant)
2401      {
2402      case VariantShogi:
2403        newWidth = 9;  newHeight = 9;
2404        gameInfo.holdingsSize = 7;
2405      case VariantBughouse:
2406      case VariantCrazyhouse:
2407        newHoldingsWidth = 2; break;
2408      case VariantGreat:
2409        newWidth = 10;
2410      case VariantSuper:
2411        newHoldingsWidth = 2;
2412        gameInfo.holdingsSize = 8;
2413        break;
2414      case VariantGothic:
2415      case VariantCapablanca:
2416      case VariantCapaRandom:
2417        newWidth = 10;
2418      default:
2419        newHoldingsWidth = gameInfo.holdingsSize = 0;
2420      };
2421
2422    if(newWidth  != gameInfo.boardWidth  ||
2423       newHeight != gameInfo.boardHeight ||
2424       newHoldingsWidth != gameInfo.holdingsWidth ) {
2425
2426      /* shift position to new playing area, if needed */
2427      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432        for(i=0; i<newHeight; i++) {
2433          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2434          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2435        }
2436      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2437        for(i=0; i<BOARD_HEIGHT; i++)
2438          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2439            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440              board[i][j];
2441      }
2442      gameInfo.boardWidth  = newWidth;
2443      gameInfo.boardHeight = newHeight;
2444      gameInfo.holdingsWidth = newHoldingsWidth;
2445      gameInfo.variant = newVariant;
2446      InitDrawingSizes(-2, 0);
2447    } else gameInfo.variant = newVariant;
2448    CopyBoard(oldBoard, board);   // remember correctly formatted board
2449      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2450    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2451 }
2452
2453 static int loggedOn = FALSE;
2454
2455 /*-- Game start info cache: --*/
2456 int gs_gamenum;
2457 char gs_kind[MSG_SIZ];
2458 static char player1Name[128] = "";
2459 static char player2Name[128] = "";
2460 static char cont_seq[] = "\n\\   ";
2461 static int player1Rating = -1;
2462 static int player2Rating = -1;
2463 /*----------------------------*/
2464
2465 ColorClass curColor = ColorNormal;
2466 int suppressKibitz = 0;
2467
2468 // [HGM] seekgraph
2469 Boolean soughtPending = FALSE;
2470 Boolean seekGraphUp;
2471 #define MAX_SEEK_ADS 200
2472 #define SQUARE 0x80
2473 char *seekAdList[MAX_SEEK_ADS];
2474 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2475 float tcList[MAX_SEEK_ADS];
2476 char colorList[MAX_SEEK_ADS];
2477 int nrOfSeekAds = 0;
2478 int minRating = 1010, maxRating = 2800;
2479 int hMargin = 10, vMargin = 20, h, w;
2480 extern int squareSize, lineGap;
2481
2482 void
2483 PlotSeekAd (int i)
2484 {
2485         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2486         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2487         if(r < minRating+100 && r >=0 ) r = minRating+100;
2488         if(r > maxRating) r = maxRating;
2489         if(tc < 1.) tc = 1.;
2490         if(tc > 95.) tc = 95.;
2491         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2492         y = ((double)r - minRating)/(maxRating - minRating)
2493             * (h-vMargin-squareSize/8-1) + vMargin;
2494         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2495         if(strstr(seekAdList[i], " u ")) color = 1;
2496         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2497            !strstr(seekAdList[i], "bullet") &&
2498            !strstr(seekAdList[i], "blitz") &&
2499            !strstr(seekAdList[i], "standard") ) color = 2;
2500         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2501         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2502 }
2503
2504 void
2505 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2506 {
2507         char buf[MSG_SIZ], *ext = "";
2508         VariantClass v = StringToVariant(type);
2509         if(strstr(type, "wild")) {
2510             ext = type + 4; // append wild number
2511             if(v == VariantFischeRandom) type = "chess960"; else
2512             if(v == VariantLoadable) type = "setup"; else
2513             type = VariantName(v);
2514         }
2515         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2516         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2517             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2518             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2519             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2520             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2521             seekNrList[nrOfSeekAds] = nr;
2522             zList[nrOfSeekAds] = 0;
2523             seekAdList[nrOfSeekAds++] = StrSave(buf);
2524             if(plot) PlotSeekAd(nrOfSeekAds-1);
2525         }
2526 }
2527
2528 void
2529 EraseSeekDot (int i)
2530 {
2531     int x = xList[i], y = yList[i], d=squareSize/4, k;
2532     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2533     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2534     // now replot every dot that overlapped
2535     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2536         int xx = xList[k], yy = yList[k];
2537         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2538             DrawSeekDot(xx, yy, colorList[k]);
2539     }
2540 }
2541
2542 void
2543 RemoveSeekAd (int nr)
2544 {
2545         int i;
2546         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2547             EraseSeekDot(i);
2548             if(seekAdList[i]) free(seekAdList[i]);
2549             seekAdList[i] = seekAdList[--nrOfSeekAds];
2550             seekNrList[i] = seekNrList[nrOfSeekAds];
2551             ratingList[i] = ratingList[nrOfSeekAds];
2552             colorList[i]  = colorList[nrOfSeekAds];
2553             tcList[i] = tcList[nrOfSeekAds];
2554             xList[i]  = xList[nrOfSeekAds];
2555             yList[i]  = yList[nrOfSeekAds];
2556             zList[i]  = zList[nrOfSeekAds];
2557             seekAdList[nrOfSeekAds] = NULL;
2558             break;
2559         }
2560 }
2561
2562 Boolean
2563 MatchSoughtLine (char *line)
2564 {
2565     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2566     int nr, base, inc, u=0; char dummy;
2567
2568     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2570        (u=1) &&
2571        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2573         // match: compact and save the line
2574         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2575         return TRUE;
2576     }
2577     return FALSE;
2578 }
2579
2580 int
2581 DrawSeekGraph ()
2582 {
2583     int i;
2584     if(!seekGraphUp) return FALSE;
2585     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2586     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2587
2588     DrawSeekBackground(0, 0, w, h);
2589     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2590     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2591     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2592         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2593         yy = h-1-yy;
2594         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2595         if(i%500 == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2599         }
2600     }
2601     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2602     for(i=1; i<100; i+=(i<10?1:5)) {
2603         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2604         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2605         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2606             char buf[MSG_SIZ];
2607             snprintf(buf, MSG_SIZ, "%d", i);
2608             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2609         }
2610     }
2611     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2612     return TRUE;
2613 }
2614
2615 int
2616 SeekGraphClick (ClickType click, int x, int y, int moving)
2617 {
2618     static int lastDown = 0, displayed = 0, lastSecond;
2619     if(y < 0) return FALSE;
2620     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2621         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2622         if(!seekGraphUp) return FALSE;
2623         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2624         DrawPosition(TRUE, NULL);
2625         return TRUE;
2626     }
2627     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2628         if(click == Release || moving) return FALSE;
2629         nrOfSeekAds = 0;
2630         soughtPending = TRUE;
2631         SendToICS(ics_prefix);
2632         SendToICS("sought\n"); // should this be "sought all"?
2633     } else { // issue challenge based on clicked ad
2634         int dist = 10000; int i, closest = 0, second = 0;
2635         for(i=0; i<nrOfSeekAds; i++) {
2636             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2637             if(d < dist) { dist = d; closest = i; }
2638             second += (d - zList[i] < 120); // count in-range ads
2639             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2640         }
2641         if(dist < 120) {
2642             char buf[MSG_SIZ];
2643             second = (second > 1);
2644             if(displayed != closest || second != lastSecond) {
2645                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2646                 lastSecond = second; displayed = closest;
2647             }
2648             if(click == Press) {
2649                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2650                 lastDown = closest;
2651                 return TRUE;
2652             } // on press 'hit', only show info
2653             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2654             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2655             SendToICS(ics_prefix);
2656             SendToICS(buf);
2657             return TRUE; // let incoming board of started game pop down the graph
2658         } else if(click == Release) { // release 'miss' is ignored
2659             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2660             if(moving == 2) { // right up-click
2661                 nrOfSeekAds = 0; // refresh graph
2662                 soughtPending = TRUE;
2663                 SendToICS(ics_prefix);
2664                 SendToICS("sought\n"); // should this be "sought all"?
2665             }
2666             return TRUE;
2667         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2668         // press miss or release hit 'pop down' seek graph
2669         seekGraphUp = FALSE;
2670         DrawPosition(TRUE, NULL);
2671     }
2672     return TRUE;
2673 }
2674
2675 void
2676 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2677 {
2678 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2679 #define STARTED_NONE 0
2680 #define STARTED_MOVES 1
2681 #define STARTED_BOARD 2
2682 #define STARTED_OBSERVE 3
2683 #define STARTED_HOLDINGS 4
2684 #define STARTED_CHATTER 5
2685 #define STARTED_COMMENT 6
2686 #define STARTED_MOVES_NOHIDE 7
2687
2688     static int started = STARTED_NONE;
2689     static char parse[20000];
2690     static int parse_pos = 0;
2691     static char buf[BUF_SIZE + 1];
2692     static int firstTime = TRUE, intfSet = FALSE;
2693     static ColorClass prevColor = ColorNormal;
2694     static int savingComment = FALSE;
2695     static int cmatch = 0; // continuation sequence match
2696     char *bp;
2697     char str[MSG_SIZ];
2698     int i, oldi;
2699     int buf_len;
2700     int next_out;
2701     int tkind;
2702     int backup;    /* [DM] For zippy color lines */
2703     char *p;
2704     char talker[MSG_SIZ]; // [HGM] chat
2705     int channel;
2706
2707     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2708
2709     if (appData.debugMode) {
2710       if (!error) {
2711         fprintf(debugFP, "<ICS: ");
2712         show_bytes(debugFP, data, count);
2713         fprintf(debugFP, "\n");
2714       }
2715     }
2716
2717     if (appData.debugMode) { int f = forwardMostMove;
2718         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2719                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2720                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2721     }
2722     if (count > 0) {
2723         /* If last read ended with a partial line that we couldn't parse,
2724            prepend it to the new read and try again. */
2725         if (leftover_len > 0) {
2726             for (i=0; i<leftover_len; i++)
2727               buf[i] = buf[leftover_start + i];
2728         }
2729
2730     /* copy new characters into the buffer */
2731     bp = buf + leftover_len;
2732     buf_len=leftover_len;
2733     for (i=0; i<count; i++)
2734     {
2735         // ignore these
2736         if (data[i] == '\r')
2737             continue;
2738
2739         // join lines split by ICS?
2740         if (!appData.noJoin)
2741         {
2742             /*
2743                 Joining just consists of finding matches against the
2744                 continuation sequence, and discarding that sequence
2745                 if found instead of copying it.  So, until a match
2746                 fails, there's nothing to do since it might be the
2747                 complete sequence, and thus, something we don't want
2748                 copied.
2749             */
2750             if (data[i] == cont_seq[cmatch])
2751             {
2752                 cmatch++;
2753                 if (cmatch == strlen(cont_seq))
2754                 {
2755                     cmatch = 0; // complete match.  just reset the counter
2756
2757                     /*
2758                         it's possible for the ICS to not include the space
2759                         at the end of the last word, making our [correct]
2760                         join operation fuse two separate words.  the server
2761                         does this when the space occurs at the width setting.
2762                     */
2763                     if (!buf_len || buf[buf_len-1] != ' ')
2764                     {
2765                         *bp++ = ' ';
2766                         buf_len++;
2767                     }
2768                 }
2769                 continue;
2770             }
2771             else if (cmatch)
2772             {
2773                 /*
2774                     match failed, so we have to copy what matched before
2775                     falling through and copying this character.  In reality,
2776                     this will only ever be just the newline character, but
2777                     it doesn't hurt to be precise.
2778                 */
2779                 strncpy(bp, cont_seq, cmatch);
2780                 bp += cmatch;
2781                 buf_len += cmatch;
2782                 cmatch = 0;
2783             }
2784         }
2785
2786         // copy this char
2787         *bp++ = data[i];
2788         buf_len++;
2789     }
2790
2791         buf[buf_len] = NULLCHAR;
2792 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2793         next_out = 0;
2794         leftover_start = 0;
2795
2796         i = 0;
2797         while (i < buf_len) {
2798             /* Deal with part of the TELNET option negotiation
2799                protocol.  We refuse to do anything beyond the
2800                defaults, except that we allow the WILL ECHO option,
2801                which ICS uses to turn off password echoing when we are
2802                directly connected to it.  We reject this option
2803                if localLineEditing mode is on (always on in xboard)
2804                and we are talking to port 23, which might be a real
2805                telnet server that will try to keep WILL ECHO on permanently.
2806              */
2807             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2808                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2809                 unsigned char option;
2810                 oldi = i;
2811                 switch ((unsigned char) buf[++i]) {
2812                   case TN_WILL:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<WILL ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       case TN_ECHO:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "ECHO ");
2819                         /* Reply only if this is a change, according
2820                            to the protocol rules. */
2821                         if (remoteEchoOption) break;
2822                         if (appData.localLineEditing &&
2823                             atoi(appData.icsPort) == TN_PORT) {
2824                             TelnetRequest(TN_DONT, TN_ECHO);
2825                         } else {
2826                             EchoOff();
2827                             TelnetRequest(TN_DO, TN_ECHO);
2828                             remoteEchoOption = TRUE;
2829                         }
2830                         break;
2831                       default:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "%d ", option);
2834                         /* Whatever this is, we don't want it. */
2835                         TelnetRequest(TN_DONT, option);
2836                         break;
2837                     }
2838                     break;
2839                   case TN_WONT:
2840                     if (appData.debugMode)
2841                       fprintf(debugFP, "\n<WONT ");
2842                     switch (option = (unsigned char) buf[++i]) {
2843                       case TN_ECHO:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "ECHO ");
2846                         /* Reply only if this is a change, according
2847                            to the protocol rules. */
2848                         if (!remoteEchoOption) break;
2849                         EchoOn();
2850                         TelnetRequest(TN_DONT, TN_ECHO);
2851                         remoteEchoOption = FALSE;
2852                         break;
2853                       default:
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", (unsigned char) option);
2856                         /* Whatever this is, it must already be turned
2857                            off, because we never agree to turn on
2858                            anything non-default, so according to the
2859                            protocol rules, we don't reply. */
2860                         break;
2861                     }
2862                     break;
2863                   case TN_DO:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<DO ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       default:
2868                         /* Whatever this is, we refuse to do it. */
2869                         if (appData.debugMode)
2870                           fprintf(debugFP, "%d ", option);
2871                         TelnetRequest(TN_WONT, option);
2872                         break;
2873                     }
2874                     break;
2875                   case TN_DONT:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<DONT ");
2878                     switch (option = (unsigned char) buf[++i]) {
2879                       default:
2880                         if (appData.debugMode)
2881                           fprintf(debugFP, "%d ", option);
2882                         /* Whatever this is, we are already not doing
2883                            it, because we never agree to do anything
2884                            non-default, so according to the protocol
2885                            rules, we don't reply. */
2886                         break;
2887                     }
2888                     break;
2889                   case TN_IAC:
2890                     if (appData.debugMode)
2891                       fprintf(debugFP, "\n<IAC ");
2892                     /* Doubled IAC; pass it through */
2893                     i--;
2894                     break;
2895                   default:
2896                     if (appData.debugMode)
2897                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2898                     /* Drop all other telnet commands on the floor */
2899                     break;
2900                 }
2901                 if (oldi > next_out)
2902                   SendToPlayer(&buf[next_out], oldi - next_out);
2903                 if (++i > next_out)
2904                   next_out = i;
2905                 continue;
2906             }
2907
2908             /* OK, this at least will *usually* work */
2909             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2910                 loggedOn = TRUE;
2911             }
2912
2913             if (loggedOn && !intfSet) {
2914                 if (ics_type == ICS_ICC) {
2915                   snprintf(str, MSG_SIZ,
2916                           "/set-quietly interface %s\n/set-quietly style 12\n",
2917                           programVersion);
2918                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2920                 } else if (ics_type == ICS_CHESSNET) {
2921                   snprintf(str, MSG_SIZ, "/style 12\n");
2922                 } else {
2923                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2924                   strcat(str, programVersion);
2925                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2928 #ifdef WIN32
2929                   strcat(str, "$iset nohighlight 1\n");
2930 #endif
2931                   strcat(str, "$iset lock 1\n$style 12\n");
2932                 }
2933                 SendToICS(str);
2934                 NotifyFrontendLogin();
2935                 intfSet = TRUE;
2936             }
2937
2938             if (started == STARTED_COMMENT) {
2939                 /* Accumulate characters in comment */
2940                 parse[parse_pos++] = buf[i];
2941                 if (buf[i] == '\n') {
2942                     parse[parse_pos] = NULLCHAR;
2943                     if(chattingPartner>=0) {
2944                         char mess[MSG_SIZ];
2945                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2946                         OutputChatMessage(chattingPartner, mess);
2947                         chattingPartner = -1;
2948                         next_out = i+1; // [HGM] suppress printing in ICS window
2949                     } else
2950                     if(!suppressKibitz) // [HGM] kibitz
2951                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2952                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2953                         int nrDigit = 0, nrAlph = 0, j;
2954                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2955                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2956                         parse[parse_pos] = NULLCHAR;
2957                         // try to be smart: if it does not look like search info, it should go to
2958                         // ICS interaction window after all, not to engine-output window.
2959                         for(j=0; j<parse_pos; j++) { // count letters and digits
2960                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2961                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2962                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2963                         }
2964                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2965                             int depth=0; float score;
2966                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2967                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2968                                 pvInfoList[forwardMostMove-1].depth = depth;
2969                                 pvInfoList[forwardMostMove-1].score = 100*score;
2970                             }
2971                             OutputKibitz(suppressKibitz, parse);
2972                         } else {
2973                             char tmp[MSG_SIZ];
2974                             if(gameMode == IcsObserving) // restore original ICS messages
2975                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2976                             else
2977                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2978                             SendToPlayer(tmp, strlen(tmp));
2979                         }
2980                         next_out = i+1; // [HGM] suppress printing in ICS window
2981                     }
2982                     started = STARTED_NONE;
2983                 } else {
2984                     /* Don't match patterns against characters in comment */
2985                     i++;
2986                     continue;
2987                 }
2988             }
2989             if (started == STARTED_CHATTER) {
2990                 if (buf[i] != '\n') {
2991                     /* Don't match patterns against characters in chatter */
2992                     i++;
2993                     continue;
2994                 }
2995                 started = STARTED_NONE;
2996                 if(suppressKibitz) next_out = i+1;
2997             }
2998
2999             /* Kludge to deal with rcmd protocol */
3000             if (firstTime && looking_at(buf, &i, "\001*")) {
3001                 DisplayFatalError(&buf[1], 0, 1);
3002                 continue;
3003             } else {
3004                 firstTime = FALSE;
3005             }
3006
3007             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3008                 ics_type = ICS_ICC;
3009                 ics_prefix = "/";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3015                 ics_type = ICS_FICS;
3016                 ics_prefix = "$";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3022                 ics_type = ICS_CHESSNET;
3023                 ics_prefix = "/";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028
3029             if (!loggedOn &&
3030                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3031                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3032                  looking_at(buf, &i, "will be \"*\""))) {
3033               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3034               continue;
3035             }
3036
3037             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3038               char buf[MSG_SIZ];
3039               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3040               DisplayIcsInteractionTitle(buf);
3041               have_set_title = TRUE;
3042             }
3043
3044             /* skip finger notes */
3045             if (started == STARTED_NONE &&
3046                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3047                  (buf[i] == '1' && buf[i+1] == '0')) &&
3048                 buf[i+2] == ':' && buf[i+3] == ' ') {
3049               started = STARTED_CHATTER;
3050               i += 3;
3051               continue;
3052             }
3053
3054             oldi = i;
3055             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3056             if(appData.seekGraph) {
3057                 if(soughtPending && MatchSoughtLine(buf+i)) {
3058                     i = strstr(buf+i, "rated") - buf;
3059                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3060                     next_out = leftover_start = i;
3061                     started = STARTED_CHATTER;
3062                     suppressKibitz = TRUE;
3063                     continue;
3064                 }
3065                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3066                         && looking_at(buf, &i, "* ads displayed")) {
3067                     soughtPending = FALSE;
3068                     seekGraphUp = TRUE;
3069                     DrawSeekGraph();
3070                     continue;
3071                 }
3072                 if(appData.autoRefresh) {
3073                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3074                         int s = (ics_type == ICS_ICC); // ICC format differs
3075                         if(seekGraphUp)
3076                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3077                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3078                         looking_at(buf, &i, "*% "); // eat prompt
3079                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3080                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3081                         next_out = i; // suppress
3082                         continue;
3083                     }
3084                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3085                         char *p = star_match[0];
3086                         while(*p) {
3087                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3088                             while(*p && *p++ != ' '); // next
3089                         }
3090                         looking_at(buf, &i, "*% "); // eat prompt
3091                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3092                         next_out = i;
3093                         continue;
3094                     }
3095                 }
3096             }
3097
3098             /* skip formula vars */
3099             if (started == STARTED_NONE &&
3100                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3101               started = STARTED_CHATTER;
3102               i += 3;
3103               continue;
3104             }
3105
3106             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3107             if (appData.autoKibitz && started == STARTED_NONE &&
3108                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3109                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3110                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3111                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3112                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3113                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3114                         suppressKibitz = TRUE;
3115                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3116                         next_out = i;
3117                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3118                                 && (gameMode == IcsPlayingWhite)) ||
3119                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3120                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3121                             started = STARTED_CHATTER; // own kibitz we simply discard
3122                         else {
3123                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3124                             parse_pos = 0; parse[0] = NULLCHAR;
3125                             savingComment = TRUE;
3126                             suppressKibitz = gameMode != IcsObserving ? 2 :
3127                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3128                         }
3129                         continue;
3130                 } else
3131                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3132                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3133                          && atoi(star_match[0])) {
3134                     // suppress the acknowledgements of our own autoKibitz
3135                     char *p;
3136                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3137                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3138                     SendToPlayer(star_match[0], strlen(star_match[0]));
3139                     if(looking_at(buf, &i, "*% ")) // eat prompt
3140                         suppressKibitz = FALSE;
3141                     next_out = i;
3142                     continue;
3143                 }
3144             } // [HGM] kibitz: end of patch
3145
3146             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3147
3148             // [HGM] chat: intercept tells by users for which we have an open chat window
3149             channel = -1;
3150             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3151                                            looking_at(buf, &i, "* whispers:") ||
3152                                            looking_at(buf, &i, "* kibitzes:") ||
3153                                            looking_at(buf, &i, "* shouts:") ||
3154                                            looking_at(buf, &i, "* c-shouts:") ||
3155                                            looking_at(buf, &i, "--> * ") ||
3156                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3157                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3159                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3160                 int p;
3161                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3162                 chattingPartner = -1;
3163
3164                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3165                 for(p=0; p<MAX_CHAT; p++) {
3166                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3167                     talker[0] = '['; strcat(talker, "] ");
3168                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3169                     chattingPartner = p; break;
3170                     }
3171                 } else
3172                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(!strcmp("kibitzes", chatPartner[p])) {
3175                         talker[0] = '['; strcat(talker, "] ");
3176                         chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("whispers", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3187                   if(buf[i-8] == '-' && buf[i-3] == 't')
3188                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3189                     if(!strcmp("c-shouts", chatPartner[p])) {
3190                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3191                         chattingPartner = p; break;
3192                     }
3193                   }
3194                   if(chattingPartner < 0)
3195                   for(p=0; p<MAX_CHAT; p++) {
3196                     if(!strcmp("shouts", chatPartner[p])) {
3197                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3198                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3199                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3200                         chattingPartner = p; break;
3201                     }
3202                   }
3203                 }
3204                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3205                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3206                     talker[0] = 0; Colorize(ColorTell, FALSE);
3207                     chattingPartner = p; break;
3208                 }
3209                 if(chattingPartner<0) i = oldi; else {
3210                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3211                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3212                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213                     started = STARTED_COMMENT;
3214                     parse_pos = 0; parse[0] = NULLCHAR;
3215                     savingComment = 3 + chattingPartner; // counts as TRUE
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219             } // [HGM] chat: end of patch
3220
3221           backup = i;
3222             if (appData.zippyTalk || appData.zippyPlay) {
3223                 /* [DM] Backup address for color zippy lines */
3224 #if ZIPPY
3225                if (loggedOn == TRUE)
3226                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3227                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3228 #endif
3229             } // [DM] 'else { ' deleted
3230                 if (
3231                     /* Regular tells and says */
3232                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3233                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3234                     looking_at(buf, &i, "* says: ") ||
3235                     /* Don't color "message" or "messages" output */
3236                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3237                     looking_at(buf, &i, "*. * at *:*: ") ||
3238                     looking_at(buf, &i, "--* (*:*): ") ||
3239                     /* Message notifications (same color as tells) */
3240                     looking_at(buf, &i, "* has left a message ") ||
3241                     looking_at(buf, &i, "* just sent you a message:\n") ||
3242                     /* Whispers and kibitzes */
3243                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3244                     looking_at(buf, &i, "* kibitzes: ") ||
3245                     /* Channel tells */
3246                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3247
3248                   if (tkind == 1 && strchr(star_match[0], ':')) {
3249                       /* Avoid "tells you:" spoofs in channels */
3250                      tkind = 3;
3251                   }
3252                   if (star_match[0][0] == NULLCHAR ||
3253                       strchr(star_match[0], ' ') ||
3254                       (tkind == 3 && strchr(star_match[1], ' '))) {
3255                     /* Reject bogus matches */
3256                     i = oldi;
3257                   } else {
3258                     if (appData.colorize) {
3259                       if (oldi > next_out) {
3260                         SendToPlayer(&buf[next_out], oldi - next_out);
3261                         next_out = oldi;
3262                       }
3263                       switch (tkind) {
3264                       case 1:
3265                         Colorize(ColorTell, FALSE);
3266                         curColor = ColorTell;
3267                         break;
3268                       case 2:
3269                         Colorize(ColorKibitz, FALSE);
3270                         curColor = ColorKibitz;
3271                         break;
3272                       case 3:
3273                         p = strrchr(star_match[1], '(');
3274                         if (p == NULL) {
3275                           p = star_match[1];
3276                         } else {
3277                           p++;
3278                         }
3279                         if (atoi(p) == 1) {
3280                           Colorize(ColorChannel1, FALSE);
3281                           curColor = ColorChannel1;
3282                         } else {
3283                           Colorize(ColorChannel, FALSE);
3284                           curColor = ColorChannel;
3285                         }
3286                         break;
3287                       case 5:
3288                         curColor = ColorNormal;
3289                         break;
3290                       }
3291                     }
3292                     if (started == STARTED_NONE && appData.autoComment &&
3293                         (gameMode == IcsObserving ||
3294                          gameMode == IcsPlayingWhite ||
3295                          gameMode == IcsPlayingBlack)) {
3296                       parse_pos = i - oldi;
3297                       memcpy(parse, &buf[oldi], parse_pos);
3298                       parse[parse_pos] = NULLCHAR;
3299                       started = STARTED_COMMENT;
3300                       savingComment = TRUE;
3301                     } else {
3302                       started = STARTED_CHATTER;
3303                       savingComment = FALSE;
3304                     }
3305                     loggedOn = TRUE;
3306                     continue;
3307                   }
3308                 }
3309
3310                 if (looking_at(buf, &i, "* s-shouts: ") ||
3311                     looking_at(buf, &i, "* c-shouts: ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSShout, FALSE);
3318                         curColor = ColorSShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at(buf, &i, "--->")) {
3326                     loggedOn = TRUE;
3327                     continue;
3328                 }
3329
3330                 if (looking_at(buf, &i, "* shouts: ") ||
3331                     looking_at(buf, &i, "--> ")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorShout, FALSE);
3338                         curColor = ColorShout;
3339                     }
3340                     loggedOn = TRUE;
3341                     started = STARTED_CHATTER;
3342                     continue;
3343                 }
3344
3345                 if (looking_at( buf, &i, "Challenge:")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorChallenge, FALSE);
3352                         curColor = ColorChallenge;
3353                     }
3354                     loggedOn = TRUE;
3355                     continue;
3356                 }
3357
3358                 if (looking_at(buf, &i, "* offers you") ||
3359                     looking_at(buf, &i, "* offers to be") ||
3360                     looking_at(buf, &i, "* would like to") ||
3361                     looking_at(buf, &i, "* requests to") ||
3362                     looking_at(buf, &i, "Your opponent offers") ||
3363                     looking_at(buf, &i, "Your opponent requests")) {
3364
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorRequest, FALSE);
3371                         curColor = ColorRequest;
3372                     }
3373                     continue;
3374                 }
3375
3376                 if (looking_at(buf, &i, "* (*) seeking")) {
3377                     if (appData.colorize) {
3378                         if (oldi > next_out) {
3379                             SendToPlayer(&buf[next_out], oldi - next_out);
3380                             next_out = oldi;
3381                         }
3382                         Colorize(ColorSeek, FALSE);
3383                         curColor = ColorSeek;
3384                     }
3385                     continue;
3386             }
3387
3388           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3389
3390             if (looking_at(buf, &i, "\\   ")) {
3391                 if (prevColor != ColorNormal) {
3392                     if (oldi > next_out) {
3393                         SendToPlayer(&buf[next_out], oldi - next_out);
3394                         next_out = oldi;
3395                     }
3396                     Colorize(prevColor, TRUE);
3397                     curColor = prevColor;
3398                 }
3399                 if (savingComment) {
3400                     parse_pos = i - oldi;
3401                     memcpy(parse, &buf[oldi], parse_pos);
3402                     parse[parse_pos] = NULLCHAR;
3403                     started = STARTED_COMMENT;
3404                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3405                         chattingPartner = savingComment - 3; // kludge to remember the box
3406                 } else {
3407                     started = STARTED_CHATTER;
3408                 }
3409                 continue;
3410             }
3411
3412             if (looking_at(buf, &i, "Black Strength :") ||
3413                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3414                 looking_at(buf, &i, "<10>") ||
3415                 looking_at(buf, &i, "#@#")) {
3416                 /* Wrong board style */
3417                 loggedOn = TRUE;
3418                 SendToICS(ics_prefix);
3419                 SendToICS("set style 12\n");
3420                 SendToICS(ics_prefix);
3421                 SendToICS("refresh\n");
3422                 continue;
3423             }
3424
3425             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3426                 ICSInitScript();
3427                 have_sent_ICS_logon = 1;
3428                 continue;
3429             }
3430
3431             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3432                 (looking_at(buf, &i, "\n<12> ") ||
3433                  looking_at(buf, &i, "<12> "))) {
3434                 loggedOn = TRUE;
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_BOARD;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3445                 looking_at(buf, &i, "<b1> ")) {
3446                 if (oldi > next_out) {
3447                     SendToPlayer(&buf[next_out], oldi - next_out);
3448                 }
3449                 next_out = i;
3450                 started = STARTED_HOLDINGS;
3451                 parse_pos = 0;
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3456                 loggedOn = TRUE;
3457                 /* Header for a move list -- first line */
3458
3459                 switch (ics_getting_history) {
3460                   case H_FALSE:
3461                     switch (gameMode) {
3462                       case IcsIdle:
3463                       case BeginningOfGame:
3464                         /* User typed "moves" or "oldmoves" while we
3465                            were idle.  Pretend we asked for these
3466                            moves and soak them up so user can step
3467                            through them and/or save them.
3468                            */
3469                         Reset(FALSE, TRUE);
3470                         gameMode = IcsObserving;
3471                         ModeHighlight();
3472                         ics_gamenum = -1;
3473                         ics_getting_history = H_GOT_UNREQ_HEADER;
3474                         break;
3475                       case EditGame: /*?*/
3476                       case EditPosition: /*?*/
3477                         /* Should above feature work in these modes too? */
3478                         /* For now it doesn't */
3479                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3480                         break;
3481                       default:
3482                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3483                         break;
3484                     }
3485                     break;
3486                   case H_REQUESTED:
3487                     /* Is this the right one? */
3488                     if (gameInfo.white && gameInfo.black &&
3489                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3490                         strcmp(gameInfo.black, star_match[2]) == 0) {
3491                         /* All is well */
3492                         ics_getting_history = H_GOT_REQ_HEADER;
3493                     }
3494                     break;
3495                   case H_GOT_REQ_HEADER:
3496                   case H_GOT_UNREQ_HEADER:
3497                   case H_GOT_UNWANTED_HEADER:
3498                   case H_GETTING_MOVES:
3499                     /* Should not happen */
3500                     DisplayError(_("Error gathering move list: two headers"), 0);
3501                     ics_getting_history = H_FALSE;
3502                     break;
3503                 }
3504
3505                 /* Save player ratings into gameInfo if needed */
3506                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3507                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3508                     (gameInfo.whiteRating == -1 ||
3509                      gameInfo.blackRating == -1)) {
3510
3511                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3512                     gameInfo.blackRating = string_to_rating(star_match[3]);
3513                     if (appData.debugMode)
3514                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3515                               gameInfo.whiteRating, gameInfo.blackRating);
3516                 }
3517                 continue;
3518             }
3519
3520             if (looking_at(buf, &i,
3521               "* * match, initial time: * minute*, increment: * second")) {
3522                 /* Header for a move list -- second line */
3523                 /* Initial board will follow if this is a wild game */
3524                 if (gameInfo.event != NULL) free(gameInfo.event);
3525                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3526                 gameInfo.event = StrSave(str);
3527                 /* [HGM] we switched variant. Translate boards if needed. */
3528                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3529                 continue;
3530             }
3531
3532             if (looking_at(buf, &i, "Move  ")) {
3533                 /* Beginning of a move list */
3534                 switch (ics_getting_history) {
3535                   case H_FALSE:
3536                     /* Normally should not happen */
3537                     /* Maybe user hit reset while we were parsing */
3538                     break;
3539                   case H_REQUESTED:
3540                     /* Happens if we are ignoring a move list that is not
3541                      * the one we just requested.  Common if the user
3542                      * tries to observe two games without turning off
3543                      * getMoveList */
3544                     break;
3545                   case H_GETTING_MOVES:
3546                     /* Should not happen */
3547                     DisplayError(_("Error gathering move list: nested"), 0);
3548                     ics_getting_history = H_FALSE;
3549                     break;
3550                   case H_GOT_REQ_HEADER:
3551                     ics_getting_history = H_GETTING_MOVES;
3552                     started = STARTED_MOVES;
3553                     parse_pos = 0;
3554                     if (oldi > next_out) {
3555                         SendToPlayer(&buf[next_out], oldi - next_out);
3556                     }
3557                     break;
3558                   case H_GOT_UNREQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES_NOHIDE;
3561                     parse_pos = 0;
3562                     break;
3563                   case H_GOT_UNWANTED_HEADER:
3564                     ics_getting_history = H_FALSE;
3565                     break;
3566                 }
3567                 continue;
3568             }
3569
3570             if (looking_at(buf, &i, "% ") ||
3571                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3572                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3573                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3574                     soughtPending = FALSE;
3575                     seekGraphUp = TRUE;
3576                     DrawSeekGraph();
3577                 }
3578                 if(suppressKibitz) next_out = i;
3579                 savingComment = FALSE;
3580                 suppressKibitz = 0;
3581                 switch (started) {
3582                   case STARTED_MOVES:
3583                   case STARTED_MOVES_NOHIDE:
3584                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3585                     parse[parse_pos + i - oldi] = NULLCHAR;
3586                     ParseGameHistory(parse);
3587 #if ZIPPY
3588                     if (appData.zippyPlay && first.initDone) {
3589                         FeedMovesToProgram(&first, forwardMostMove);
3590                         if (gameMode == IcsPlayingWhite) {
3591                             if (WhiteOnMove(forwardMostMove)) {
3592                                 if (first.sendTime) {
3593                                   if (first.useColors) {
3594                                     SendToProgram("black\n", &first);
3595                                   }
3596                                   SendTimeRemaining(&first, TRUE);
3597                                 }
3598                                 if (first.useColors) {
3599                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3600                                 }
3601                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3602                                 first.maybeThinking = TRUE;
3603                             } else {
3604                                 if (first.usePlayother) {
3605                                   if (first.sendTime) {
3606                                     SendTimeRemaining(&first, TRUE);
3607                                   }
3608                                   SendToProgram("playother\n", &first);
3609                                   firstMove = FALSE;
3610                                 } else {
3611                                   firstMove = TRUE;
3612                                 }
3613                             }
3614                         } else if (gameMode == IcsPlayingBlack) {
3615                             if (!WhiteOnMove(forwardMostMove)) {
3616                                 if (first.sendTime) {
3617                                   if (first.useColors) {
3618                                     SendToProgram("white\n", &first);
3619                                   }
3620                                   SendTimeRemaining(&first, FALSE);
3621                                 }
3622                                 if (first.useColors) {
3623                                   SendToProgram("black\n", &first);
3624                                 }
3625                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3626                                 first.maybeThinking = TRUE;
3627                             } else {
3628                                 if (first.usePlayother) {
3629                                   if (first.sendTime) {
3630                                     SendTimeRemaining(&first, FALSE);
3631                                   }
3632                                   SendToProgram("playother\n", &first);
3633                                   firstMove = FALSE;
3634                                 } else {
3635                                   firstMove = TRUE;
3636                                 }
3637                             }
3638                         }
3639                     }
3640 #endif
3641                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3642                         /* Moves came from oldmoves or moves command
3643                            while we weren't doing anything else.
3644                            */
3645                         currentMove = forwardMostMove;
3646                         ClearHighlights();/*!!could figure this out*/
3647                         flipView = appData.flipView;
3648                         DrawPosition(TRUE, boards[currentMove]);
3649                         DisplayBothClocks();
3650                         snprintf(str, MSG_SIZ, "%s %s %s",
3651                                 gameInfo.white, _("vs."),  gameInfo.black);
3652                         DisplayTitle(str);
3653                         gameMode = IcsIdle;
3654                     } else {
3655                         /* Moves were history of an active game */
3656                         if (gameInfo.resultDetails != NULL) {
3657                             free(gameInfo.resultDetails);
3658                             gameInfo.resultDetails = NULL;
3659                         }
3660                     }
3661                     HistorySet(parseList, backwardMostMove,
3662                                forwardMostMove, currentMove-1);
3663                     DisplayMove(currentMove - 1);
3664                     if (started == STARTED_MOVES) next_out = i;
3665                     started = STARTED_NONE;
3666                     ics_getting_history = H_FALSE;
3667                     break;
3668
3669                   case STARTED_OBSERVE:
3670                     started = STARTED_NONE;
3671                     SendToICS(ics_prefix);
3672                     SendToICS("refresh\n");
3673                     break;
3674
3675                   default:
3676                     break;
3677                 }
3678                 if(bookHit) { // [HGM] book: simulate book reply
3679                     static char bookMove[MSG_SIZ]; // a bit generous?
3680
3681                     programStats.nodes = programStats.depth = programStats.time =
3682                     programStats.score = programStats.got_only_move = 0;
3683                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3684
3685                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3686                     strcat(bookMove, bookHit);
3687                     HandleMachineMove(bookMove, &first);
3688                 }
3689                 continue;
3690             }
3691
3692             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3693                  started == STARTED_HOLDINGS ||
3694                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3695                 /* Accumulate characters in move list or board */
3696                 parse[parse_pos++] = buf[i];
3697             }
3698
3699             /* Start of game messages.  Mostly we detect start of game
3700                when the first board image arrives.  On some versions
3701                of the ICS, though, we need to do a "refresh" after starting
3702                to observe in order to get the current board right away. */
3703             if (looking_at(buf, &i, "Adding game * to observation list")) {
3704                 started = STARTED_OBSERVE;
3705                 continue;
3706             }
3707
3708             /* Handle auto-observe */
3709             if (appData.autoObserve &&
3710                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3711                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3712                 char *player;
3713                 /* Choose the player that was highlighted, if any. */
3714                 if (star_match[0][0] == '\033' ||
3715                     star_match[1][0] != '\033') {
3716                     player = star_match[0];
3717                 } else {
3718                     player = star_match[2];
3719                 }
3720                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3721                         ics_prefix, StripHighlightAndTitle(player));
3722                 SendToICS(str);
3723
3724                 /* Save ratings from notify string */
3725                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3726                 player1Rating = string_to_rating(star_match[1]);
3727                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3728                 player2Rating = string_to_rating(star_match[3]);
3729
3730                 if (appData.debugMode)
3731                   fprintf(debugFP,
3732                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3733                           player1Name, player1Rating,
3734                           player2Name, player2Rating);
3735
3736                 continue;
3737             }
3738
3739             /* Deal with automatic examine mode after a game,
3740                and with IcsObserving -> IcsExamining transition */
3741             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3742                 looking_at(buf, &i, "has made you an examiner of game *")) {
3743
3744                 int gamenum = atoi(star_match[0]);
3745                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3746                     gamenum == ics_gamenum) {
3747                     /* We were already playing or observing this game;
3748                        no need to refetch history */
3749                     gameMode = IcsExamining;
3750                     if (pausing) {
3751                         pauseExamForwardMostMove = forwardMostMove;
3752                     } else if (currentMove < forwardMostMove) {
3753                         ForwardInner(forwardMostMove);
3754                     }
3755                 } else {
3756                     /* I don't think this case really can happen */
3757                     SendToICS(ics_prefix);
3758                     SendToICS("refresh\n");
3759                 }
3760                 continue;
3761             }
3762
3763             /* Error messages */
3764 //          if (ics_user_moved) {
3765             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3766                 if (looking_at(buf, &i, "Illegal move") ||
3767                     looking_at(buf, &i, "Not a legal move") ||
3768                     looking_at(buf, &i, "Your king is in check") ||
3769                     looking_at(buf, &i, "It isn't your turn") ||
3770                     looking_at(buf, &i, "It is not your move")) {
3771                     /* Illegal move */
3772                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3773                         currentMove = forwardMostMove-1;
3774                         DisplayMove(currentMove - 1); /* before DMError */
3775                         DrawPosition(FALSE, boards[currentMove]);
3776                         SwitchClocks(forwardMostMove-1); // [HGM] race
3777                         DisplayBothClocks();
3778                     }
3779                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3780                     ics_user_moved = 0;
3781                     continue;
3782                 }
3783             }
3784
3785             if (looking_at(buf, &i, "still have time") ||
3786                 looking_at(buf, &i, "not out of time") ||
3787                 looking_at(buf, &i, "either player is out of time") ||
3788                 looking_at(buf, &i, "has timeseal; checking")) {
3789                 /* We must have called his flag a little too soon */
3790                 whiteFlag = blackFlag = FALSE;
3791                 continue;
3792             }
3793
3794             if (looking_at(buf, &i, "added * seconds to") ||
3795                 looking_at(buf, &i, "seconds were added to")) {
3796                 /* Update the clocks */
3797                 SendToICS(ics_prefix);
3798                 SendToICS("refresh\n");
3799                 continue;
3800             }
3801
3802             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3803                 ics_clock_paused = TRUE;
3804                 StopClocks();
3805                 continue;
3806             }
3807
3808             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3809                 ics_clock_paused = FALSE;
3810                 StartClocks();
3811                 continue;
3812             }
3813
3814             /* Grab player ratings from the Creating: message.
3815                Note we have to check for the special case when
3816                the ICS inserts things like [white] or [black]. */
3817             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3818                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3819                 /* star_matches:
3820                    0    player 1 name (not necessarily white)
3821                    1    player 1 rating
3822                    2    empty, white, or black (IGNORED)
3823                    3    player 2 name (not necessarily black)
3824                    4    player 2 rating
3825
3826                    The names/ratings are sorted out when the game
3827                    actually starts (below).
3828                 */
3829                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3830                 player1Rating = string_to_rating(star_match[1]);
3831                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3832                 player2Rating = string_to_rating(star_match[4]);
3833
3834                 if (appData.debugMode)
3835                   fprintf(debugFP,
3836                           "Ratings from 'Creating:' %s %d, %s %d\n",
3837                           player1Name, player1Rating,
3838                           player2Name, player2Rating);
3839
3840                 continue;
3841             }
3842
3843             /* Improved generic start/end-of-game messages */
3844             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3845                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3846                 /* If tkind == 0: */
3847                 /* star_match[0] is the game number */
3848                 /*           [1] is the white player's name */
3849                 /*           [2] is the black player's name */
3850                 /* For end-of-game: */
3851                 /*           [3] is the reason for the game end */
3852                 /*           [4] is a PGN end game-token, preceded by " " */
3853                 /* For start-of-game: */
3854                 /*           [3] begins with "Creating" or "Continuing" */
3855                 /*           [4] is " *" or empty (don't care). */
3856                 int gamenum = atoi(star_match[0]);
3857                 char *whitename, *blackname, *why, *endtoken;
3858                 ChessMove endtype = EndOfFile;
3859
3860                 if (tkind == 0) {
3861                   whitename = star_match[1];
3862                   blackname = star_match[2];
3863                   why = star_match[3];
3864                   endtoken = star_match[4];
3865                 } else {
3866                   whitename = star_match[1];
3867                   blackname = star_match[3];
3868                   why = star_match[5];
3869                   endtoken = star_match[6];
3870                 }
3871
3872                 /* Game start messages */
3873                 if (strncmp(why, "Creating ", 9) == 0 ||
3874                     strncmp(why, "Continuing ", 11) == 0) {
3875                     gs_gamenum = gamenum;
3876                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3877                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3878                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3879 #if ZIPPY
3880                     if (appData.zippyPlay) {
3881                         ZippyGameStart(whitename, blackname);
3882                     }
3883 #endif /*ZIPPY*/
3884                     partnerBoardValid = FALSE; // [HGM] bughouse
3885                     continue;
3886                 }
3887
3888                 /* Game end messages */
3889                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3890                     ics_gamenum != gamenum) {
3891                     continue;
3892                 }
3893                 while (endtoken[0] == ' ') endtoken++;
3894                 switch (endtoken[0]) {
3895                   case '*':
3896                   default:
3897                     endtype = GameUnfinished;
3898                     break;
3899                   case '0':
3900                     endtype = BlackWins;
3901                     break;
3902                   case '1':
3903                     if (endtoken[1] == '/')
3904                       endtype = GameIsDrawn;
3905                     else
3906                       endtype = WhiteWins;
3907                     break;
3908                 }
3909                 GameEnds(endtype, why, GE_ICS);
3910 #if ZIPPY
3911                 if (appData.zippyPlay && first.initDone) {
3912                     ZippyGameEnd(endtype, why);
3913                     if (first.pr == NoProc) {
3914                       /* Start the next process early so that we'll
3915                          be ready for the next challenge */
3916                       StartChessProgram(&first);
3917                     }
3918                     /* Send "new" early, in case this command takes
3919                        a long time to finish, so that we'll be ready
3920                        for the next challenge. */
3921                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3922                     Reset(TRUE, TRUE);
3923                 }
3924 #endif /*ZIPPY*/
3925                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "Removing game * from observation") ||
3930                 looking_at(buf, &i, "no longer observing game *") ||
3931                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3932                 if (gameMode == IcsObserving &&
3933                     atoi(star_match[0]) == ics_gamenum)
3934                   {
3935                       /* icsEngineAnalyze */
3936                       if (appData.icsEngineAnalyze) {
3937                             ExitAnalyzeMode();
3938                             ModeHighlight();
3939                       }
3940                       StopClocks();
3941                       gameMode = IcsIdle;
3942                       ics_gamenum = -1;
3943                       ics_user_moved = FALSE;
3944                   }
3945                 continue;
3946             }
3947
3948             if (looking_at(buf, &i, "no longer examining game *")) {
3949                 if (gameMode == IcsExamining &&
3950                     atoi(star_match[0]) == ics_gamenum)
3951                   {
3952                       gameMode = IcsIdle;
3953                       ics_gamenum = -1;
3954                       ics_user_moved = FALSE;
3955                   }
3956                 continue;
3957             }
3958
3959             /* Advance leftover_start past any newlines we find,
3960                so only partial lines can get reparsed */
3961             if (looking_at(buf, &i, "\n")) {
3962                 prevColor = curColor;
3963                 if (curColor != ColorNormal) {
3964                     if (oldi > next_out) {
3965                         SendToPlayer(&buf[next_out], oldi - next_out);
3966                         next_out = oldi;
3967                     }
3968                     Colorize(ColorNormal, FALSE);
3969                     curColor = ColorNormal;
3970                 }
3971                 if (started == STARTED_BOARD) {
3972                     started = STARTED_NONE;
3973                     parse[parse_pos] = NULLCHAR;
3974                     ParseBoard12(parse);
3975                     ics_user_moved = 0;
3976
3977                     /* Send premove here */
3978                     if (appData.premove) {
3979                       char str[MSG_SIZ];
3980                       if (currentMove == 0 &&
3981                           gameMode == IcsPlayingWhite &&
3982                           appData.premoveWhite) {
3983                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3984                         if (appData.debugMode)
3985                           fprintf(debugFP, "Sending premove:\n");
3986                         SendToICS(str);
3987                       } else if (currentMove == 1 &&
3988                                  gameMode == IcsPlayingBlack &&
3989                                  appData.premoveBlack) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (gotPremove) {
3995                         gotPremove = 0;
3996                         ClearPremoveHighlights();
3997                         if (appData.debugMode)
3998                           fprintf(debugFP, "Sending premove:\n");
3999                           UserMoveEvent(premoveFromX, premoveFromY,
4000                                         premoveToX, premoveToY,
4001                                         premovePromoChar);
4002                       }
4003                     }
4004
4005                     /* Usually suppress following prompt */
4006                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4007                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4008                         if (looking_at(buf, &i, "*% ")) {
4009                             savingComment = FALSE;
4010                             suppressKibitz = 0;
4011                         }
4012                     }
4013                     next_out = i;
4014                 } else if (started == STARTED_HOLDINGS) {
4015                     int gamenum;
4016                     char new_piece[MSG_SIZ];
4017                     started = STARTED_NONE;
4018                     parse[parse_pos] = NULLCHAR;
4019                     if (appData.debugMode)
4020                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4021                                                         parse, currentMove);
4022                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4023                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4024                         if (gameInfo.variant == VariantNormal) {
4025                           /* [HGM] We seem to switch variant during a game!
4026                            * Presumably no holdings were displayed, so we have
4027                            * to move the position two files to the right to
4028                            * create room for them!
4029                            */
4030                           VariantClass newVariant;
4031                           switch(gameInfo.boardWidth) { // base guess on board width
4032                                 case 9:  newVariant = VariantShogi; break;
4033                                 case 10: newVariant = VariantGreat; break;
4034                                 default: newVariant = VariantCrazyhouse; break;
4035                           }
4036                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4037                           /* Get a move list just to see the header, which
4038                              will tell us whether this is really bug or zh */
4039                           if (ics_getting_history == H_FALSE) {
4040                             ics_getting_history = H_REQUESTED;
4041                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4042                             SendToICS(str);
4043                           }
4044                         }
4045                         new_piece[0] = NULLCHAR;
4046                         sscanf(parse, "game %d white [%s black [%s <- %s",
4047                                &gamenum, white_holding, black_holding,
4048                                new_piece);
4049                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4050                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4051                         /* [HGM] copy holdings to board holdings area */
4052                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4053                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4054                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4055 #if ZIPPY
4056                         if (appData.zippyPlay && first.initDone) {
4057                             ZippyHoldings(white_holding, black_holding,
4058                                           new_piece);
4059                         }
4060 #endif /*ZIPPY*/
4061                         if (tinyLayout || smallLayout) {
4062                             char wh[16], bh[16];
4063                             PackHolding(wh, white_holding);
4064                             PackHolding(bh, black_holding);
4065                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4066                                     gameInfo.white, gameInfo.black);
4067                         } else {
4068                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4069                                     gameInfo.white, white_holding, _("vs."),
4070                                     gameInfo.black, black_holding);
4071                         }
4072                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4073                         DrawPosition(FALSE, boards[currentMove]);
4074                         DisplayTitle(str);
4075                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4076                         sscanf(parse, "game %d white [%s black [%s <- %s",
4077                                &gamenum, white_holding, black_holding,
4078                                new_piece);
4079                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4080                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4081                         /* [HGM] copy holdings to partner-board holdings area */
4082                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4083                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4084                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4085                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4086                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4087                       }
4088                     }
4089                     /* Suppress following prompt */
4090                     if (looking_at(buf, &i, "*% ")) {
4091                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4092                         savingComment = FALSE;
4093                         suppressKibitz = 0;
4094                     }
4095                     next_out = i;
4096                 }
4097                 continue;
4098             }
4099
4100             i++;                /* skip unparsed character and loop back */
4101         }
4102
4103         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4104 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4105 //          SendToPlayer(&buf[next_out], i - next_out);
4106             started != STARTED_HOLDINGS && leftover_start > next_out) {
4107             SendToPlayer(&buf[next_out], leftover_start - next_out);
4108             next_out = i;
4109         }
4110
4111         leftover_len = buf_len - leftover_start;
4112         /* if buffer ends with something we couldn't parse,
4113            reparse it after appending the next read */
4114
4115     } else if (count == 0) {
4116         RemoveInputSource(isr);
4117         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4118     } else {
4119         DisplayFatalError(_("Error reading from ICS"), error, 1);
4120     }
4121 }
4122
4123
4124 /* Board style 12 looks like this:
4125
4126    <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
4127
4128  * The "<12> " is stripped before it gets to this routine.  The two
4129  * trailing 0's (flip state and clock ticking) are later addition, and
4130  * some chess servers may not have them, or may have only the first.
4131  * Additional trailing fields may be added in the future.
4132  */
4133
4134 #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"
4135
4136 #define RELATION_OBSERVING_PLAYED    0
4137 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4138 #define RELATION_PLAYING_MYMOVE      1
4139 #define RELATION_PLAYING_NOTMYMOVE  -1
4140 #define RELATION_EXAMINING           2
4141 #define RELATION_ISOLATED_BOARD     -3
4142 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4143
4144 void
4145 ParseBoard12 (char *string)
4146 {
4147     GameMode newGameMode;
4148     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4149     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4150     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4151     char to_play, board_chars[200];
4152     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4153     char black[32], white[32];
4154     Board board;
4155     int prevMove = currentMove;
4156     int ticking = 2;
4157     ChessMove moveType;
4158     int fromX, fromY, toX, toY;
4159     char promoChar;
4160     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4161     char *bookHit = NULL; // [HGM] book
4162     Boolean weird = FALSE, reqFlag = FALSE;
4163
4164     fromX = fromY = toX = toY = -1;
4165
4166     newGame = FALSE;
4167
4168     if (appData.debugMode)
4169       fprintf(debugFP, _("Parsing board: %s\n"), string);
4170
4171     move_str[0] = NULLCHAR;
4172     elapsed_time[0] = NULLCHAR;
4173     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4174         int  i = 0, j;
4175         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4176             if(string[i] == ' ') { ranks++; files = 0; }
4177             else files++;
4178             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4179             i++;
4180         }
4181         for(j = 0; j <i; j++) board_chars[j] = string[j];
4182         board_chars[i] = '\0';
4183         string += i + 1;
4184     }
4185     n = sscanf(string, PATTERN, &to_play, &double_push,
4186                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4187                &gamenum, white, black, &relation, &basetime, &increment,
4188                &white_stren, &black_stren, &white_time, &black_time,
4189                &moveNum, str, elapsed_time, move_str, &ics_flip,
4190                &ticking);
4191
4192     if (n < 21) {
4193         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4194         DisplayError(str, 0);
4195         return;
4196     }
4197
4198     /* Convert the move number to internal form */
4199     moveNum = (moveNum - 1) * 2;
4200     if (to_play == 'B') moveNum++;
4201     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4202       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4203                         0, 1);
4204       return;
4205     }
4206
4207     switch (relation) {
4208       case RELATION_OBSERVING_PLAYED:
4209       case RELATION_OBSERVING_STATIC:
4210         if (gamenum == -1) {
4211             /* Old ICC buglet */
4212             relation = RELATION_OBSERVING_STATIC;
4213         }
4214         newGameMode = IcsObserving;
4215         break;
4216       case RELATION_PLAYING_MYMOVE:
4217       case RELATION_PLAYING_NOTMYMOVE:
4218         newGameMode =
4219           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4220             IcsPlayingWhite : IcsPlayingBlack;
4221         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4222         break;
4223       case RELATION_EXAMINING:
4224         newGameMode = IcsExamining;
4225         break;
4226       case RELATION_ISOLATED_BOARD:
4227       default:
4228         /* Just display this board.  If user was doing something else,
4229            we will forget about it until the next board comes. */
4230         newGameMode = IcsIdle;
4231         break;
4232       case RELATION_STARTING_POSITION:
4233         newGameMode = gameMode;
4234         break;
4235     }
4236
4237     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4238          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4239       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4240       char *toSqr;
4241       for (k = 0; k < ranks; k++) {
4242         for (j = 0; j < files; j++)
4243           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4244         if(gameInfo.holdingsWidth > 1) {
4245              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4246              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4247         }
4248       }
4249       CopyBoard(partnerBoard, board);
4250       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4251         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4252         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4253       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4254       if(toSqr = strchr(str, '-')) {
4255         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4256         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4257       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4258       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4259       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4260       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4261       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4262       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4263                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4264       DisplayMessage(partnerStatus, "");
4265         partnerBoardValid = TRUE;
4266       return;
4267     }
4268
4269     /* Modify behavior for initial board display on move listing
4270        of wild games.
4271        */
4272     switch (ics_getting_history) {
4273       case H_FALSE:
4274       case H_REQUESTED:
4275         break;
4276       case H_GOT_REQ_HEADER:
4277       case H_GOT_UNREQ_HEADER:
4278         /* This is the initial position of the current game */
4279         gamenum = ics_gamenum;
4280         moveNum = 0;            /* old ICS bug workaround */
4281         if (to_play == 'B') {
4282           startedFromSetupPosition = TRUE;
4283           blackPlaysFirst = TRUE;
4284           moveNum = 1;
4285           if (forwardMostMove == 0) forwardMostMove = 1;
4286           if (backwardMostMove == 0) backwardMostMove = 1;
4287           if (currentMove == 0) currentMove = 1;
4288         }
4289         newGameMode = gameMode;
4290         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4291         break;
4292       case H_GOT_UNWANTED_HEADER:
4293         /* This is an initial board that we don't want */
4294         return;
4295       case H_GETTING_MOVES:
4296         /* Should not happen */
4297         DisplayError(_("Error gathering move list: extra board"), 0);
4298         ics_getting_history = H_FALSE;
4299         return;
4300     }
4301
4302    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4303                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4304      /* [HGM] We seem to have switched variant unexpectedly
4305       * Try to guess new variant from board size
4306       */
4307           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4308           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4309           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4310           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4311           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4312           if(!weird) newVariant = VariantNormal;
4313           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4314           /* Get a move list just to see the header, which
4315              will tell us whether this is really bug or zh */
4316           if (ics_getting_history == H_FALSE) {
4317             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4318             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4319             SendToICS(str);
4320           }
4321     }
4322
4323     /* Take action if this is the first board of a new game, or of a
4324        different game than is currently being displayed.  */
4325     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4326         relation == RELATION_ISOLATED_BOARD) {
4327
4328         /* Forget the old game and get the history (if any) of the new one */
4329         if (gameMode != BeginningOfGame) {
4330           Reset(TRUE, TRUE);
4331         }
4332         newGame = TRUE;
4333         if (appData.autoRaiseBoard) BoardToTop();
4334         prevMove = -3;
4335         if (gamenum == -1) {
4336             newGameMode = IcsIdle;
4337         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4338                    appData.getMoveList && !reqFlag) {
4339             /* Need to get game history */
4340             ics_getting_history = H_REQUESTED;
4341             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4342             SendToICS(str);
4343         }
4344
4345         /* Initially flip the board to have black on the bottom if playing
4346            black or if the ICS flip flag is set, but let the user change
4347            it with the Flip View button. */
4348         flipView = appData.autoFlipView ?
4349           (newGameMode == IcsPlayingBlack) || ics_flip :
4350           appData.flipView;
4351
4352         /* Done with values from previous mode; copy in new ones */
4353         gameMode = newGameMode;
4354         ModeHighlight();
4355         ics_gamenum = gamenum;
4356         if (gamenum == gs_gamenum) {
4357             int klen = strlen(gs_kind);
4358             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4359             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4360             gameInfo.event = StrSave(str);
4361         } else {
4362             gameInfo.event = StrSave("ICS game");
4363         }
4364         gameInfo.site = StrSave(appData.icsHost);
4365         gameInfo.date = PGNDate();
4366         gameInfo.round = StrSave("-");
4367         gameInfo.white = StrSave(white);
4368         gameInfo.black = StrSave(black);
4369         timeControl = basetime * 60 * 1000;
4370         timeControl_2 = 0;
4371         timeIncrement = increment * 1000;
4372         movesPerSession = 0;
4373         gameInfo.timeControl = TimeControlTagValue();
4374         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4377     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4378     setbuf(debugFP, NULL);
4379   }
4380
4381         gameInfo.outOfBook = NULL;
4382
4383         /* Do we have the ratings? */
4384         if (strcmp(player1Name, white) == 0 &&
4385             strcmp(player2Name, black) == 0) {
4386             if (appData.debugMode)
4387               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4388                       player1Rating, player2Rating);
4389             gameInfo.whiteRating = player1Rating;
4390             gameInfo.blackRating = player2Rating;
4391         } else if (strcmp(player2Name, white) == 0 &&
4392                    strcmp(player1Name, black) == 0) {
4393             if (appData.debugMode)
4394               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4395                       player2Rating, player1Rating);
4396             gameInfo.whiteRating = player2Rating;
4397             gameInfo.blackRating = player1Rating;
4398         }
4399         player1Name[0] = player2Name[0] = NULLCHAR;
4400
4401         /* Silence shouts if requested */
4402         if (appData.quietPlay &&
4403             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4404             SendToICS(ics_prefix);
4405             SendToICS("set shout 0\n");
4406         }
4407     }
4408
4409     /* Deal with midgame name changes */
4410     if (!newGame) {
4411         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4412             if (gameInfo.white) free(gameInfo.white);
4413             gameInfo.white = StrSave(white);
4414         }
4415         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4416             if (gameInfo.black) free(gameInfo.black);
4417             gameInfo.black = StrSave(black);
4418         }
4419     }
4420
4421     /* Throw away game result if anything actually changes in examine mode */
4422     if (gameMode == IcsExamining && !newGame) {
4423         gameInfo.result = GameUnfinished;
4424         if (gameInfo.resultDetails != NULL) {
4425             free(gameInfo.resultDetails);
4426             gameInfo.resultDetails = NULL;
4427         }
4428     }
4429
4430     /* In pausing && IcsExamining mode, we ignore boards coming
4431        in if they are in a different variation than we are. */
4432     if (pauseExamInvalid) return;
4433     if (pausing && gameMode == IcsExamining) {
4434         if (moveNum <= pauseExamForwardMostMove) {
4435             pauseExamInvalid = TRUE;
4436             forwardMostMove = pauseExamForwardMostMove;
4437             return;
4438         }
4439     }
4440
4441   if (appData.debugMode) {
4442     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4443   }
4444     /* Parse the board */
4445     for (k = 0; k < ranks; k++) {
4446       for (j = 0; j < files; j++)
4447         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4448       if(gameInfo.holdingsWidth > 1) {
4449            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4450            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4451       }
4452     }
4453     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4454       board[5][BOARD_RGHT+1] = WhiteAngel;
4455       board[6][BOARD_RGHT+1] = WhiteMarshall;
4456       board[1][0] = BlackMarshall;
4457       board[2][0] = BlackAngel;
4458       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4459     }
4460     CopyBoard(boards[moveNum], board);
4461     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4462     if (moveNum == 0) {
4463         startedFromSetupPosition =
4464           !CompareBoards(board, initialPosition);
4465         if(startedFromSetupPosition)
4466             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4467     }
4468
4469     /* [HGM] Set castling rights. Take the outermost Rooks,
4470        to make it also work for FRC opening positions. Note that board12
4471        is really defective for later FRC positions, as it has no way to
4472        indicate which Rook can castle if they are on the same side of King.
4473        For the initial position we grant rights to the outermost Rooks,
4474        and remember thos rights, and we then copy them on positions
4475        later in an FRC game. This means WB might not recognize castlings with
4476        Rooks that have moved back to their original position as illegal,
4477        but in ICS mode that is not its job anyway.
4478     */
4479     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4480     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4481
4482         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4483             if(board[0][i] == WhiteRook) j = i;
4484         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4485         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4486             if(board[0][i] == WhiteRook) j = i;
4487         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4488         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4489             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4490         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4491         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4492             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4493         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4494
4495         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4496         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4497         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4498             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4499         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4500             if(board[BOARD_HEIGHT-1][k] == bKing)
4501                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4502         if(gameInfo.variant == VariantTwoKings) {
4503             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4504             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4505             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4506         }
4507     } else { int r;
4508         r = boards[moveNum][CASTLING][0] = initialRights[0];
4509         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4510         r = boards[moveNum][CASTLING][1] = initialRights[1];
4511         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4512         r = boards[moveNum][CASTLING][3] = initialRights[3];
4513         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4514         r = boards[moveNum][CASTLING][4] = initialRights[4];
4515         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4516         /* wildcastle kludge: always assume King has rights */
4517         r = boards[moveNum][CASTLING][2] = initialRights[2];
4518         r = boards[moveNum][CASTLING][5] = initialRights[5];
4519     }
4520     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4521     boards[moveNum][EP_STATUS] = EP_NONE;
4522     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4523     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4524     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4525
4526
4527     if (ics_getting_history == H_GOT_REQ_HEADER ||
4528         ics_getting_history == H_GOT_UNREQ_HEADER) {
4529         /* This was an initial position from a move list, not
4530            the current position */
4531         return;
4532     }
4533
4534     /* Update currentMove and known move number limits */
4535     newMove = newGame || moveNum > forwardMostMove;
4536
4537     if (newGame) {
4538         forwardMostMove = backwardMostMove = currentMove = moveNum;
4539         if (gameMode == IcsExamining && moveNum == 0) {
4540           /* Workaround for ICS limitation: we are not told the wild
4541              type when starting to examine a game.  But if we ask for
4542              the move list, the move list header will tell us */
4543             ics_getting_history = H_REQUESTED;
4544             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4545             SendToICS(str);
4546         }
4547     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4548                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4549 #if ZIPPY
4550         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4551         /* [HGM] applied this also to an engine that is silently watching        */
4552         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4553             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4554             gameInfo.variant == currentlyInitializedVariant) {
4555           takeback = forwardMostMove - moveNum;
4556           for (i = 0; i < takeback; i++) {
4557             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4558             SendToProgram("undo\n", &first);
4559           }
4560         }
4561 #endif
4562
4563         forwardMostMove = moveNum;
4564         if (!pausing || currentMove > forwardMostMove)
4565           currentMove = forwardMostMove;
4566     } else {
4567         /* New part of history that is not contiguous with old part */
4568         if (pausing && gameMode == IcsExamining) {
4569             pauseExamInvalid = TRUE;
4570             forwardMostMove = pauseExamForwardMostMove;
4571             return;
4572         }
4573         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4574 #if ZIPPY
4575             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4576                 // [HGM] when we will receive the move list we now request, it will be
4577                 // fed to the engine from the first move on. So if the engine is not
4578                 // in the initial position now, bring it there.
4579                 InitChessProgram(&first, 0);
4580             }
4581 #endif
4582             ics_getting_history = H_REQUESTED;
4583             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4584             SendToICS(str);
4585         }
4586         forwardMostMove = backwardMostMove = currentMove = moveNum;
4587     }
4588
4589     /* Update the clocks */
4590     if (strchr(elapsed_time, '.')) {
4591       /* Time is in ms */
4592       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4593       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4594     } else {
4595       /* Time is in seconds */
4596       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4597       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4598     }
4599
4600
4601 #if ZIPPY
4602     if (appData.zippyPlay && newGame &&
4603         gameMode != IcsObserving && gameMode != IcsIdle &&
4604         gameMode != IcsExamining)
4605       ZippyFirstBoard(moveNum, basetime, increment);
4606 #endif
4607
4608     /* Put the move on the move list, first converting
4609        to canonical algebraic form. */
4610     if (moveNum > 0) {
4611   if (appData.debugMode) {
4612     if (appData.debugMode) { int f = forwardMostMove;
4613         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4614                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4615                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4616     }
4617     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4618     fprintf(debugFP, "moveNum = %d\n", moveNum);
4619     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4620     setbuf(debugFP, NULL);
4621   }
4622         if (moveNum <= backwardMostMove) {
4623             /* We don't know what the board looked like before
4624                this move.  Punt. */
4625           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4626             strcat(parseList[moveNum - 1], " ");
4627             strcat(parseList[moveNum - 1], elapsed_time);
4628             moveList[moveNum - 1][0] = NULLCHAR;
4629         } else if (strcmp(move_str, "none") == 0) {
4630             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4631             /* Again, we don't know what the board looked like;
4632                this is really the start of the game. */
4633             parseList[moveNum - 1][0] = NULLCHAR;
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             backwardMostMove = moveNum;
4636             startedFromSetupPosition = TRUE;
4637             fromX = fromY = toX = toY = -1;
4638         } else {
4639           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4640           //                 So we parse the long-algebraic move string in stead of the SAN move
4641           int valid; char buf[MSG_SIZ], *prom;
4642
4643           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4644                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4645           // str looks something like "Q/a1-a2"; kill the slash
4646           if(str[1] == '/')
4647             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4648           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4649           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4650                 strcat(buf, prom); // long move lacks promo specification!
4651           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4652                 if(appData.debugMode)
4653                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4654                 safeStrCpy(move_str, buf, MSG_SIZ);
4655           }
4656           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4657                                 &fromX, &fromY, &toX, &toY, &promoChar)
4658                || ParseOneMove(buf, moveNum - 1, &moveType,
4659                                 &fromX, &fromY, &toX, &toY, &promoChar);
4660           // end of long SAN patch
4661           if (valid) {
4662             (void) CoordsToAlgebraic(boards[moveNum - 1],
4663                                      PosFlags(moveNum - 1),
4664                                      fromY, fromX, toY, toX, promoChar,
4665                                      parseList[moveNum-1]);
4666             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4667               case MT_NONE:
4668               case MT_STALEMATE:
4669               default:
4670                 break;
4671               case MT_CHECK:
4672                 if(gameInfo.variant != VariantShogi)
4673                     strcat(parseList[moveNum - 1], "+");
4674                 break;
4675               case MT_CHECKMATE:
4676               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4677                 strcat(parseList[moveNum - 1], "#");
4678                 break;
4679             }
4680             strcat(parseList[moveNum - 1], " ");
4681             strcat(parseList[moveNum - 1], elapsed_time);
4682             /* currentMoveString is set as a side-effect of ParseOneMove */
4683             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4684             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4685             strcat(moveList[moveNum - 1], "\n");
4686
4687             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4688                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4689               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4690                 ChessSquare old, new = boards[moveNum][k][j];
4691                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4692                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4693                   if(old == new) continue;
4694                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4695                   else if(new == WhiteWazir || new == BlackWazir) {
4696                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4697                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4698                       else boards[moveNum][k][j] = old; // preserve type of Gold
4699                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4700                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4701               }
4702           } else {
4703             /* Move from ICS was illegal!?  Punt. */
4704             if (appData.debugMode) {
4705               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4706               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4707             }
4708             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4709             strcat(parseList[moveNum - 1], " ");
4710             strcat(parseList[moveNum - 1], elapsed_time);
4711             moveList[moveNum - 1][0] = NULLCHAR;
4712             fromX = fromY = toX = toY = -1;
4713           }
4714         }
4715   if (appData.debugMode) {
4716     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4717     setbuf(debugFP, NULL);
4718   }
4719
4720 #if ZIPPY
4721         /* Send move to chess program (BEFORE animating it). */
4722         if (appData.zippyPlay && !newGame && newMove &&
4723            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4724
4725             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4726                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4727                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4729                             move_str);
4730                     DisplayError(str, 0);
4731                 } else {
4732                     if (first.sendTime) {
4733                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4734                     }
4735                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4736                     if (firstMove && !bookHit) {
4737                         firstMove = FALSE;
4738                         if (first.useColors) {
4739                           SendToProgram(gameMode == IcsPlayingWhite ?
4740                                         "white\ngo\n" :
4741                                         "black\ngo\n", &first);
4742                         } else {
4743                           SendToProgram("go\n", &first);
4744                         }
4745                         first.maybeThinking = TRUE;
4746                     }
4747                 }
4748             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4749               if (moveList[moveNum - 1][0] == NULLCHAR) {
4750                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4751                 DisplayError(str, 0);
4752               } else {
4753                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4754                 SendMoveToProgram(moveNum - 1, &first);
4755               }
4756             }
4757         }
4758 #endif
4759     }
4760
4761     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4762         /* If move comes from a remote source, animate it.  If it
4763            isn't remote, it will have already been animated. */
4764         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4765             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4766         }
4767         if (!pausing && appData.highlightLastMove) {
4768             SetHighlights(fromX, fromY, toX, toY);
4769         }
4770     }
4771
4772     /* Start the clocks */
4773     whiteFlag = blackFlag = FALSE;
4774     appData.clockMode = !(basetime == 0 && increment == 0);
4775     if (ticking == 0) {
4776       ics_clock_paused = TRUE;
4777       StopClocks();
4778     } else if (ticking == 1) {
4779       ics_clock_paused = FALSE;
4780     }
4781     if (gameMode == IcsIdle ||
4782         relation == RELATION_OBSERVING_STATIC ||
4783         relation == RELATION_EXAMINING ||
4784         ics_clock_paused)
4785       DisplayBothClocks();
4786     else
4787       StartClocks();
4788
4789     /* Display opponents and material strengths */
4790     if (gameInfo.variant != VariantBughouse &&
4791         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4792         if (tinyLayout || smallLayout) {
4793             if(gameInfo.variant == VariantNormal)
4794               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4795                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4796                     basetime, increment);
4797             else
4798               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4799                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4800                     basetime, increment, (int) gameInfo.variant);
4801         } else {
4802             if(gameInfo.variant == VariantNormal)
4803               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4804                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4805                     basetime, increment);
4806             else
4807               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4808                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4809                     basetime, increment, VariantName(gameInfo.variant));
4810         }
4811         DisplayTitle(str);
4812   if (appData.debugMode) {
4813     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4814   }
4815     }
4816
4817
4818     /* Display the board */
4819     if (!pausing && !appData.noGUI) {
4820
4821       if (appData.premove)
4822           if (!gotPremove ||
4823              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4824              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4825               ClearPremoveHighlights();
4826
4827       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4828         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4829       DrawPosition(j, boards[currentMove]);
4830
4831       DisplayMove(moveNum - 1);
4832       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4833             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4834               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4835         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4836       }
4837     }
4838
4839     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4840 #if ZIPPY
4841     if(bookHit) { // [HGM] book: simulate book reply
4842         static char bookMove[MSG_SIZ]; // a bit generous?
4843
4844         programStats.nodes = programStats.depth = programStats.time =
4845         programStats.score = programStats.got_only_move = 0;
4846         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4847
4848         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4849         strcat(bookMove, bookHit);
4850         HandleMachineMove(bookMove, &first);
4851     }
4852 #endif
4853 }
4854
4855 void
4856 GetMoveListEvent ()
4857 {
4858     char buf[MSG_SIZ];
4859     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4860         ics_getting_history = H_REQUESTED;
4861         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4862         SendToICS(buf);
4863     }
4864 }
4865
4866 void
4867 AnalysisPeriodicEvent (int force)
4868 {
4869     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4870          && !force) || !appData.periodicUpdates)
4871       return;
4872
4873     /* Send . command to Crafty to collect stats */
4874     SendToProgram(".\n", &first);
4875
4876     /* Don't send another until we get a response (this makes
4877        us stop sending to old Crafty's which don't understand
4878        the "." command (sending illegal cmds resets node count & time,
4879        which looks bad)) */
4880     programStats.ok_to_send = 0;
4881 }
4882
4883 void
4884 ics_update_width (int new_width)
4885 {
4886         ics_printf("set width %d\n", new_width);
4887 }
4888
4889 void
4890 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4891 {
4892     char buf[MSG_SIZ];
4893
4894     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4895         // null move in variant where engine does not understand it (for analysis purposes)
4896         SendBoard(cps, moveNum + 1); // send position after move in stead.
4897         return;
4898     }
4899     if (cps->useUsermove) {
4900       SendToProgram("usermove ", cps);
4901     }
4902     if (cps->useSAN) {
4903       char *space;
4904       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4905         int len = space - parseList[moveNum];
4906         memcpy(buf, parseList[moveNum], len);
4907         buf[len++] = '\n';
4908         buf[len] = NULLCHAR;
4909       } else {
4910         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4911       }
4912       SendToProgram(buf, cps);
4913     } else {
4914       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4915         AlphaRank(moveList[moveNum], 4);
4916         SendToProgram(moveList[moveNum], cps);
4917         AlphaRank(moveList[moveNum], 4); // and back
4918       } else
4919       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4920        * the engine. It would be nice to have a better way to identify castle
4921        * moves here. */
4922       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4923                                                                          && cps->useOOCastle) {
4924         int fromX = moveList[moveNum][0] - AAA;
4925         int fromY = moveList[moveNum][1] - ONE;
4926         int toX = moveList[moveNum][2] - AAA;
4927         int toY = moveList[moveNum][3] - ONE;
4928         if((boards[moveNum][fromY][fromX] == WhiteKing
4929             && boards[moveNum][toY][toX] == WhiteRook)
4930            || (boards[moveNum][fromY][fromX] == BlackKing
4931                && boards[moveNum][toY][toX] == BlackRook)) {
4932           if(toX > fromX) SendToProgram("O-O\n", cps);
4933           else SendToProgram("O-O-O\n", cps);
4934         }
4935         else SendToProgram(moveList[moveNum], cps);
4936       } else
4937       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4938         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4939           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4940           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4941                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4942         } else
4943           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4944                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4945         SendToProgram(buf, cps);
4946       }
4947       else SendToProgram(moveList[moveNum], cps);
4948       /* End of additions by Tord */
4949     }
4950
4951     /* [HGM] setting up the opening has brought engine in force mode! */
4952     /*       Send 'go' if we are in a mode where machine should play. */
4953     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4954         (gameMode == TwoMachinesPlay   ||
4955 #if ZIPPY
4956          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4957 #endif
4958          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4959         SendToProgram("go\n", cps);
4960   if (appData.debugMode) {
4961     fprintf(debugFP, "(extra)\n");
4962   }
4963     }
4964     setboardSpoiledMachineBlack = 0;
4965 }
4966
4967 void
4968 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4969 {
4970     char user_move[MSG_SIZ];
4971     char suffix[4];
4972
4973     if(gameInfo.variant == VariantSChess && promoChar) {
4974         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4975         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4976     } else suffix[0] = NULLCHAR;
4977
4978     switch (moveType) {
4979       default:
4980         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4981                 (int)moveType, fromX, fromY, toX, toY);
4982         DisplayError(user_move + strlen("say "), 0);
4983         break;
4984       case WhiteKingSideCastle:
4985       case BlackKingSideCastle:
4986       case WhiteQueenSideCastleWild:
4987       case BlackQueenSideCastleWild:
4988       /* PUSH Fabien */
4989       case WhiteHSideCastleFR:
4990       case BlackHSideCastleFR:
4991       /* POP Fabien */
4992         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4993         break;
4994       case WhiteQueenSideCastle:
4995       case BlackQueenSideCastle:
4996       case WhiteKingSideCastleWild:
4997       case BlackKingSideCastleWild:
4998       /* PUSH Fabien */
4999       case WhiteASideCastleFR:
5000       case BlackASideCastleFR:
5001       /* POP Fabien */
5002         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5003         break;
5004       case WhiteNonPromotion:
5005       case BlackNonPromotion:
5006         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5007         break;
5008       case WhitePromotion:
5009       case BlackPromotion:
5010         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5011           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5013                 PieceToChar(WhiteFerz));
5014         else if(gameInfo.variant == VariantGreat)
5015           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5016                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5017                 PieceToChar(WhiteMan));
5018         else
5019           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5021                 promoChar);
5022         break;
5023       case WhiteDrop:
5024       case BlackDrop:
5025       drop:
5026         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5027                  ToUpper(PieceToChar((ChessSquare) fromX)),
5028                  AAA + toX, ONE + toY);
5029         break;
5030       case IllegalMove:  /* could be a variant we don't quite understand */
5031         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5032       case NormalMove:
5033       case WhiteCapturesEnPassant:
5034       case BlackCapturesEnPassant:
5035         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5036                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5037         break;
5038     }
5039     SendToICS(user_move);
5040     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5041         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5042 }
5043
5044 void
5045 UploadGameEvent ()
5046 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5047     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5048     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5049     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5050       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5051       return;
5052     }
5053     if(gameMode != IcsExamining) { // is this ever not the case?
5054         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5055
5056         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5057           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5058         } else { // on FICS we must first go to general examine mode
5059           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5060         }
5061         if(gameInfo.variant != VariantNormal) {
5062             // try figure out wild number, as xboard names are not always valid on ICS
5063             for(i=1; i<=36; i++) {
5064               snprintf(buf, MSG_SIZ, "wild/%d", i);
5065                 if(StringToVariant(buf) == gameInfo.variant) break;
5066             }
5067             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5068             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5069             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5070         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5071         SendToICS(ics_prefix);
5072         SendToICS(buf);
5073         if(startedFromSetupPosition || backwardMostMove != 0) {
5074           fen = PositionToFEN(backwardMostMove, NULL);
5075           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5076             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5077             SendToICS(buf);
5078           } else { // FICS: everything has to set by separate bsetup commands
5079             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5080             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5081             SendToICS(buf);
5082             if(!WhiteOnMove(backwardMostMove)) {
5083                 SendToICS("bsetup tomove black\n");
5084             }
5085             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5086             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5087             SendToICS(buf);
5088             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5089             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5090             SendToICS(buf);
5091             i = boards[backwardMostMove][EP_STATUS];
5092             if(i >= 0) { // set e.p.
5093               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5094                 SendToICS(buf);
5095             }
5096             bsetup++;
5097           }
5098         }
5099       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5100             SendToICS("bsetup done\n"); // switch to normal examining.
5101     }
5102     for(i = backwardMostMove; i<last; i++) {
5103         char buf[20];
5104         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5105         SendToICS(buf);
5106     }
5107     SendToICS(ics_prefix);
5108     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5109 }
5110
5111 void
5112 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5113 {
5114     if (rf == DROP_RANK) {
5115       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5116       sprintf(move, "%c@%c%c\n",
5117                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5118     } else {
5119         if (promoChar == 'x' || promoChar == NULLCHAR) {
5120           sprintf(move, "%c%c%c%c\n",
5121                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5122         } else {
5123             sprintf(move, "%c%c%c%c%c\n",
5124                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5125         }
5126     }
5127 }
5128
5129 void
5130 ProcessICSInitScript (FILE *f)
5131 {
5132     char buf[MSG_SIZ];
5133
5134     while (fgets(buf, MSG_SIZ, f)) {
5135         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5136     }
5137
5138     fclose(f);
5139 }
5140
5141
5142 static int lastX, lastY, selectFlag, dragging;
5143
5144 void
5145 Sweep (int step)
5146 {
5147     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5148     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5149     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5150     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5151     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5152     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5153     do {
5154         promoSweep -= step;
5155         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5156         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5157         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5158         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5159         if(!step) step = -1;
5160     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5161             appData.testLegality && (promoSweep == king ||
5162             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5163     ChangeDragPiece(promoSweep);
5164 }
5165
5166 int
5167 PromoScroll (int x, int y)
5168 {
5169   int step = 0;
5170
5171   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5172   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5173   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5174   if(!step) return FALSE;
5175   lastX = x; lastY = y;
5176   if((promoSweep < BlackPawn) == flipView) step = -step;
5177   if(step > 0) selectFlag = 1;
5178   if(!selectFlag) Sweep(step);
5179   return FALSE;
5180 }
5181
5182 void
5183 NextPiece (int step)
5184 {
5185     ChessSquare piece = boards[currentMove][toY][toX];
5186     do {
5187         pieceSweep -= step;
5188         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5189         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5190         if(!step) step = -1;
5191     } while(PieceToChar(pieceSweep) == '.');
5192     boards[currentMove][toY][toX] = pieceSweep;
5193     DrawPosition(FALSE, boards[currentMove]);
5194     boards[currentMove][toY][toX] = piece;
5195 }
5196 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5197 void
5198 AlphaRank (char *move, int n)
5199 {
5200 //    char *p = move, c; int x, y;
5201
5202     if (appData.debugMode) {
5203         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5204     }
5205
5206     if(move[1]=='*' &&
5207        move[2]>='0' && move[2]<='9' &&
5208        move[3]>='a' && move[3]<='x'    ) {
5209         move[1] = '@';
5210         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5211         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5212     } else
5213     if(move[0]>='0' && move[0]<='9' &&
5214        move[1]>='a' && move[1]<='x' &&
5215        move[2]>='0' && move[2]<='9' &&
5216        move[3]>='a' && move[3]<='x'    ) {
5217         /* input move, Shogi -> normal */
5218         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5219         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5220         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5221         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5222     } else
5223     if(move[1]=='@' &&
5224        move[3]>='0' && move[3]<='9' &&
5225        move[2]>='a' && move[2]<='x'    ) {
5226         move[1] = '*';
5227         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5228         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5229     } else
5230     if(
5231        move[0]>='a' && move[0]<='x' &&
5232        move[3]>='0' && move[3]<='9' &&
5233        move[2]>='a' && move[2]<='x'    ) {
5234          /* output move, normal -> Shogi */
5235         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5236         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5237         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5239         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5240     }
5241     if (appData.debugMode) {
5242         fprintf(debugFP, "   out = '%s'\n", move);
5243     }
5244 }
5245
5246 char yy_textstr[8000];
5247
5248 /* Parser for moves from gnuchess, ICS, or user typein box */
5249 Boolean
5250 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5251 {
5252     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5253
5254     switch (*moveType) {
5255       case WhitePromotion:
5256       case BlackPromotion:
5257       case WhiteNonPromotion:
5258       case BlackNonPromotion:
5259       case NormalMove:
5260       case WhiteCapturesEnPassant:
5261       case BlackCapturesEnPassant:
5262       case WhiteKingSideCastle:
5263       case WhiteQueenSideCastle:
5264       case BlackKingSideCastle:
5265       case BlackQueenSideCastle:
5266       case WhiteKingSideCastleWild:
5267       case WhiteQueenSideCastleWild:
5268       case BlackKingSideCastleWild:
5269       case BlackQueenSideCastleWild:
5270       /* Code added by Tord: */
5271       case WhiteHSideCastleFR:
5272       case WhiteASideCastleFR:
5273       case BlackHSideCastleFR:
5274       case BlackASideCastleFR:
5275       /* End of code added by Tord */
5276       case IllegalMove:         /* bug or odd chess variant */
5277         *fromX = currentMoveString[0] - AAA;
5278         *fromY = currentMoveString[1] - ONE;
5279         *toX = currentMoveString[2] - AAA;
5280         *toY = currentMoveString[3] - ONE;
5281         *promoChar = currentMoveString[4];
5282         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5283             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5284     if (appData.debugMode) {
5285         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5286     }
5287             *fromX = *fromY = *toX = *toY = 0;
5288             return FALSE;
5289         }
5290         if (appData.testLegality) {
5291           return (*moveType != IllegalMove);
5292         } else {
5293           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5294                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5295         }
5296
5297       case WhiteDrop:
5298       case BlackDrop:
5299         *fromX = *moveType == WhiteDrop ?
5300           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5301           (int) CharToPiece(ToLower(currentMoveString[0]));
5302         *fromY = DROP_RANK;
5303         *toX = currentMoveString[2] - AAA;
5304         *toY = currentMoveString[3] - ONE;
5305         *promoChar = NULLCHAR;
5306         return TRUE;
5307
5308       case AmbiguousMove:
5309       case ImpossibleMove:
5310       case EndOfFile:
5311       case ElapsedTime:
5312       case Comment:
5313       case PGNTag:
5314       case NAG:
5315       case WhiteWins:
5316       case BlackWins:
5317       case GameIsDrawn:
5318       default:
5319     if (appData.debugMode) {
5320         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5321     }
5322         /* bug? */
5323         *fromX = *fromY = *toX = *toY = 0;
5324         *promoChar = NULLCHAR;
5325         return FALSE;
5326     }
5327 }
5328
5329 Boolean pushed = FALSE;
5330 char *lastParseAttempt;
5331
5332 void
5333 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5334 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5335   int fromX, fromY, toX, toY; char promoChar;
5336   ChessMove moveType;
5337   Boolean valid;
5338   int nr = 0;
5339
5340   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5341     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5342     pushed = TRUE;
5343   }
5344   endPV = forwardMostMove;
5345   do {
5346     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5347     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5348     lastParseAttempt = pv;
5349     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5350     if(!valid && nr == 0 &&
5351        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5352         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5353         // Hande case where played move is different from leading PV move
5354         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5355         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5356         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5357         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5358           endPV += 2; // if position different, keep this
5359           moveList[endPV-1][0] = fromX + AAA;
5360           moveList[endPV-1][1] = fromY + ONE;
5361           moveList[endPV-1][2] = toX + AAA;
5362           moveList[endPV-1][3] = toY + ONE;
5363           parseList[endPV-1][0] = NULLCHAR;
5364           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5365         }
5366       }
5367     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5368     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5369     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5370     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5371         valid++; // allow comments in PV
5372         continue;
5373     }
5374     nr++;
5375     if(endPV+1 > framePtr) break; // no space, truncate
5376     if(!valid) break;
5377     endPV++;
5378     CopyBoard(boards[endPV], boards[endPV-1]);
5379     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5380     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5381     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5382     CoordsToAlgebraic(boards[endPV - 1],
5383                              PosFlags(endPV - 1),
5384                              fromY, fromX, toY, toX, promoChar,
5385                              parseList[endPV - 1]);
5386   } while(valid);
5387   if(atEnd == 2) return; // used hidden, for PV conversion
5388   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5389   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5390   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5391                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5392   DrawPosition(TRUE, boards[currentMove]);
5393 }
5394
5395 int
5396 MultiPV (ChessProgramState *cps)
5397 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5398         int i;
5399         for(i=0; i<cps->nrOptions; i++)
5400             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5401                 return i;
5402         return -1;
5403 }
5404
5405 Boolean
5406 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5407 {
5408         int startPV, multi, lineStart, origIndex = index;
5409         char *p, buf2[MSG_SIZ];
5410
5411         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5412         lastX = x; lastY = y;
5413         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5414         lineStart = startPV = index;
5415         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5416         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5417         index = startPV;
5418         do{ while(buf[index] && buf[index] != '\n') index++;
5419         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5420         buf[index] = 0;
5421         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5422                 int n = first.option[multi].value;
5423                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5424                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5425                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5426                 first.option[multi].value = n;
5427                 *start = *end = 0;
5428                 return FALSE;
5429         }
5430         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5431         *start = startPV; *end = index-1;
5432         return TRUE;
5433 }
5434
5435 char *
5436 PvToSAN (char *pv)
5437 {
5438         static char buf[10*MSG_SIZ];
5439         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5440         *buf = NULLCHAR;
5441         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5442         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5443         for(i = forwardMostMove; i<endPV; i++){
5444             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5445             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5446             k += strlen(buf+k);
5447         }
5448         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5449         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5450         endPV = savedEnd;
5451         return buf;
5452 }
5453
5454 Boolean
5455 LoadPV (int x, int y)
5456 { // called on right mouse click to load PV
5457   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5458   lastX = x; lastY = y;
5459   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5460   return TRUE;
5461 }
5462
5463 void
5464 UnLoadPV ()
5465 {
5466   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5467   if(endPV < 0) return;
5468   if(appData.autoCopyPV) CopyFENToClipboard();
5469   endPV = -1;
5470   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5471         Boolean saveAnimate = appData.animate;
5472         if(pushed) {
5473             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5474                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5475             } else storedGames--; // abandon shelved tail of original game
5476         }
5477         pushed = FALSE;
5478         forwardMostMove = currentMove;
5479         currentMove = oldFMM;
5480         appData.animate = FALSE;
5481         ToNrEvent(forwardMostMove);
5482         appData.animate = saveAnimate;
5483   }
5484   currentMove = forwardMostMove;
5485   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5486   ClearPremoveHighlights();
5487   DrawPosition(TRUE, boards[currentMove]);
5488 }
5489
5490 void
5491 MovePV (int x, int y, int h)
5492 { // step through PV based on mouse coordinates (called on mouse move)
5493   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5494
5495   // we must somehow check if right button is still down (might be released off board!)
5496   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5497   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5498   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5499   if(!step) return;
5500   lastX = x; lastY = y;
5501
5502   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5503   if(endPV < 0) return;
5504   if(y < margin) step = 1; else
5505   if(y > h - margin) step = -1;
5506   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5507   currentMove += step;
5508   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5509   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5510                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5511   DrawPosition(FALSE, boards[currentMove]);
5512 }
5513
5514
5515 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5516 // All positions will have equal probability, but the current method will not provide a unique
5517 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5518 #define DARK 1
5519 #define LITE 2
5520 #define ANY 3
5521
5522 int squaresLeft[4];
5523 int piecesLeft[(int)BlackPawn];
5524 int seed, nrOfShuffles;
5525
5526 void
5527 GetPositionNumber ()
5528 {       // sets global variable seed
5529         int i;
5530
5531         seed = appData.defaultFrcPosition;
5532         if(seed < 0) { // randomize based on time for negative FRC position numbers
5533                 for(i=0; i<50; i++) seed += random();
5534                 seed = random() ^ random() >> 8 ^ random() << 8;
5535                 if(seed<0) seed = -seed;
5536         }
5537 }
5538
5539 int
5540 put (Board board, int pieceType, int rank, int n, int shade)
5541 // put the piece on the (n-1)-th empty squares of the given shade
5542 {
5543         int i;
5544
5545         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5546                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5547                         board[rank][i] = (ChessSquare) pieceType;
5548                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5549                         squaresLeft[ANY]--;
5550                         piecesLeft[pieceType]--;
5551                         return i;
5552                 }
5553         }
5554         return -1;
5555 }
5556
5557
5558 void
5559 AddOnePiece (Board board, int pieceType, int rank, int shade)
5560 // calculate where the next piece goes, (any empty square), and put it there
5561 {
5562         int i;
5563
5564         i = seed % squaresLeft[shade];
5565         nrOfShuffles *= squaresLeft[shade];
5566         seed /= squaresLeft[shade];
5567         put(board, pieceType, rank, i, shade);
5568 }
5569
5570 void
5571 AddTwoPieces (Board board, int pieceType, int rank)
5572 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5573 {
5574         int i, n=squaresLeft[ANY], j=n-1, k;
5575
5576         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5577         i = seed % k;  // pick one
5578         nrOfShuffles *= k;
5579         seed /= k;
5580         while(i >= j) i -= j--;
5581         j = n - 1 - j; i += j;
5582         put(board, pieceType, rank, j, ANY);
5583         put(board, pieceType, rank, i, ANY);
5584 }
5585
5586 void
5587 SetUpShuffle (Board board, int number)
5588 {
5589         int i, p, first=1;
5590
5591         GetPositionNumber(); nrOfShuffles = 1;
5592
5593         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5594         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5595         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5596
5597         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5598
5599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5600             p = (int) board[0][i];
5601             if(p < (int) BlackPawn) piecesLeft[p] ++;
5602             board[0][i] = EmptySquare;
5603         }
5604
5605         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5606             // shuffles restricted to allow normal castling put KRR first
5607             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5608                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5609             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5610                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5611             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5612                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5613             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5614                 put(board, WhiteRook, 0, 0, ANY);
5615             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5616         }
5617
5618         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5619             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5620             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5621                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5622                 while(piecesLeft[p] >= 2) {
5623                     AddOnePiece(board, p, 0, LITE);
5624                     AddOnePiece(board, p, 0, DARK);
5625                 }
5626                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5627             }
5628
5629         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5630             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5631             // but we leave King and Rooks for last, to possibly obey FRC restriction
5632             if(p == (int)WhiteRook) continue;
5633             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5634             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5635         }
5636
5637         // now everything is placed, except perhaps King (Unicorn) and Rooks
5638
5639         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5640             // Last King gets castling rights
5641             while(piecesLeft[(int)WhiteUnicorn]) {
5642                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5643                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5644             }
5645
5646             while(piecesLeft[(int)WhiteKing]) {
5647                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5648                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5649             }
5650
5651
5652         } else {
5653             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5654             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5655         }
5656
5657         // Only Rooks can be left; simply place them all
5658         while(piecesLeft[(int)WhiteRook]) {
5659                 i = put(board, WhiteRook, 0, 0, ANY);
5660                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5661                         if(first) {
5662                                 first=0;
5663                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5664                         }
5665                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5666                 }
5667         }
5668         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5669             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5670         }
5671
5672         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5673 }
5674
5675 int
5676 SetCharTable (char *table, const char * map)
5677 /* [HGM] moved here from winboard.c because of its general usefulness */
5678 /*       Basically a safe strcpy that uses the last character as King */
5679 {
5680     int result = FALSE; int NrPieces;
5681
5682     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5683                     && NrPieces >= 12 && !(NrPieces&1)) {
5684         int i; /* [HGM] Accept even length from 12 to 34 */
5685
5686         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5687         for( i=0; i<NrPieces/2-1; i++ ) {
5688             table[i] = map[i];
5689             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5690         }
5691         table[(int) WhiteKing]  = map[NrPieces/2-1];
5692         table[(int) BlackKing]  = map[NrPieces-1];
5693
5694         result = TRUE;
5695     }
5696
5697     return result;
5698 }
5699
5700 void
5701 Prelude (Board board)
5702 {       // [HGM] superchess: random selection of exo-pieces
5703         int i, j, k; ChessSquare p;
5704         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5705
5706         GetPositionNumber(); // use FRC position number
5707
5708         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5709             SetCharTable(pieceToChar, appData.pieceToCharTable);
5710             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5711                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5712         }
5713
5714         j = seed%4;                 seed /= 4;
5715         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5716         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5717         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5718         j = seed%3 + (seed%3 >= j); seed /= 3;
5719         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5720         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5721         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5722         j = seed%3;                 seed /= 3;
5723         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5724         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5725         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5726         j = seed%2 + (seed%2 >= j); seed /= 2;
5727         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5728         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5729         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5730         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5731         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5732         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5733         put(board, exoPieces[0],    0, 0, ANY);
5734         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5735 }
5736
5737 void
5738 InitPosition (int redraw)
5739 {
5740     ChessSquare (* pieces)[BOARD_FILES];
5741     int i, j, pawnRow, overrule,
5742     oldx = gameInfo.boardWidth,
5743     oldy = gameInfo.boardHeight,
5744     oldh = gameInfo.holdingsWidth;
5745     static int oldv;
5746
5747     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5748
5749     /* [AS] Initialize pv info list [HGM] and game status */
5750     {
5751         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5752             pvInfoList[i].depth = 0;
5753             boards[i][EP_STATUS] = EP_NONE;
5754             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5755         }
5756
5757         initialRulePlies = 0; /* 50-move counter start */
5758
5759         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5760         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5761     }
5762
5763
5764     /* [HGM] logic here is completely changed. In stead of full positions */
5765     /* the initialized data only consist of the two backranks. The switch */
5766     /* selects which one we will use, which is than copied to the Board   */
5767     /* initialPosition, which for the rest is initialized by Pawns and    */
5768     /* empty squares. This initial position is then copied to boards[0],  */
5769     /* possibly after shuffling, so that it remains available.            */
5770
5771     gameInfo.holdingsWidth = 0; /* default board sizes */
5772     gameInfo.boardWidth    = 8;
5773     gameInfo.boardHeight   = 8;
5774     gameInfo.holdingsSize  = 0;
5775     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5776     for(i=0; i<BOARD_FILES-2; i++)
5777       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5778     initialPosition[EP_STATUS] = EP_NONE;
5779     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5780     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5781          SetCharTable(pieceNickName, appData.pieceNickNames);
5782     else SetCharTable(pieceNickName, "............");
5783     pieces = FIDEArray;
5784
5785     switch (gameInfo.variant) {
5786     case VariantFischeRandom:
5787       shuffleOpenings = TRUE;
5788     default:
5789       break;
5790     case VariantShatranj:
5791       pieces = ShatranjArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5794       break;
5795     case VariantMakruk:
5796       pieces = makrukArray;
5797       nrCastlingRights = 0;
5798       startedFromSetupPosition = TRUE;
5799       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5800       break;
5801     case VariantTwoKings:
5802       pieces = twoKingsArray;
5803       break;
5804     case VariantGrand:
5805       pieces = GrandArray;
5806       nrCastlingRights = 0;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       gameInfo.boardWidth = 10;
5809       gameInfo.boardHeight = 10;
5810       gameInfo.holdingsSize = 7;
5811       break;
5812     case VariantCapaRandom:
5813       shuffleOpenings = TRUE;
5814     case VariantCapablanca:
5815       pieces = CapablancaArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5818       break;
5819     case VariantGothic:
5820       pieces = GothicArray;
5821       gameInfo.boardWidth = 10;
5822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5823       break;
5824     case VariantSChess:
5825       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5826       gameInfo.holdingsSize = 7;
5827       break;
5828     case VariantJanus:
5829       pieces = JanusArray;
5830       gameInfo.boardWidth = 10;
5831       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5832       nrCastlingRights = 6;
5833         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5834         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5835         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5836         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5837         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5838         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5839       break;
5840     case VariantFalcon:
5841       pieces = FalconArray;
5842       gameInfo.boardWidth = 10;
5843       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5844       break;
5845     case VariantXiangqi:
5846       pieces = XiangqiArray;
5847       gameInfo.boardWidth  = 9;
5848       gameInfo.boardHeight = 10;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5851       break;
5852     case VariantShogi:
5853       pieces = ShogiArray;
5854       gameInfo.boardWidth  = 9;
5855       gameInfo.boardHeight = 9;
5856       gameInfo.holdingsSize = 7;
5857       nrCastlingRights = 0;
5858       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5859       break;
5860     case VariantCourier:
5861       pieces = CourierArray;
5862       gameInfo.boardWidth  = 12;
5863       nrCastlingRights = 0;
5864       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5865       break;
5866     case VariantKnightmate:
5867       pieces = KnightmateArray;
5868       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5869       break;
5870     case VariantSpartan:
5871       pieces = SpartanArray;
5872       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5873       break;
5874     case VariantFairy:
5875       pieces = fairyArray;
5876       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5877       break;
5878     case VariantGreat:
5879       pieces = GreatArray;
5880       gameInfo.boardWidth = 10;
5881       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5882       gameInfo.holdingsSize = 8;
5883       break;
5884     case VariantSuper:
5885       pieces = FIDEArray;
5886       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5887       gameInfo.holdingsSize = 8;
5888       startedFromSetupPosition = TRUE;
5889       break;
5890     case VariantCrazyhouse:
5891     case VariantBughouse:
5892       pieces = FIDEArray;
5893       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5894       gameInfo.holdingsSize = 5;
5895       break;
5896     case VariantWildCastle:
5897       pieces = FIDEArray;
5898       /* !!?shuffle with kings guaranteed to be on d or e file */
5899       shuffleOpenings = 1;
5900       break;
5901     case VariantNoCastle:
5902       pieces = FIDEArray;
5903       nrCastlingRights = 0;
5904       /* !!?unconstrained back-rank shuffle */
5905       shuffleOpenings = 1;
5906       break;
5907     }
5908
5909     overrule = 0;
5910     if(appData.NrFiles >= 0) {
5911         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5912         gameInfo.boardWidth = appData.NrFiles;
5913     }
5914     if(appData.NrRanks >= 0) {
5915         gameInfo.boardHeight = appData.NrRanks;
5916     }
5917     if(appData.holdingsSize >= 0) {
5918         i = appData.holdingsSize;
5919         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5920         gameInfo.holdingsSize = i;
5921     }
5922     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5923     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5924         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5925
5926     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5927     if(pawnRow < 1) pawnRow = 1;
5928     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5929
5930     /* User pieceToChar list overrules defaults */
5931     if(appData.pieceToCharTable != NULL)
5932         SetCharTable(pieceToChar, appData.pieceToCharTable);
5933
5934     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5935
5936         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5937             s = (ChessSquare) 0; /* account holding counts in guard band */
5938         for( i=0; i<BOARD_HEIGHT; i++ )
5939             initialPosition[i][j] = s;
5940
5941         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5942         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5943         initialPosition[pawnRow][j] = WhitePawn;
5944         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5945         if(gameInfo.variant == VariantXiangqi) {
5946             if(j&1) {
5947                 initialPosition[pawnRow][j] =
5948                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5949                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5950                    initialPosition[2][j] = WhiteCannon;
5951                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5952                 }
5953             }
5954         }
5955         if(gameInfo.variant == VariantGrand) {
5956             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5957                initialPosition[0][j] = WhiteRook;
5958                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5959             }
5960         }
5961         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5962     }
5963     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5964
5965             j=BOARD_LEFT+1;
5966             initialPosition[1][j] = WhiteBishop;
5967             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5968             j=BOARD_RGHT-2;
5969             initialPosition[1][j] = WhiteRook;
5970             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5971     }
5972
5973     if( nrCastlingRights == -1) {
5974         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5975         /*       This sets default castling rights from none to normal corners   */
5976         /* Variants with other castling rights must set them themselves above    */
5977         nrCastlingRights = 6;
5978
5979         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5980         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5981         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5982         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5983         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5984         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5985      }
5986
5987      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5988      if(gameInfo.variant == VariantGreat) { // promotion commoners
5989         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5990         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5991         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5992         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5993      }
5994      if( gameInfo.variant == VariantSChess ) {
5995       initialPosition[1][0] = BlackMarshall;
5996       initialPosition[2][0] = BlackAngel;
5997       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5998       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5999       initialPosition[1][1] = initialPosition[2][1] = 
6000       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6001      }
6002   if (appData.debugMode) {
6003     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6004   }
6005     if(shuffleOpenings) {
6006         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6007         startedFromSetupPosition = TRUE;
6008     }
6009     if(startedFromPositionFile) {
6010       /* [HGM] loadPos: use PositionFile for every new game */
6011       CopyBoard(initialPosition, filePosition);
6012       for(i=0; i<nrCastlingRights; i++)
6013           initialRights[i] = filePosition[CASTLING][i];
6014       startedFromSetupPosition = TRUE;
6015     }
6016
6017     CopyBoard(boards[0], initialPosition);
6018
6019     if(oldx != gameInfo.boardWidth ||
6020        oldy != gameInfo.boardHeight ||
6021        oldv != gameInfo.variant ||
6022        oldh != gameInfo.holdingsWidth
6023                                          )
6024             InitDrawingSizes(-2 ,0);
6025
6026     oldv = gameInfo.variant;
6027     if (redraw)
6028       DrawPosition(TRUE, boards[currentMove]);
6029 }
6030
6031 void
6032 SendBoard (ChessProgramState *cps, int moveNum)
6033 {
6034     char message[MSG_SIZ];
6035
6036     if (cps->useSetboard) {
6037       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6038       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6039       SendToProgram(message, cps);
6040       free(fen);
6041
6042     } else {
6043       ChessSquare *bp;
6044       int i, j, left=0, right=BOARD_WIDTH;
6045       /* Kludge to set black to move, avoiding the troublesome and now
6046        * deprecated "black" command.
6047        */
6048       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6049         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6050
6051       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6052
6053       SendToProgram("edit\n", cps);
6054       SendToProgram("#\n", cps);
6055       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6056         bp = &boards[moveNum][i][left];
6057         for (j = left; j < right; j++, bp++) {
6058           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6059           if ((int) *bp < (int) BlackPawn) {
6060             if(j == BOARD_RGHT+1)
6061                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6062             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6063             if(message[0] == '+' || message[0] == '~') {
6064               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6065                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6066                         AAA + j, ONE + i);
6067             }
6068             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6069                 message[1] = BOARD_RGHT   - 1 - j + '1';
6070                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6071             }
6072             SendToProgram(message, cps);
6073           }
6074         }
6075       }
6076
6077       SendToProgram("c\n", cps);
6078       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6079         bp = &boards[moveNum][i][left];
6080         for (j = left; j < right; j++, bp++) {
6081           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6082           if (((int) *bp != (int) EmptySquare)
6083               && ((int) *bp >= (int) BlackPawn)) {
6084             if(j == BOARD_LEFT-2)
6085                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6086             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6087                     AAA + j, ONE + i);
6088             if(message[0] == '+' || message[0] == '~') {
6089               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6090                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6091                         AAA + j, ONE + i);
6092             }
6093             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6094                 message[1] = BOARD_RGHT   - 1 - j + '1';
6095                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6096             }
6097             SendToProgram(message, cps);
6098           }
6099         }
6100       }
6101
6102       SendToProgram(".\n", cps);
6103     }
6104     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6105 }
6106
6107 ChessSquare
6108 DefaultPromoChoice (int white)
6109 {
6110     ChessSquare result;
6111     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6112         result = WhiteFerz; // no choice
6113     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6114         result= WhiteKing; // in Suicide Q is the last thing we want
6115     else if(gameInfo.variant == VariantSpartan)
6116         result = white ? WhiteQueen : WhiteAngel;
6117     else result = WhiteQueen;
6118     if(!white) result = WHITE_TO_BLACK result;
6119     return result;
6120 }
6121
6122 static int autoQueen; // [HGM] oneclick
6123
6124 int
6125 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6126 {
6127     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6128     /* [HGM] add Shogi promotions */
6129     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6130     ChessSquare piece;
6131     ChessMove moveType;
6132     Boolean premove;
6133
6134     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6135     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6136
6137     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6138       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6139         return FALSE;
6140
6141     piece = boards[currentMove][fromY][fromX];
6142     if(gameInfo.variant == VariantShogi) {
6143         promotionZoneSize = BOARD_HEIGHT/3;
6144         highestPromotingPiece = (int)WhiteFerz;
6145     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6146         promotionZoneSize = 3;
6147     }
6148
6149     // Treat Lance as Pawn when it is not representing Amazon
6150     if(gameInfo.variant != VariantSuper) {
6151         if(piece == WhiteLance) piece = WhitePawn; else
6152         if(piece == BlackLance) piece = BlackPawn;
6153     }
6154
6155     // next weed out all moves that do not touch the promotion zone at all
6156     if((int)piece >= BlackPawn) {
6157         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6158              return FALSE;
6159         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6160     } else {
6161         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6162            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6163     }
6164
6165     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6166
6167     // weed out mandatory Shogi promotions
6168     if(gameInfo.variant == VariantShogi) {
6169         if(piece >= BlackPawn) {
6170             if(toY == 0 && piece == BlackPawn ||
6171                toY == 0 && piece == BlackQueen ||
6172                toY <= 1 && piece == BlackKnight) {
6173                 *promoChoice = '+';
6174                 return FALSE;
6175             }
6176         } else {
6177             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6178                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6179                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6180                 *promoChoice = '+';
6181                 return FALSE;
6182             }
6183         }
6184     }
6185
6186     // weed out obviously illegal Pawn moves
6187     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6188         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6189         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6190         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6191         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6192         // note we are not allowed to test for valid (non-)capture, due to premove
6193     }
6194
6195     // we either have a choice what to promote to, or (in Shogi) whether to promote
6196     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6197         *promoChoice = PieceToChar(BlackFerz);  // no choice
6198         return FALSE;
6199     }
6200     // no sense asking what we must promote to if it is going to explode...
6201     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6202         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6203         return FALSE;
6204     }
6205     // give caller the default choice even if we will not make it
6206     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6207     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6208     if(        sweepSelect && gameInfo.variant != VariantGreat
6209                            && gameInfo.variant != VariantGrand
6210                            && gameInfo.variant != VariantSuper) return FALSE;
6211     if(autoQueen) return FALSE; // predetermined
6212
6213     // suppress promotion popup on illegal moves that are not premoves
6214     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6215               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6216     if(appData.testLegality && !premove) {
6217         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6218                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6219         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6220             return FALSE;
6221     }
6222
6223     return TRUE;
6224 }
6225
6226 int
6227 InPalace (int row, int column)
6228 {   /* [HGM] for Xiangqi */
6229     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6230          column < (BOARD_WIDTH + 4)/2 &&
6231          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6232     return FALSE;
6233 }
6234
6235 int
6236 PieceForSquare (int x, int y)
6237 {
6238   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6239      return -1;
6240   else
6241      return boards[currentMove][y][x];
6242 }
6243
6244 int
6245 OKToStartUserMove (int x, int y)
6246 {
6247     ChessSquare from_piece;
6248     int white_piece;
6249
6250     if (matchMode) return FALSE;
6251     if (gameMode == EditPosition) return TRUE;
6252
6253     if (x >= 0 && y >= 0)
6254       from_piece = boards[currentMove][y][x];
6255     else
6256       from_piece = EmptySquare;
6257
6258     if (from_piece == EmptySquare) return FALSE;
6259
6260     white_piece = (int)from_piece >= (int)WhitePawn &&
6261       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6262
6263     switch (gameMode) {
6264       case AnalyzeFile:
6265       case TwoMachinesPlay:
6266       case EndOfGame:
6267         return FALSE;
6268
6269       case IcsObserving:
6270       case IcsIdle:
6271         return FALSE;
6272
6273       case MachinePlaysWhite:
6274       case IcsPlayingBlack:
6275         if (appData.zippyPlay) return FALSE;
6276         if (white_piece) {
6277             DisplayMoveError(_("You are playing Black"));
6278             return FALSE;
6279         }
6280         break;
6281
6282       case MachinePlaysBlack:
6283       case IcsPlayingWhite:
6284         if (appData.zippyPlay) return FALSE;
6285         if (!white_piece) {
6286             DisplayMoveError(_("You are playing White"));
6287             return FALSE;
6288         }
6289         break;
6290
6291       case PlayFromGameFile:
6292             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6293       case EditGame:
6294         if (!white_piece && WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is White's turn"));
6296             return FALSE;
6297         }
6298         if (white_piece && !WhiteOnMove(currentMove)) {
6299             DisplayMoveError(_("It is Black's turn"));
6300             return FALSE;
6301         }
6302         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6303             /* Editing correspondence game history */
6304             /* Could disallow this or prompt for confirmation */
6305             cmailOldMove = -1;
6306         }
6307         break;
6308
6309       case BeginningOfGame:
6310         if (appData.icsActive) return FALSE;
6311         if (!appData.noChessProgram) {
6312             if (!white_piece) {
6313                 DisplayMoveError(_("You are playing White"));
6314                 return FALSE;
6315             }
6316         }
6317         break;
6318
6319       case Training:
6320         if (!white_piece && WhiteOnMove(currentMove)) {
6321             DisplayMoveError(_("It is White's turn"));
6322             return FALSE;
6323         }
6324         if (white_piece && !WhiteOnMove(currentMove)) {
6325             DisplayMoveError(_("It is Black's turn"));
6326             return FALSE;
6327         }
6328         break;
6329
6330       default:
6331       case IcsExamining:
6332         break;
6333     }
6334     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6335         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6336         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6337         && gameMode != AnalyzeFile && gameMode != Training) {
6338         DisplayMoveError(_("Displayed position is not current"));
6339         return FALSE;
6340     }
6341     return TRUE;
6342 }
6343
6344 Boolean
6345 OnlyMove (int *x, int *y, Boolean captures) 
6346 {
6347     DisambiguateClosure cl;
6348     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6349     switch(gameMode) {
6350       case MachinePlaysBlack:
6351       case IcsPlayingWhite:
6352       case BeginningOfGame:
6353         if(!WhiteOnMove(currentMove)) return FALSE;
6354         break;
6355       case MachinePlaysWhite:
6356       case IcsPlayingBlack:
6357         if(WhiteOnMove(currentMove)) return FALSE;
6358         break;
6359       case EditGame:
6360         break;
6361       default:
6362         return FALSE;
6363     }
6364     cl.pieceIn = EmptySquare;
6365     cl.rfIn = *y;
6366     cl.ffIn = *x;
6367     cl.rtIn = -1;
6368     cl.ftIn = -1;
6369     cl.promoCharIn = NULLCHAR;
6370     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6371     if( cl.kind == NormalMove ||
6372         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6373         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6374         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6375       fromX = cl.ff;
6376       fromY = cl.rf;
6377       *x = cl.ft;
6378       *y = cl.rt;
6379       return TRUE;
6380     }
6381     if(cl.kind != ImpossibleMove) return FALSE;
6382     cl.pieceIn = EmptySquare;
6383     cl.rfIn = -1;
6384     cl.ffIn = -1;
6385     cl.rtIn = *y;
6386     cl.ftIn = *x;
6387     cl.promoCharIn = NULLCHAR;
6388     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6389     if( cl.kind == NormalMove ||
6390         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6391         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6392         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6393       fromX = cl.ff;
6394       fromY = cl.rf;
6395       *x = cl.ft;
6396       *y = cl.rt;
6397       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6398       return TRUE;
6399     }
6400     return FALSE;
6401 }
6402
6403 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6404 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6405 int lastLoadGameUseList = FALSE;
6406 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6407 ChessMove lastLoadGameStart = EndOfFile;
6408
6409 void
6410 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6411 {
6412     ChessMove moveType;
6413     ChessSquare pdown, pup;
6414
6415     /* Check if the user is playing in turn.  This is complicated because we
6416        let the user "pick up" a piece before it is his turn.  So the piece he
6417        tried to pick up may have been captured by the time he puts it down!
6418        Therefore we use the color the user is supposed to be playing in this
6419        test, not the color of the piece that is currently on the starting
6420        square---except in EditGame mode, where the user is playing both
6421        sides; fortunately there the capture race can't happen.  (It can
6422        now happen in IcsExamining mode, but that's just too bad.  The user
6423        will get a somewhat confusing message in that case.)
6424        */
6425
6426     switch (gameMode) {
6427       case AnalyzeFile:
6428       case TwoMachinesPlay:
6429       case EndOfGame:
6430       case IcsObserving:
6431       case IcsIdle:
6432         /* We switched into a game mode where moves are not accepted,
6433            perhaps while the mouse button was down. */
6434         return;
6435
6436       case MachinePlaysWhite:
6437         /* User is moving for Black */
6438         if (WhiteOnMove(currentMove)) {
6439             DisplayMoveError(_("It is White's turn"));
6440             return;
6441         }
6442         break;
6443
6444       case MachinePlaysBlack:
6445         /* User is moving for White */
6446         if (!WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is Black's turn"));
6448             return;
6449         }
6450         break;
6451
6452       case PlayFromGameFile:
6453             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6454       case EditGame:
6455       case IcsExamining:
6456       case BeginningOfGame:
6457       case AnalyzeMode:
6458       case Training:
6459         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6460         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6461             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6462             /* User is moving for Black */
6463             if (WhiteOnMove(currentMove)) {
6464                 DisplayMoveError(_("It is White's turn"));
6465                 return;
6466             }
6467         } else {
6468             /* User is moving for White */
6469             if (!WhiteOnMove(currentMove)) {
6470                 DisplayMoveError(_("It is Black's turn"));
6471                 return;
6472             }
6473         }
6474         break;
6475
6476       case IcsPlayingBlack:
6477         /* User is moving for Black */
6478         if (WhiteOnMove(currentMove)) {
6479             if (!appData.premove) {
6480                 DisplayMoveError(_("It is White's turn"));
6481             } else if (toX >= 0 && toY >= 0) {
6482                 premoveToX = toX;
6483                 premoveToY = toY;
6484                 premoveFromX = fromX;
6485                 premoveFromY = fromY;
6486                 premovePromoChar = promoChar;
6487                 gotPremove = 1;
6488                 if (appData.debugMode)
6489                     fprintf(debugFP, "Got premove: fromX %d,"
6490                             "fromY %d, toX %d, toY %d\n",
6491                             fromX, fromY, toX, toY);
6492             }
6493             return;
6494         }
6495         break;
6496
6497       case IcsPlayingWhite:
6498         /* User is moving for White */
6499         if (!WhiteOnMove(currentMove)) {
6500             if (!appData.premove) {
6501                 DisplayMoveError(_("It is Black's turn"));
6502             } else if (toX >= 0 && toY >= 0) {
6503                 premoveToX = toX;
6504                 premoveToY = toY;
6505                 premoveFromX = fromX;
6506                 premoveFromY = fromY;
6507                 premovePromoChar = promoChar;
6508                 gotPremove = 1;
6509                 if (appData.debugMode)
6510                     fprintf(debugFP, "Got premove: fromX %d,"
6511                             "fromY %d, toX %d, toY %d\n",
6512                             fromX, fromY, toX, toY);
6513             }
6514             return;
6515         }
6516         break;
6517
6518       default:
6519         break;
6520
6521       case EditPosition:
6522         /* EditPosition, empty square, or different color piece;
6523            click-click move is possible */
6524         if (toX == -2 || toY == -2) {
6525             boards[0][fromY][fromX] = EmptySquare;
6526             DrawPosition(FALSE, boards[currentMove]);
6527             return;
6528         } else if (toX >= 0 && toY >= 0) {
6529             boards[0][toY][toX] = boards[0][fromY][fromX];
6530             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6531                 if(boards[0][fromY][0] != EmptySquare) {
6532                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6533                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6534                 }
6535             } else
6536             if(fromX == BOARD_RGHT+1) {
6537                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6538                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6539                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6540                 }
6541             } else
6542             boards[0][fromY][fromX] = EmptySquare;
6543             DrawPosition(FALSE, boards[currentMove]);
6544             return;
6545         }
6546         return;
6547     }
6548
6549     if(toX < 0 || toY < 0) return;
6550     pdown = boards[currentMove][fromY][fromX];
6551     pup = boards[currentMove][toY][toX];
6552
6553     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6554     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6555          if( pup != EmptySquare ) return;
6556          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6557            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6558                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6559            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6560            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6561            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6562            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6563          fromY = DROP_RANK;
6564     }
6565
6566     /* [HGM] always test for legality, to get promotion info */
6567     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6568                                          fromY, fromX, toY, toX, promoChar);
6569
6570     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6571
6572     /* [HGM] but possibly ignore an IllegalMove result */
6573     if (appData.testLegality) {
6574         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6575             DisplayMoveError(_("Illegal move"));
6576             return;
6577         }
6578     }
6579
6580     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6581 }
6582
6583 /* Common tail of UserMoveEvent and DropMenuEvent */
6584 int
6585 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6586 {
6587     char *bookHit = 0;
6588
6589     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6590         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6591         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6592         if(WhiteOnMove(currentMove)) {
6593             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6594         } else {
6595             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6596         }
6597     }
6598
6599     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6600        move type in caller when we know the move is a legal promotion */
6601     if(moveType == NormalMove && promoChar)
6602         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6603
6604     /* [HGM] <popupFix> The following if has been moved here from
6605        UserMoveEvent(). Because it seemed to belong here (why not allow
6606        piece drops in training games?), and because it can only be
6607        performed after it is known to what we promote. */
6608     if (gameMode == Training) {
6609       /* compare the move played on the board to the next move in the
6610        * game. If they match, display the move and the opponent's response.
6611        * If they don't match, display an error message.
6612        */
6613       int saveAnimate;
6614       Board testBoard;
6615       CopyBoard(testBoard, boards[currentMove]);
6616       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6617
6618       if (CompareBoards(testBoard, boards[currentMove+1])) {
6619         ForwardInner(currentMove+1);
6620
6621         /* Autoplay the opponent's response.
6622          * if appData.animate was TRUE when Training mode was entered,
6623          * the response will be animated.
6624          */
6625         saveAnimate = appData.animate;
6626         appData.animate = animateTraining;
6627         ForwardInner(currentMove+1);
6628         appData.animate = saveAnimate;
6629
6630         /* check for the end of the game */
6631         if (currentMove >= forwardMostMove) {
6632           gameMode = PlayFromGameFile;
6633           ModeHighlight();
6634           SetTrainingModeOff();
6635           DisplayInformation(_("End of game"));
6636         }
6637       } else {
6638         DisplayError(_("Incorrect move"), 0);
6639       }
6640       return 1;
6641     }
6642
6643   /* Ok, now we know that the move is good, so we can kill
6644      the previous line in Analysis Mode */
6645   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6646                                 && currentMove < forwardMostMove) {
6647     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6648     else forwardMostMove = currentMove;
6649   }
6650
6651   /* If we need the chess program but it's dead, restart it */
6652   ResurrectChessProgram();
6653
6654   /* A user move restarts a paused game*/
6655   if (pausing)
6656     PauseEvent();
6657
6658   thinkOutput[0] = NULLCHAR;
6659
6660   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6661
6662   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6663     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6664     return 1;
6665   }
6666
6667   if (gameMode == BeginningOfGame) {
6668     if (appData.noChessProgram) {
6669       gameMode = EditGame;
6670       SetGameInfo();
6671     } else {
6672       char buf[MSG_SIZ];
6673       gameMode = MachinePlaysBlack;
6674       StartClocks();
6675       SetGameInfo();
6676       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6677       DisplayTitle(buf);
6678       if (first.sendName) {
6679         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6680         SendToProgram(buf, &first);
6681       }
6682       StartClocks();
6683     }
6684     ModeHighlight();
6685   }
6686
6687   /* Relay move to ICS or chess engine */
6688   if (appData.icsActive) {
6689     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6690         gameMode == IcsExamining) {
6691       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6692         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6693         SendToICS("draw ");
6694         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6695       }
6696       // also send plain move, in case ICS does not understand atomic claims
6697       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6698       ics_user_moved = 1;
6699     }
6700   } else {
6701     if (first.sendTime && (gameMode == BeginningOfGame ||
6702                            gameMode == MachinePlaysWhite ||
6703                            gameMode == MachinePlaysBlack)) {
6704       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6705     }
6706     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6707          // [HGM] book: if program might be playing, let it use book
6708         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6709         first.maybeThinking = TRUE;
6710     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6711         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6712         SendBoard(&first, currentMove+1);
6713     } else SendMoveToProgram(forwardMostMove-1, &first);
6714     if (currentMove == cmailOldMove + 1) {
6715       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6716     }
6717   }
6718
6719   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6720
6721   switch (gameMode) {
6722   case EditGame:
6723     if(appData.testLegality)
6724     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6725     case MT_NONE:
6726     case MT_CHECK:
6727       break;
6728     case MT_CHECKMATE:
6729     case MT_STAINMATE:
6730       if (WhiteOnMove(currentMove)) {
6731         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6732       } else {
6733         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6734       }
6735       break;
6736     case MT_STALEMATE:
6737       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6738       break;
6739     }
6740     break;
6741
6742   case MachinePlaysBlack:
6743   case MachinePlaysWhite:
6744     /* disable certain menu options while machine is thinking */
6745     SetMachineThinkingEnables();
6746     break;
6747
6748   default:
6749     break;
6750   }
6751
6752   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6753   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6754
6755   if(bookHit) { // [HGM] book: simulate book reply
6756         static char bookMove[MSG_SIZ]; // a bit generous?
6757
6758         programStats.nodes = programStats.depth = programStats.time =
6759         programStats.score = programStats.got_only_move = 0;
6760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6761
6762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6763         strcat(bookMove, bookHit);
6764         HandleMachineMove(bookMove, &first);
6765   }
6766   return 1;
6767 }
6768
6769 void
6770 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6771 {
6772     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6773     Markers *m = (Markers *) closure;
6774     if(rf == fromY && ff == fromX)
6775         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6776                          || kind == WhiteCapturesEnPassant
6777                          || kind == BlackCapturesEnPassant);
6778     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6779 }
6780
6781 void
6782 MarkTargetSquares (int clear)
6783 {
6784   int x, y;
6785   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6786      !appData.testLegality || gameMode == EditPosition) return;
6787   if(clear) {
6788     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6789   } else {
6790     int capt = 0;
6791     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6792     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6793       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6794       if(capt)
6795       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6796     }
6797   }
6798   DrawPosition(TRUE, NULL);
6799 }
6800
6801 int
6802 Explode (Board board, int fromX, int fromY, int toX, int toY)
6803 {
6804     if(gameInfo.variant == VariantAtomic &&
6805        (board[toY][toX] != EmptySquare ||                     // capture?
6806         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6807                          board[fromY][fromX] == BlackPawn   )
6808       )) {
6809         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6810         return TRUE;
6811     }
6812     return FALSE;
6813 }
6814
6815 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6816
6817 int
6818 CanPromote (ChessSquare piece, int y)
6819 {
6820         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6821         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6822         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6823            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6824            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6825                                                   gameInfo.variant == VariantMakruk) return FALSE;
6826         return (piece == BlackPawn && y == 1 ||
6827                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6828                 piece == BlackLance && y == 1 ||
6829                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6830 }
6831
6832 void
6833 LeftClick (ClickType clickType, int xPix, int yPix)
6834 {
6835     int x, y;
6836     Boolean saveAnimate;
6837     static int second = 0, promotionChoice = 0, clearFlag = 0;
6838     char promoChoice = NULLCHAR;
6839     ChessSquare piece;
6840
6841     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6842
6843     if (clickType == Press) ErrorPopDown();
6844
6845     x = EventToSquare(xPix, BOARD_WIDTH);
6846     y = EventToSquare(yPix, BOARD_HEIGHT);
6847     if (!flipView && y >= 0) {
6848         y = BOARD_HEIGHT - 1 - y;
6849     }
6850     if (flipView && x >= 0) {
6851         x = BOARD_WIDTH - 1 - x;
6852     }
6853
6854     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6855         defaultPromoChoice = promoSweep;
6856         promoSweep = EmptySquare;   // terminate sweep
6857         promoDefaultAltered = TRUE;
6858         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6859     }
6860
6861     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6862         if(clickType == Release) return; // ignore upclick of click-click destination
6863         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6864         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6865         if(gameInfo.holdingsWidth &&
6866                 (WhiteOnMove(currentMove)
6867                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6868                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6869             // click in right holdings, for determining promotion piece
6870             ChessSquare p = boards[currentMove][y][x];
6871             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6872             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6873             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6874                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6875                 fromX = fromY = -1;
6876                 return;
6877             }
6878         }
6879         DrawPosition(FALSE, boards[currentMove]);
6880         return;
6881     }
6882
6883     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6884     if(clickType == Press
6885             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6886               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6887               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6888         return;
6889
6890     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
6891         // could be static click on premove from-square: abort premove
6892         gotPremove = 0;
6893         ClearPremoveHighlights();
6894     }
6895
6896     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6897         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6898
6899     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6900         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6901                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6902         defaultPromoChoice = DefaultPromoChoice(side);
6903     }
6904
6905     autoQueen = appData.alwaysPromoteToQueen;
6906
6907     if (fromX == -1) {
6908       int originalY = y;
6909       gatingPiece = EmptySquare;
6910       if (clickType != Press) {
6911         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6912             DragPieceEnd(xPix, yPix); dragging = 0;
6913             DrawPosition(FALSE, NULL);
6914         }
6915         return;
6916       }
6917       fromX = x; fromY = y; toX = toY = -1;
6918       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6919          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6920          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6921             /* First square */
6922             if (OKToStartUserMove(fromX, fromY)) {
6923                 second = 0;
6924                 MarkTargetSquares(0);
6925                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6926                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6927                     promoSweep = defaultPromoChoice;
6928                     selectFlag = 0; lastX = xPix; lastY = yPix;
6929                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6930                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6931                 }
6932                 if (appData.highlightDragging) {
6933                     SetHighlights(fromX, fromY, -1, -1);
6934                 }
6935             } else fromX = fromY = -1;
6936             return;
6937         }
6938     }
6939
6940     /* fromX != -1 */
6941     if (clickType == Press && gameMode != EditPosition) {
6942         ChessSquare fromP;
6943         ChessSquare toP;
6944         int frc;
6945
6946         // ignore off-board to clicks
6947         if(y < 0 || x < 0) return;
6948
6949         /* Check if clicking again on the same color piece */
6950         fromP = boards[currentMove][fromY][fromX];
6951         toP = boards[currentMove][y][x];
6952         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6953         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6954              WhitePawn <= toP && toP <= WhiteKing &&
6955              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6956              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6957             (BlackPawn <= fromP && fromP <= BlackKing &&
6958              BlackPawn <= toP && toP <= BlackKing &&
6959              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6960              !(fromP == BlackKing && toP == BlackRook && frc))) {
6961             /* Clicked again on same color piece -- changed his mind */
6962             second = (x == fromX && y == fromY);
6963             promoDefaultAltered = FALSE;
6964             MarkTargetSquares(1);
6965            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6966             if (appData.highlightDragging) {
6967                 SetHighlights(x, y, -1, -1);
6968             } else {
6969                 ClearHighlights();
6970             }
6971             if (OKToStartUserMove(x, y)) {
6972                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6973                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6974                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6975                  gatingPiece = boards[currentMove][fromY][fromX];
6976                 else gatingPiece = EmptySquare;
6977                 fromX = x;
6978                 fromY = y; dragging = 1;
6979                 MarkTargetSquares(0);
6980                 DragPieceBegin(xPix, yPix, FALSE);
6981                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6982                     promoSweep = defaultPromoChoice;
6983                     selectFlag = 0; lastX = xPix; lastY = yPix;
6984                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6985                 }
6986             }
6987            }
6988            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6989            second = FALSE; 
6990         }
6991         // ignore clicks on holdings
6992         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6993     }
6994
6995     if (clickType == Release && x == fromX && y == fromY) {
6996         DragPieceEnd(xPix, yPix); dragging = 0;
6997         if(clearFlag) {
6998             // a deferred attempt to click-click move an empty square on top of a piece
6999             boards[currentMove][y][x] = EmptySquare;
7000             ClearHighlights();
7001             DrawPosition(FALSE, boards[currentMove]);
7002             fromX = fromY = -1; clearFlag = 0;
7003             return;
7004         }
7005         if (appData.animateDragging) {
7006             /* Undo animation damage if any */
7007             DrawPosition(FALSE, NULL);
7008         }
7009         if (second) {
7010             /* Second up/down in same square; just abort move */
7011             second = 0;
7012             fromX = fromY = -1;
7013             gatingPiece = EmptySquare;
7014             ClearHighlights();
7015             gotPremove = 0;
7016             ClearPremoveHighlights();
7017         } else {
7018             /* First upclick in same square; start click-click mode */
7019             SetHighlights(x, y, -1, -1);
7020         }
7021         return;
7022     }
7023
7024     clearFlag = 0;
7025
7026     /* we now have a different from- and (possibly off-board) to-square */
7027     /* Completed move */
7028     toX = x;
7029     toY = y;
7030     saveAnimate = appData.animate;
7031     MarkTargetSquares(1);
7032     if (clickType == Press) {
7033         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7034             // must be Edit Position mode with empty-square selected
7035             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7036             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7037             return;
7038         }
7039         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7040             ChessSquare piece = boards[currentMove][fromY][fromX];
7041             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7042             promoSweep = defaultPromoChoice;
7043             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7044             selectFlag = 0; lastX = xPix; lastY = yPix;
7045             Sweep(0); // Pawn that is going to promote: preview promotion piece
7046             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7047             DrawPosition(FALSE, boards[currentMove]);
7048             return;
7049         }
7050         /* Finish clickclick move */
7051         if (appData.animate || appData.highlightLastMove) {
7052             SetHighlights(fromX, fromY, toX, toY);
7053         } else {
7054             ClearHighlights();
7055         }
7056     } else {
7057         /* Finish drag move */
7058         if (appData.highlightLastMove) {
7059             SetHighlights(fromX, fromY, toX, toY);
7060         } else {
7061             ClearHighlights();
7062         }
7063         DragPieceEnd(xPix, yPix); dragging = 0;
7064         /* Don't animate move and drag both */
7065         appData.animate = FALSE;
7066     }
7067
7068     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7069     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7070         ChessSquare piece = boards[currentMove][fromY][fromX];
7071         if(gameMode == EditPosition && piece != EmptySquare &&
7072            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7073             int n;
7074
7075             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7076                 n = PieceToNumber(piece - (int)BlackPawn);
7077                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7078                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7079                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7080             } else
7081             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7082                 n = PieceToNumber(piece);
7083                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7084                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7085                 boards[currentMove][n][BOARD_WIDTH-2]++;
7086             }
7087             boards[currentMove][fromY][fromX] = EmptySquare;
7088         }
7089         ClearHighlights();
7090         fromX = fromY = -1;
7091         DrawPosition(TRUE, boards[currentMove]);
7092         return;
7093     }
7094
7095     // off-board moves should not be highlighted
7096     if(x < 0 || y < 0) ClearHighlights();
7097
7098     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7099
7100     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7101         SetHighlights(fromX, fromY, toX, toY);
7102         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7103             // [HGM] super: promotion to captured piece selected from holdings
7104             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7105             promotionChoice = TRUE;
7106             // kludge follows to temporarily execute move on display, without promoting yet
7107             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7108             boards[currentMove][toY][toX] = p;
7109             DrawPosition(FALSE, boards[currentMove]);
7110             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7111             boards[currentMove][toY][toX] = q;
7112             DisplayMessage("Click in holdings to choose piece", "");
7113             return;
7114         }
7115         PromotionPopUp();
7116     } else {
7117         int oldMove = currentMove;
7118         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7119         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7120         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7121         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7122            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7123             DrawPosition(TRUE, boards[currentMove]);
7124         fromX = fromY = -1;
7125     }
7126     appData.animate = saveAnimate;
7127     if (appData.animate || appData.animateDragging) {
7128         /* Undo animation damage if needed */
7129         DrawPosition(FALSE, NULL);
7130     }
7131 }
7132
7133 int
7134 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7135 {   // front-end-free part taken out of PieceMenuPopup
7136     int whichMenu; int xSqr, ySqr;
7137
7138     if(seekGraphUp) { // [HGM] seekgraph
7139         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7140         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7141         return -2;
7142     }
7143
7144     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7145          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7146         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7147         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7148         if(action == Press)   {
7149             originalFlip = flipView;
7150             flipView = !flipView; // temporarily flip board to see game from partners perspective
7151             DrawPosition(TRUE, partnerBoard);
7152             DisplayMessage(partnerStatus, "");
7153             partnerUp = TRUE;
7154         } else if(action == Release) {
7155             flipView = originalFlip;
7156             DrawPosition(TRUE, boards[currentMove]);
7157             partnerUp = FALSE;
7158         }
7159         return -2;
7160     }
7161
7162     xSqr = EventToSquare(x, BOARD_WIDTH);
7163     ySqr = EventToSquare(y, BOARD_HEIGHT);
7164     if (action == Release) {
7165         if(pieceSweep != EmptySquare) {
7166             EditPositionMenuEvent(pieceSweep, toX, toY);
7167             pieceSweep = EmptySquare;
7168         } else UnLoadPV(); // [HGM] pv
7169     }
7170     if (action != Press) return -2; // return code to be ignored
7171     switch (gameMode) {
7172       case IcsExamining:
7173         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7174       case EditPosition:
7175         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7178         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7179         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7180         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7181         NextPiece(0);
7182         return 2; // grab
7183       case IcsObserving:
7184         if(!appData.icsEngineAnalyze) return -1;
7185       case IcsPlayingWhite:
7186       case IcsPlayingBlack:
7187         if(!appData.zippyPlay) goto noZip;
7188       case AnalyzeMode:
7189       case AnalyzeFile:
7190       case MachinePlaysWhite:
7191       case MachinePlaysBlack:
7192       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7193         if (!appData.dropMenu) {
7194           LoadPV(x, y);
7195           return 2; // flag front-end to grab mouse events
7196         }
7197         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7198            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7199       case EditGame:
7200       noZip:
7201         if (xSqr < 0 || ySqr < 0) return -1;
7202         if (!appData.dropMenu || appData.testLegality &&
7203             gameInfo.variant != VariantBughouse &&
7204             gameInfo.variant != VariantCrazyhouse) return -1;
7205         whichMenu = 1; // drop menu
7206         break;
7207       default:
7208         return -1;
7209     }
7210
7211     if (((*fromX = xSqr) < 0) ||
7212         ((*fromY = ySqr) < 0)) {
7213         *fromX = *fromY = -1;
7214         return -1;
7215     }
7216     if (flipView)
7217       *fromX = BOARD_WIDTH - 1 - *fromX;
7218     else
7219       *fromY = BOARD_HEIGHT - 1 - *fromY;
7220
7221     return whichMenu;
7222 }
7223
7224 void
7225 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7226 {
7227 //    char * hint = lastHint;
7228     FrontEndProgramStats stats;
7229
7230     stats.which = cps == &first ? 0 : 1;
7231     stats.depth = cpstats->depth;
7232     stats.nodes = cpstats->nodes;
7233     stats.score = cpstats->score;
7234     stats.time = cpstats->time;
7235     stats.pv = cpstats->movelist;
7236     stats.hint = lastHint;
7237     stats.an_move_index = 0;
7238     stats.an_move_count = 0;
7239
7240     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7241         stats.hint = cpstats->move_name;
7242         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7243         stats.an_move_count = cpstats->nr_moves;
7244     }
7245
7246     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
7247
7248     SetProgramStats( &stats );
7249 }
7250
7251 void
7252 ClearEngineOutputPane (int which)
7253 {
7254     static FrontEndProgramStats dummyStats;
7255     dummyStats.which = which;
7256     dummyStats.pv = "#";
7257     SetProgramStats( &dummyStats );
7258 }
7259
7260 #define MAXPLAYERS 500
7261
7262 char *
7263 TourneyStandings (int display)
7264 {
7265     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7266     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7267     char result, *p, *names[MAXPLAYERS];
7268
7269     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7270         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7271     names[0] = p = strdup(appData.participants);
7272     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7273
7274     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7275
7276     while(result = appData.results[nr]) {
7277         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7278         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7279         wScore = bScore = 0;
7280         switch(result) {
7281           case '+': wScore = 2; break;
7282           case '-': bScore = 2; break;
7283           case '=': wScore = bScore = 1; break;
7284           case ' ':
7285           case '*': return strdup("busy"); // tourney not finished
7286         }
7287         score[w] += wScore;
7288         score[b] += bScore;
7289         games[w]++;
7290         games[b]++;
7291         nr++;
7292     }
7293     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7294     for(w=0; w<nPlayers; w++) {
7295         bScore = -1;
7296         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7297         ranking[w] = b; points[w] = bScore; score[b] = -2;
7298     }
7299     p = malloc(nPlayers*34+1);
7300     for(w=0; w<nPlayers && w<display; w++)
7301         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7302     free(names[0]);
7303     return p;
7304 }
7305
7306 void
7307 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7308 {       // count all piece types
7309         int p, f, r;
7310         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7311         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7313                 p = board[r][f];
7314                 pCnt[p]++;
7315                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7316                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7317                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7318                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7319                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7320                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7321         }
7322 }
7323
7324 int
7325 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7326 {
7327         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7328         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7329
7330         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7331         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7332         if(myPawns == 2 && nMine == 3) // KPP
7333             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7334         if(myPawns == 1 && nMine == 2) // KP
7335             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7336         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7337             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7338         if(myPawns) return FALSE;
7339         if(pCnt[WhiteRook+side])
7340             return pCnt[BlackRook-side] ||
7341                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7342                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7343                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7344         if(pCnt[WhiteCannon+side]) {
7345             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7346             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7347         }
7348         if(pCnt[WhiteKnight+side])
7349             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7350         return FALSE;
7351 }
7352
7353 int
7354 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7355 {
7356         VariantClass v = gameInfo.variant;
7357
7358         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7359         if(v == VariantShatranj) return TRUE; // always winnable through baring
7360         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7361         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7362
7363         if(v == VariantXiangqi) {
7364                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7365
7366                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7367                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7368                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7369                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7370                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7371                 if(stale) // we have at least one last-rank P plus perhaps C
7372                     return majors // KPKX
7373                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7374                 else // KCA*E*
7375                     return pCnt[WhiteFerz+side] // KCAK
7376                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7377                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7378                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7379
7380         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7381                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7382
7383                 if(nMine == 1) return FALSE; // bare King
7384                 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
7385                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7386                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7387                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7388                 if(pCnt[WhiteKnight+side])
7389                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7390                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7391                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7392                 if(nBishops)
7393                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7394                 if(pCnt[WhiteAlfil+side])
7395                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7396                 if(pCnt[WhiteWazir+side])
7397                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7398         }
7399
7400         return TRUE;
7401 }
7402
7403 int
7404 CompareWithRights (Board b1, Board b2)
7405 {
7406     int rights = 0;
7407     if(!CompareBoards(b1, b2)) return FALSE;
7408     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7409     /* compare castling rights */
7410     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7411            rights++; /* King lost rights, while rook still had them */
7412     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7413         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7414            rights++; /* but at least one rook lost them */
7415     }
7416     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7417            rights++;
7418     if( b1[CASTLING][5] != NoRights ) {
7419         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7420            rights++;
7421     }
7422     return rights == 0;
7423 }
7424
7425 int
7426 Adjudicate (ChessProgramState *cps)
7427 {       // [HGM] some adjudications useful with buggy engines
7428         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7429         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7430         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7431         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7432         int k, count = 0; static int bare = 1;
7433         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7434         Boolean canAdjudicate = !appData.icsActive;
7435
7436         // most tests only when we understand the game, i.e. legality-checking on
7437             if( appData.testLegality )
7438             {   /* [HGM] Some more adjudications for obstinate engines */
7439                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7440                 static int moveCount = 6;
7441                 ChessMove result;
7442                 char *reason = NULL;
7443
7444                 /* Count what is on board. */
7445                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7446
7447                 /* Some material-based adjudications that have to be made before stalemate test */
7448                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7449                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7450                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7451                      if(canAdjudicate && appData.checkMates) {
7452                          if(engineOpponent)
7453                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7454                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7455                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7456                          return 1;
7457                      }
7458                 }
7459
7460                 /* Bare King in Shatranj (loses) or Losers (wins) */
7461                 if( nrW == 1 || nrB == 1) {
7462                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7463                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7464                      if(canAdjudicate && appData.checkMates) {
7465                          if(engineOpponent)
7466                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7467                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7468                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7469                          return 1;
7470                      }
7471                   } else
7472                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7473                   {    /* bare King */
7474                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7475                         if(canAdjudicate && appData.checkMates) {
7476                             /* but only adjudicate if adjudication enabled */
7477                             if(engineOpponent)
7478                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7479                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7480                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7481                             return 1;
7482                         }
7483                   }
7484                 } else bare = 1;
7485
7486
7487             // don't wait for engine to announce game end if we can judge ourselves
7488             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7489               case MT_CHECK:
7490                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7491                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7492                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7493                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7494                             checkCnt++;
7495                         if(checkCnt >= 2) {
7496                             reason = "Xboard adjudication: 3rd check";
7497                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7498                             break;
7499                         }
7500                     }
7501                 }
7502               case MT_NONE:
7503               default:
7504                 break;
7505               case MT_STALEMATE:
7506               case MT_STAINMATE:
7507                 reason = "Xboard adjudication: Stalemate";
7508                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7509                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7510                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7511                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7512                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7513                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7514                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7515                                                                         EP_CHECKMATE : EP_WINS);
7516                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7517                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7518                 }
7519                 break;
7520               case MT_CHECKMATE:
7521                 reason = "Xboard adjudication: Checkmate";
7522                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7523                 break;
7524             }
7525
7526                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7527                     case EP_STALEMATE:
7528                         result = GameIsDrawn; break;
7529                     case EP_CHECKMATE:
7530                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7531                     case EP_WINS:
7532                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7533                     default:
7534                         result = EndOfFile;
7535                 }
7536                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7537                     if(engineOpponent)
7538                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7539                     GameEnds( result, reason, GE_XBOARD );
7540                     return 1;
7541                 }
7542
7543                 /* Next absolutely insufficient mating material. */
7544                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7545                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7546                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7547
7548                      /* always flag draws, for judging claims */
7549                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7550
7551                      if(canAdjudicate && appData.materialDraws) {
7552                          /* but only adjudicate them if adjudication enabled */
7553                          if(engineOpponent) {
7554                            SendToProgram("force\n", engineOpponent); // suppress reply
7555                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7556                          }
7557                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7558                          return 1;
7559                      }
7560                 }
7561
7562                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7563                 if(gameInfo.variant == VariantXiangqi ?
7564                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7565                  : nrW + nrB == 4 &&
7566                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7567                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7568                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7569                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7570                    ) ) {
7571                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7572                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7573                           if(engineOpponent) {
7574                             SendToProgram("force\n", engineOpponent); // suppress reply
7575                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7576                           }
7577                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7578                           return 1;
7579                      }
7580                 } else moveCount = 6;
7581             }
7582
7583         // Repetition draws and 50-move rule can be applied independently of legality testing
7584
7585                 /* Check for rep-draws */
7586                 count = 0;
7587                 for(k = forwardMostMove-2;
7588                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7589                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7590                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7591                     k-=2)
7592                 {   int rights=0;
7593                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7594                         /* compare castling rights */
7595                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7596                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7597                                 rights++; /* King lost rights, while rook still had them */
7598                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7599                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7600                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7601                                    rights++; /* but at least one rook lost them */
7602                         }
7603                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7604                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7605                                 rights++;
7606                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7607                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7608                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7609                                    rights++;
7610                         }
7611                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7612                             && appData.drawRepeats > 1) {
7613                              /* adjudicate after user-specified nr of repeats */
7614                              int result = GameIsDrawn;
7615                              char *details = "XBoard adjudication: repetition draw";
7616                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7617                                 // [HGM] xiangqi: check for forbidden perpetuals
7618                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7619                                 for(m=forwardMostMove; m>k; m-=2) {
7620                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7621                                         ourPerpetual = 0; // the current mover did not always check
7622                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7623                                         hisPerpetual = 0; // the opponent did not always check
7624                                 }
7625                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7626                                                                         ourPerpetual, hisPerpetual);
7627                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7628                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7629                                     details = "Xboard adjudication: perpetual checking";
7630                                 } else
7631                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7632                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7633                                 } else
7634                                 // Now check for perpetual chases
7635                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7636                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7637                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7638                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7639                                         static char resdet[MSG_SIZ];
7640                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7641                                         details = resdet;
7642                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7643                                     } else
7644                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7645                                         break; // Abort repetition-checking loop.
7646                                 }
7647                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7648                              }
7649                              if(engineOpponent) {
7650                                SendToProgram("force\n", engineOpponent); // suppress reply
7651                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7652                              }
7653                              GameEnds( result, details, GE_XBOARD );
7654                              return 1;
7655                         }
7656                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7657                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7658                     }
7659                 }
7660
7661                 /* Now we test for 50-move draws. Determine ply count */
7662                 count = forwardMostMove;
7663                 /* look for last irreversble move */
7664                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7665                     count--;
7666                 /* if we hit starting position, add initial plies */
7667                 if( count == backwardMostMove )
7668                     count -= initialRulePlies;
7669                 count = forwardMostMove - count;
7670                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7671                         // adjust reversible move counter for checks in Xiangqi
7672                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7673                         if(i < backwardMostMove) i = backwardMostMove;
7674                         while(i <= forwardMostMove) {
7675                                 lastCheck = inCheck; // check evasion does not count
7676                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7677                                 if(inCheck || lastCheck) count--; // check does not count
7678                                 i++;
7679                         }
7680                 }
7681                 if( count >= 100)
7682                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7683                          /* this is used to judge if draw claims are legal */
7684                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7685                          if(engineOpponent) {
7686                            SendToProgram("force\n", engineOpponent); // suppress reply
7687                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7688                          }
7689                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7690                          return 1;
7691                 }
7692
7693                 /* if draw offer is pending, treat it as a draw claim
7694                  * when draw condition present, to allow engines a way to
7695                  * claim draws before making their move to avoid a race
7696                  * condition occurring after their move
7697                  */
7698                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7699                          char *p = NULL;
7700                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7701                              p = "Draw claim: 50-move rule";
7702                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7703                              p = "Draw claim: 3-fold repetition";
7704                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7705                              p = "Draw claim: insufficient mating material";
7706                          if( p != NULL && canAdjudicate) {
7707                              if(engineOpponent) {
7708                                SendToProgram("force\n", engineOpponent); // suppress reply
7709                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                              }
7711                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7712                              return 1;
7713                          }
7714                 }
7715
7716                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7717                     if(engineOpponent) {
7718                       SendToProgram("force\n", engineOpponent); // suppress reply
7719                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7720                     }
7721                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7722                     return 1;
7723                 }
7724         return 0;
7725 }
7726
7727 char *
7728 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7729 {   // [HGM] book: this routine intercepts moves to simulate book replies
7730     char *bookHit = NULL;
7731
7732     //first determine if the incoming move brings opponent into his book
7733     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7734         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7735     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7736     if(bookHit != NULL && !cps->bookSuspend) {
7737         // make sure opponent is not going to reply after receiving move to book position
7738         SendToProgram("force\n", cps);
7739         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7740     }
7741     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7742     // now arrange restart after book miss
7743     if(bookHit) {
7744         // after a book hit we never send 'go', and the code after the call to this routine
7745         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7746         char buf[MSG_SIZ], *move = bookHit;
7747         if(cps->useSAN) {
7748             int fromX, fromY, toX, toY;
7749             char promoChar;
7750             ChessMove moveType;
7751             move = buf + 30;
7752             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7753                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7754                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7755                                     PosFlags(forwardMostMove),
7756                                     fromY, fromX, toY, toX, promoChar, move);
7757             } else {
7758                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7759                 bookHit = NULL;
7760             }
7761         }
7762         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7763         SendToProgram(buf, cps);
7764         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7765     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7766         SendToProgram("go\n", cps);
7767         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7768     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7769         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7770             SendToProgram("go\n", cps);
7771         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7772     }
7773     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7774 }
7775
7776 char *savedMessage;
7777 ChessProgramState *savedState;
7778 void
7779 DeferredBookMove (void)
7780 {
7781         if(savedState->lastPing != savedState->lastPong)
7782                     ScheduleDelayedEvent(DeferredBookMove, 10);
7783         else
7784         HandleMachineMove(savedMessage, savedState);
7785 }
7786
7787 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7788
7789 void
7790 HandleMachineMove (char *message, ChessProgramState *cps)
7791 {
7792     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7793     char realname[MSG_SIZ];
7794     int fromX, fromY, toX, toY;
7795     ChessMove moveType;
7796     char promoChar;
7797     char *p, *pv=buf1;
7798     int machineWhite;
7799     char *bookHit;
7800
7801     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7802         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7803         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7804             DisplayError(_("Invalid pairing from pairing engine"), 0);
7805             return;
7806         }
7807         pairingReceived = 1;
7808         NextMatchGame();
7809         return; // Skim the pairing messages here.
7810     }
7811
7812     cps->userError = 0;
7813
7814 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7815     /*
7816      * Kludge to ignore BEL characters
7817      */
7818     while (*message == '\007') message++;
7819
7820     /*
7821      * [HGM] engine debug message: ignore lines starting with '#' character
7822      */
7823     if(cps->debug && *message == '#') return;
7824
7825     /*
7826      * Look for book output
7827      */
7828     if (cps == &first && bookRequested) {
7829         if (message[0] == '\t' || message[0] == ' ') {
7830             /* Part of the book output is here; append it */
7831             strcat(bookOutput, message);
7832             strcat(bookOutput, "  \n");
7833             return;
7834         } else if (bookOutput[0] != NULLCHAR) {
7835             /* All of book output has arrived; display it */
7836             char *p = bookOutput;
7837             while (*p != NULLCHAR) {
7838                 if (*p == '\t') *p = ' ';
7839                 p++;
7840             }
7841             DisplayInformation(bookOutput);
7842             bookRequested = FALSE;
7843             /* Fall through to parse the current output */
7844         }
7845     }
7846
7847     /*
7848      * Look for machine move.
7849      */
7850     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7851         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7852     {
7853         /* This method is only useful on engines that support ping */
7854         if (cps->lastPing != cps->lastPong) {
7855           if (gameMode == BeginningOfGame) {
7856             /* Extra move from before last new; ignore */
7857             if (appData.debugMode) {
7858                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7859             }
7860           } else {
7861             if (appData.debugMode) {
7862                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7863                         cps->which, gameMode);
7864             }
7865
7866             SendToProgram("undo\n", cps);
7867           }
7868           return;
7869         }
7870
7871         switch (gameMode) {
7872           case BeginningOfGame:
7873             /* Extra move from before last reset; ignore */
7874             if (appData.debugMode) {
7875                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7876             }
7877             return;
7878
7879           case EndOfGame:
7880           case IcsIdle:
7881           default:
7882             /* Extra move after we tried to stop.  The mode test is
7883                not a reliable way of detecting this problem, but it's
7884                the best we can do on engines that don't support ping.
7885             */
7886             if (appData.debugMode) {
7887                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7888                         cps->which, gameMode);
7889             }
7890             SendToProgram("undo\n", cps);
7891             return;
7892
7893           case MachinePlaysWhite:
7894           case IcsPlayingWhite:
7895             machineWhite = TRUE;
7896             break;
7897
7898           case MachinePlaysBlack:
7899           case IcsPlayingBlack:
7900             machineWhite = FALSE;
7901             break;
7902
7903           case TwoMachinesPlay:
7904             machineWhite = (cps->twoMachinesColor[0] == 'w');
7905             break;
7906         }
7907         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7908             if (appData.debugMode) {
7909                 fprintf(debugFP,
7910                         "Ignoring move out of turn by %s, gameMode %d"
7911                         ", forwardMost %d\n",
7912                         cps->which, gameMode, forwardMostMove);
7913             }
7914             return;
7915         }
7916
7917         if(cps->alphaRank) AlphaRank(machineMove, 4);
7918         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7919                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7920             /* Machine move could not be parsed; ignore it. */
7921           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7922                     machineMove, _(cps->which));
7923             DisplayError(buf1, 0);
7924             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7925                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7926             if (gameMode == TwoMachinesPlay) {
7927               GameEnds(machineWhite ? BlackWins : WhiteWins,
7928                        buf1, GE_XBOARD);
7929             }
7930             return;
7931         }
7932
7933         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7934         /* So we have to redo legality test with true e.p. status here,  */
7935         /* to make sure an illegal e.p. capture does not slip through,   */
7936         /* to cause a forfeit on a justified illegal-move complaint      */
7937         /* of the opponent.                                              */
7938         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7939            ChessMove moveType;
7940            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7941                              fromY, fromX, toY, toX, promoChar);
7942             if(moveType == IllegalMove) {
7943               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7944                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7945                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7946                            buf1, GE_XBOARD);
7947                 return;
7948            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7949            /* [HGM] Kludge to handle engines that send FRC-style castling
7950               when they shouldn't (like TSCP-Gothic) */
7951            switch(moveType) {
7952              case WhiteASideCastleFR:
7953              case BlackASideCastleFR:
7954                toX+=2;
7955                currentMoveString[2]++;
7956                break;
7957              case WhiteHSideCastleFR:
7958              case BlackHSideCastleFR:
7959                toX--;
7960                currentMoveString[2]--;
7961                break;
7962              default: ; // nothing to do, but suppresses warning of pedantic compilers
7963            }
7964         }
7965         hintRequested = FALSE;
7966         lastHint[0] = NULLCHAR;
7967         bookRequested = FALSE;
7968         /* Program may be pondering now */
7969         cps->maybeThinking = TRUE;
7970         if (cps->sendTime == 2) cps->sendTime = 1;
7971         if (cps->offeredDraw) cps->offeredDraw--;
7972
7973         /* [AS] Save move info*/
7974         pvInfoList[ forwardMostMove ].score = programStats.score;
7975         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7976         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7977
7978         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7979
7980         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7981         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7982             int count = 0;
7983
7984             while( count < adjudicateLossPlies ) {
7985                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7986
7987                 if( count & 1 ) {
7988                     score = -score; /* Flip score for winning side */
7989                 }
7990
7991                 if( score > adjudicateLossThreshold ) {
7992                     break;
7993                 }
7994
7995                 count++;
7996             }
7997
7998             if( count >= adjudicateLossPlies ) {
7999                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8000
8001                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8002                     "Xboard adjudication",
8003                     GE_XBOARD );
8004
8005                 return;
8006             }
8007         }
8008
8009         if(Adjudicate(cps)) {
8010             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8011             return; // [HGM] adjudicate: for all automatic game ends
8012         }
8013
8014 #if ZIPPY
8015         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8016             first.initDone) {
8017           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8018                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8019                 SendToICS("draw ");
8020                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8021           }
8022           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8023           ics_user_moved = 1;
8024           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8025                 char buf[3*MSG_SIZ];
8026
8027                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8028                         programStats.score / 100.,
8029                         programStats.depth,
8030                         programStats.time / 100.,
8031                         (unsigned int)programStats.nodes,
8032                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8033                         programStats.movelist);
8034                 SendToICS(buf);
8035 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8036           }
8037         }
8038 #endif
8039
8040         /* [AS] Clear stats for next move */
8041         ClearProgramStats();
8042         thinkOutput[0] = NULLCHAR;
8043         hiddenThinkOutputState = 0;
8044
8045         bookHit = NULL;
8046         if (gameMode == TwoMachinesPlay) {
8047             /* [HGM] relaying draw offers moved to after reception of move */
8048             /* and interpreting offer as claim if it brings draw condition */
8049             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8050                 SendToProgram("draw\n", cps->other);
8051             }
8052             if (cps->other->sendTime) {
8053                 SendTimeRemaining(cps->other,
8054                                   cps->other->twoMachinesColor[0] == 'w');
8055             }
8056             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8057             if (firstMove && !bookHit) {
8058                 firstMove = FALSE;
8059                 if (cps->other->useColors) {
8060                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8061                 }
8062                 SendToProgram("go\n", cps->other);
8063             }
8064             cps->other->maybeThinking = TRUE;
8065         }
8066
8067         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8068
8069         if (!pausing && appData.ringBellAfterMoves) {
8070             RingBell();
8071         }
8072
8073         /*
8074          * Reenable menu items that were disabled while
8075          * machine was thinking
8076          */
8077         if (gameMode != TwoMachinesPlay)
8078             SetUserThinkingEnables();
8079
8080         // [HGM] book: after book hit opponent has received move and is now in force mode
8081         // force the book reply into it, and then fake that it outputted this move by jumping
8082         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8083         if(bookHit) {
8084                 static char bookMove[MSG_SIZ]; // a bit generous?
8085
8086                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8087                 strcat(bookMove, bookHit);
8088                 message = bookMove;
8089                 cps = cps->other;
8090                 programStats.nodes = programStats.depth = programStats.time =
8091                 programStats.score = programStats.got_only_move = 0;
8092                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8093
8094                 if(cps->lastPing != cps->lastPong) {
8095                     savedMessage = message; // args for deferred call
8096                     savedState = cps;
8097                     ScheduleDelayedEvent(DeferredBookMove, 10);
8098                     return;
8099                 }
8100                 goto FakeBookMove;
8101         }
8102
8103         return;
8104     }
8105
8106     /* Set special modes for chess engines.  Later something general
8107      *  could be added here; for now there is just one kludge feature,
8108      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8109      *  when "xboard" is given as an interactive command.
8110      */
8111     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8112         cps->useSigint = FALSE;
8113         cps->useSigterm = FALSE;
8114     }
8115     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8116       ParseFeatures(message+8, cps);
8117       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8118     }
8119
8120     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8121                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8122       int dummy, s=6; char buf[MSG_SIZ];
8123       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8124       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8125       if(startedFromSetupPosition) return;
8126       ParseFEN(boards[0], &dummy, message+s);
8127       DrawPosition(TRUE, boards[0]);
8128       startedFromSetupPosition = TRUE;
8129       return;
8130     }
8131     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8132      * want this, I was asked to put it in, and obliged.
8133      */
8134     if (!strncmp(message, "setboard ", 9)) {
8135         Board initial_position;
8136
8137         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8138
8139         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8140             DisplayError(_("Bad FEN received from engine"), 0);
8141             return ;
8142         } else {
8143            Reset(TRUE, FALSE);
8144            CopyBoard(boards[0], initial_position);
8145            initialRulePlies = FENrulePlies;
8146            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8147            else gameMode = MachinePlaysBlack;
8148            DrawPosition(FALSE, boards[currentMove]);
8149         }
8150         return;
8151     }
8152
8153     /*
8154      * Look for communication commands
8155      */
8156     if (!strncmp(message, "telluser ", 9)) {
8157         if(message[9] == '\\' && message[10] == '\\')
8158             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8159         PlayTellSound();
8160         DisplayNote(message + 9);
8161         return;
8162     }
8163     if (!strncmp(message, "tellusererror ", 14)) {
8164         cps->userError = 1;
8165         if(message[14] == '\\' && message[15] == '\\')
8166             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8167         PlayTellSound();
8168         DisplayError(message + 14, 0);
8169         return;
8170     }
8171     if (!strncmp(message, "tellopponent ", 13)) {
8172       if (appData.icsActive) {
8173         if (loggedOn) {
8174           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8175           SendToICS(buf1);
8176         }
8177       } else {
8178         DisplayNote(message + 13);
8179       }
8180       return;
8181     }
8182     if (!strncmp(message, "tellothers ", 11)) {
8183       if (appData.icsActive) {
8184         if (loggedOn) {
8185           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8186           SendToICS(buf1);
8187         }
8188       }
8189       return;
8190     }
8191     if (!strncmp(message, "tellall ", 8)) {
8192       if (appData.icsActive) {
8193         if (loggedOn) {
8194           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8195           SendToICS(buf1);
8196         }
8197       } else {
8198         DisplayNote(message + 8);
8199       }
8200       return;
8201     }
8202     if (strncmp(message, "warning", 7) == 0) {
8203         /* Undocumented feature, use tellusererror in new code */
8204         DisplayError(message, 0);
8205         return;
8206     }
8207     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8208         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8209         strcat(realname, " query");
8210         AskQuestion(realname, buf2, buf1, cps->pr);
8211         return;
8212     }
8213     /* Commands from the engine directly to ICS.  We don't allow these to be
8214      *  sent until we are logged on. Crafty kibitzes have been known to
8215      *  interfere with the login process.
8216      */
8217     if (loggedOn) {
8218         if (!strncmp(message, "tellics ", 8)) {
8219             SendToICS(message + 8);
8220             SendToICS("\n");
8221             return;
8222         }
8223         if (!strncmp(message, "tellicsnoalias ", 15)) {
8224             SendToICS(ics_prefix);
8225             SendToICS(message + 15);
8226             SendToICS("\n");
8227             return;
8228         }
8229         /* The following are for backward compatibility only */
8230         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8231             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8232             SendToICS(ics_prefix);
8233             SendToICS(message);
8234             SendToICS("\n");
8235             return;
8236         }
8237     }
8238     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8239         return;
8240     }
8241     /*
8242      * If the move is illegal, cancel it and redraw the board.
8243      * Also deal with other error cases.  Matching is rather loose
8244      * here to accommodate engines written before the spec.
8245      */
8246     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8247         strncmp(message, "Error", 5) == 0) {
8248         if (StrStr(message, "name") ||
8249             StrStr(message, "rating") || StrStr(message, "?") ||
8250             StrStr(message, "result") || StrStr(message, "board") ||
8251             StrStr(message, "bk") || StrStr(message, "computer") ||
8252             StrStr(message, "variant") || StrStr(message, "hint") ||
8253             StrStr(message, "random") || StrStr(message, "depth") ||
8254             StrStr(message, "accepted")) {
8255             return;
8256         }
8257         if (StrStr(message, "protover")) {
8258           /* Program is responding to input, so it's apparently done
8259              initializing, and this error message indicates it is
8260              protocol version 1.  So we don't need to wait any longer
8261              for it to initialize and send feature commands. */
8262           FeatureDone(cps, 1);
8263           cps->protocolVersion = 1;
8264           return;
8265         }
8266         cps->maybeThinking = FALSE;
8267
8268         if (StrStr(message, "draw")) {
8269             /* Program doesn't have "draw" command */
8270             cps->sendDrawOffers = 0;
8271             return;
8272         }
8273         if (cps->sendTime != 1 &&
8274             (StrStr(message, "time") || StrStr(message, "otim"))) {
8275           /* Program apparently doesn't have "time" or "otim" command */
8276           cps->sendTime = 0;
8277           return;
8278         }
8279         if (StrStr(message, "analyze")) {
8280             cps->analysisSupport = FALSE;
8281             cps->analyzing = FALSE;
8282 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8283             EditGameEvent(); // [HGM] try to preserve loaded game
8284             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8285             DisplayError(buf2, 0);
8286             return;
8287         }
8288         if (StrStr(message, "(no matching move)st")) {
8289           /* Special kludge for GNU Chess 4 only */
8290           cps->stKludge = TRUE;
8291           SendTimeControl(cps, movesPerSession, timeControl,
8292                           timeIncrement, appData.searchDepth,
8293                           searchTime);
8294           return;
8295         }
8296         if (StrStr(message, "(no matching move)sd")) {
8297           /* Special kludge for GNU Chess 4 only */
8298           cps->sdKludge = TRUE;
8299           SendTimeControl(cps, movesPerSession, timeControl,
8300                           timeIncrement, appData.searchDepth,
8301                           searchTime);
8302           return;
8303         }
8304         if (!StrStr(message, "llegal")) {
8305             return;
8306         }
8307         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8308             gameMode == IcsIdle) return;
8309         if (forwardMostMove <= backwardMostMove) return;
8310         if (pausing) PauseEvent();
8311       if(appData.forceIllegal) {
8312             // [HGM] illegal: machine refused move; force position after move into it
8313           SendToProgram("force\n", cps);
8314           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8315                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8316                 // when black is to move, while there might be nothing on a2 or black
8317                 // might already have the move. So send the board as if white has the move.
8318                 // But first we must change the stm of the engine, as it refused the last move
8319                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8320                 if(WhiteOnMove(forwardMostMove)) {
8321                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8322                     SendBoard(cps, forwardMostMove); // kludgeless board
8323                 } else {
8324                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8325                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8326                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8327                 }
8328           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8329             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8330                  gameMode == TwoMachinesPlay)
8331               SendToProgram("go\n", cps);
8332             return;
8333       } else
8334         if (gameMode == PlayFromGameFile) {
8335             /* Stop reading this game file */
8336             gameMode = EditGame;
8337             ModeHighlight();
8338         }
8339         /* [HGM] illegal-move claim should forfeit game when Xboard */
8340         /* only passes fully legal moves                            */
8341         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8342             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8343                                 "False illegal-move claim", GE_XBOARD );
8344             return; // do not take back move we tested as valid
8345         }
8346         currentMove = forwardMostMove-1;
8347         DisplayMove(currentMove-1); /* before DisplayMoveError */
8348         SwitchClocks(forwardMostMove-1); // [HGM] race
8349         DisplayBothClocks();
8350         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8351                 parseList[currentMove], _(cps->which));
8352         DisplayMoveError(buf1);
8353         DrawPosition(FALSE, boards[currentMove]);
8354
8355         SetUserThinkingEnables();
8356         return;
8357     }
8358     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8359         /* Program has a broken "time" command that
8360            outputs a string not ending in newline.
8361            Don't use it. */
8362         cps->sendTime = 0;
8363     }
8364
8365     /*
8366      * If chess program startup fails, exit with an error message.
8367      * Attempts to recover here are futile. [HGM] Well, we try anyway
8368      */
8369     if ((StrStr(message, "unknown host") != NULL)
8370         || (StrStr(message, "No remote directory") != NULL)
8371         || (StrStr(message, "not found") != NULL)
8372         || (StrStr(message, "No such file") != NULL)
8373         || (StrStr(message, "can't alloc") != NULL)
8374         || (StrStr(message, "Permission denied") != NULL)) {
8375
8376         cps->maybeThinking = FALSE;
8377         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8378                 _(cps->which), cps->program, cps->host, message);
8379         RemoveInputSource(cps->isr);
8380         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8381             cps->isr = NULL;
8382             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8383             cps->pr = NoProc; 
8384             if(cps == &first) {
8385                 appData.noChessProgram = TRUE;
8386                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8387                 gameMode = BeginningOfGame; ModeHighlight();
8388                 SetNCPMode();
8389             }
8390             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8391             DisplayMessage("", ""); // erase waiting message
8392             DisplayError(buf1, 0);
8393         }
8394         return;
8395     }
8396
8397     /*
8398      * Look for hint output
8399      */
8400     if (sscanf(message, "Hint: %s", buf1) == 1) {
8401         if (cps == &first && hintRequested) {
8402             hintRequested = FALSE;
8403             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8404                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8405                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8406                                     PosFlags(forwardMostMove),
8407                                     fromY, fromX, toY, toX, promoChar, buf1);
8408                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8409                 DisplayInformation(buf2);
8410             } else {
8411                 /* Hint move could not be parsed!? */
8412               snprintf(buf2, sizeof(buf2),
8413                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8414                         buf1, _(cps->which));
8415                 DisplayError(buf2, 0);
8416             }
8417         } else {
8418           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8419         }
8420         return;
8421     }
8422
8423     /*
8424      * Ignore other messages if game is not in progress
8425      */
8426     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8427         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8428
8429     /*
8430      * look for win, lose, draw, or draw offer
8431      */
8432     if (strncmp(message, "1-0", 3) == 0) {
8433         char *p, *q, *r = "";
8434         p = strchr(message, '{');
8435         if (p) {
8436             q = strchr(p, '}');
8437             if (q) {
8438                 *q = NULLCHAR;
8439                 r = p + 1;
8440             }
8441         }
8442         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8443         return;
8444     } else if (strncmp(message, "0-1", 3) == 0) {
8445         char *p, *q, *r = "";
8446         p = strchr(message, '{');
8447         if (p) {
8448             q = strchr(p, '}');
8449             if (q) {
8450                 *q = NULLCHAR;
8451                 r = p + 1;
8452             }
8453         }
8454         /* Kludge for Arasan 4.1 bug */
8455         if (strcmp(r, "Black resigns") == 0) {
8456             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8457             return;
8458         }
8459         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8460         return;
8461     } else if (strncmp(message, "1/2", 3) == 0) {
8462         char *p, *q, *r = "";
8463         p = strchr(message, '{');
8464         if (p) {
8465             q = strchr(p, '}');
8466             if (q) {
8467                 *q = NULLCHAR;
8468                 r = p + 1;
8469             }
8470         }
8471
8472         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8473         return;
8474
8475     } else if (strncmp(message, "White resign", 12) == 0) {
8476         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strncmp(message, "Black resign", 12) == 0) {
8479         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8480         return;
8481     } else if (strncmp(message, "White matches", 13) == 0 ||
8482                strncmp(message, "Black matches", 13) == 0   ) {
8483         /* [HGM] ignore GNUShogi noises */
8484         return;
8485     } else if (strncmp(message, "White", 5) == 0 &&
8486                message[5] != '(' &&
8487                StrStr(message, "Black") == NULL) {
8488         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8489         return;
8490     } else if (strncmp(message, "Black", 5) == 0 &&
8491                message[5] != '(') {
8492         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8493         return;
8494     } else if (strcmp(message, "resign") == 0 ||
8495                strcmp(message, "computer resigns") == 0) {
8496         switch (gameMode) {
8497           case MachinePlaysBlack:
8498           case IcsPlayingBlack:
8499             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8500             break;
8501           case MachinePlaysWhite:
8502           case IcsPlayingWhite:
8503             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8504             break;
8505           case TwoMachinesPlay:
8506             if (cps->twoMachinesColor[0] == 'w')
8507               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8508             else
8509               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8510             break;
8511           default:
8512             /* can't happen */
8513             break;
8514         }
8515         return;
8516     } else if (strncmp(message, "opponent mates", 14) == 0) {
8517         switch (gameMode) {
8518           case MachinePlaysBlack:
8519           case IcsPlayingBlack:
8520             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8521             break;
8522           case MachinePlaysWhite:
8523           case IcsPlayingWhite:
8524             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8525             break;
8526           case TwoMachinesPlay:
8527             if (cps->twoMachinesColor[0] == 'w')
8528               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8529             else
8530               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8531             break;
8532           default:
8533             /* can't happen */
8534             break;
8535         }
8536         return;
8537     } else if (strncmp(message, "computer mates", 14) == 0) {
8538         switch (gameMode) {
8539           case MachinePlaysBlack:
8540           case IcsPlayingBlack:
8541             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8542             break;
8543           case MachinePlaysWhite:
8544           case IcsPlayingWhite:
8545             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8546             break;
8547           case TwoMachinesPlay:
8548             if (cps->twoMachinesColor[0] == 'w')
8549               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8550             else
8551               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8552             break;
8553           default:
8554             /* can't happen */
8555             break;
8556         }
8557         return;
8558     } else if (strncmp(message, "checkmate", 9) == 0) {
8559         if (WhiteOnMove(forwardMostMove)) {
8560             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8561         } else {
8562             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8563         }
8564         return;
8565     } else if (strstr(message, "Draw") != NULL ||
8566                strstr(message, "game is a draw") != NULL) {
8567         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8568         return;
8569     } else if (strstr(message, "offer") != NULL &&
8570                strstr(message, "draw") != NULL) {
8571 #if ZIPPY
8572         if (appData.zippyPlay && first.initDone) {
8573             /* Relay offer to ICS */
8574             SendToICS(ics_prefix);
8575             SendToICS("draw\n");
8576         }
8577 #endif
8578         cps->offeredDraw = 2; /* valid until this engine moves twice */
8579         if (gameMode == TwoMachinesPlay) {
8580             if (cps->other->offeredDraw) {
8581                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8582             /* [HGM] in two-machine mode we delay relaying draw offer      */
8583             /* until after we also have move, to see if it is really claim */
8584             }
8585         } else if (gameMode == MachinePlaysWhite ||
8586                    gameMode == MachinePlaysBlack) {
8587           if (userOfferedDraw) {
8588             DisplayInformation(_("Machine accepts your draw offer"));
8589             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8590           } else {
8591             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8592           }
8593         }
8594     }
8595
8596
8597     /*
8598      * Look for thinking output
8599      */
8600     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8601           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8602                                 ) {
8603         int plylev, mvleft, mvtot, curscore, time;
8604         char mvname[MOVE_LEN];
8605         u64 nodes; // [DM]
8606         char plyext;
8607         int ignore = FALSE;
8608         int prefixHint = FALSE;
8609         mvname[0] = NULLCHAR;
8610
8611         switch (gameMode) {
8612           case MachinePlaysBlack:
8613           case IcsPlayingBlack:
8614             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8615             break;
8616           case MachinePlaysWhite:
8617           case IcsPlayingWhite:
8618             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8619             break;
8620           case AnalyzeMode:
8621           case AnalyzeFile:
8622             break;
8623           case IcsObserving: /* [DM] icsEngineAnalyze */
8624             if (!appData.icsEngineAnalyze) ignore = TRUE;
8625             break;
8626           case TwoMachinesPlay:
8627             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8628                 ignore = TRUE;
8629             }
8630             break;
8631           default:
8632             ignore = TRUE;
8633             break;
8634         }
8635
8636         if (!ignore) {
8637             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8638             buf1[0] = NULLCHAR;
8639             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8640                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8641
8642                 if (plyext != ' ' && plyext != '\t') {
8643                     time *= 100;
8644                 }
8645
8646                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8647                 if( cps->scoreIsAbsolute &&
8648                     ( gameMode == MachinePlaysBlack ||
8649                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8650                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8651                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8652                      !WhiteOnMove(currentMove)
8653                     ) )
8654                 {
8655                     curscore = -curscore;
8656                 }
8657
8658                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8659
8660                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8661                         char buf[MSG_SIZ];
8662                         FILE *f;
8663                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8664                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8665                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8666                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8667                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8668                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8669                                 fclose(f);
8670                         } else DisplayError(_("failed writing PV"), 0);
8671                 }
8672
8673                 tempStats.depth = plylev;
8674                 tempStats.nodes = nodes;
8675                 tempStats.time = time;
8676                 tempStats.score = curscore;
8677                 tempStats.got_only_move = 0;
8678
8679                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8680                         int ticklen;
8681
8682                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8683                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8684                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8685                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8686                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8687                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8688                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8689                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8690                 }
8691
8692                 /* Buffer overflow protection */
8693                 if (pv[0] != NULLCHAR) {
8694                     if (strlen(pv) >= sizeof(tempStats.movelist)
8695                         && appData.debugMode) {
8696                         fprintf(debugFP,
8697                                 "PV is too long; using the first %u bytes.\n",
8698                                 (unsigned) sizeof(tempStats.movelist) - 1);
8699                     }
8700
8701                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8702                 } else {
8703                     sprintf(tempStats.movelist, " no PV\n");
8704                 }
8705
8706                 if (tempStats.seen_stat) {
8707                     tempStats.ok_to_send = 1;
8708                 }
8709
8710                 if (strchr(tempStats.movelist, '(') != NULL) {
8711                     tempStats.line_is_book = 1;
8712                     tempStats.nr_moves = 0;
8713                     tempStats.moves_left = 0;
8714                 } else {
8715                     tempStats.line_is_book = 0;
8716                 }
8717
8718                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8719                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8720
8721                 SendProgramStatsToFrontend( cps, &tempStats );
8722
8723                 /*
8724                     [AS] Protect the thinkOutput buffer from overflow... this
8725                     is only useful if buf1 hasn't overflowed first!
8726                 */
8727                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8728                          plylev,
8729                          (gameMode == TwoMachinesPlay ?
8730                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8731                          ((double) curscore) / 100.0,
8732                          prefixHint ? lastHint : "",
8733                          prefixHint ? " " : "" );
8734
8735                 if( buf1[0] != NULLCHAR ) {
8736                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8737
8738                     if( strlen(pv) > max_len ) {
8739                         if( appData.debugMode) {
8740                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8741                         }
8742                         pv[max_len+1] = '\0';
8743                     }
8744
8745                     strcat( thinkOutput, pv);
8746                 }
8747
8748                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8749                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8750                     DisplayMove(currentMove - 1);
8751                 }
8752                 return;
8753
8754             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8755                 /* crafty (9.25+) says "(only move) <move>"
8756                  * if there is only 1 legal move
8757                  */
8758                 sscanf(p, "(only move) %s", buf1);
8759                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8760                 sprintf(programStats.movelist, "%s (only move)", buf1);
8761                 programStats.depth = 1;
8762                 programStats.nr_moves = 1;
8763                 programStats.moves_left = 1;
8764                 programStats.nodes = 1;
8765                 programStats.time = 1;
8766                 programStats.got_only_move = 1;
8767
8768                 /* Not really, but we also use this member to
8769                    mean "line isn't going to change" (Crafty
8770                    isn't searching, so stats won't change) */
8771                 programStats.line_is_book = 1;
8772
8773                 SendProgramStatsToFrontend( cps, &programStats );
8774
8775                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8776                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8777                     DisplayMove(currentMove - 1);
8778                 }
8779                 return;
8780             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8781                               &time, &nodes, &plylev, &mvleft,
8782                               &mvtot, mvname) >= 5) {
8783                 /* The stat01: line is from Crafty (9.29+) in response
8784                    to the "." command */
8785                 programStats.seen_stat = 1;
8786                 cps->maybeThinking = TRUE;
8787
8788                 if (programStats.got_only_move || !appData.periodicUpdates)
8789                   return;
8790
8791                 programStats.depth = plylev;
8792                 programStats.time = time;
8793                 programStats.nodes = nodes;
8794                 programStats.moves_left = mvleft;
8795                 programStats.nr_moves = mvtot;
8796                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8797                 programStats.ok_to_send = 1;
8798                 programStats.movelist[0] = '\0';
8799
8800                 SendProgramStatsToFrontend( cps, &programStats );
8801
8802                 return;
8803
8804             } else if (strncmp(message,"++",2) == 0) {
8805                 /* Crafty 9.29+ outputs this */
8806                 programStats.got_fail = 2;
8807                 return;
8808
8809             } else if (strncmp(message,"--",2) == 0) {
8810                 /* Crafty 9.29+ outputs this */
8811                 programStats.got_fail = 1;
8812                 return;
8813
8814             } else if (thinkOutput[0] != NULLCHAR &&
8815                        strncmp(message, "    ", 4) == 0) {
8816                 unsigned message_len;
8817
8818                 p = message;
8819                 while (*p && *p == ' ') p++;
8820
8821                 message_len = strlen( p );
8822
8823                 /* [AS] Avoid buffer overflow */
8824                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8825                     strcat(thinkOutput, " ");
8826                     strcat(thinkOutput, p);
8827                 }
8828
8829                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8830                     strcat(programStats.movelist, " ");
8831                     strcat(programStats.movelist, p);
8832                 }
8833
8834                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8835                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8836                     DisplayMove(currentMove - 1);
8837                 }
8838                 return;
8839             }
8840         }
8841         else {
8842             buf1[0] = NULLCHAR;
8843
8844             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8845                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8846             {
8847                 ChessProgramStats cpstats;
8848
8849                 if (plyext != ' ' && plyext != '\t') {
8850                     time *= 100;
8851                 }
8852
8853                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8854                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8855                     curscore = -curscore;
8856                 }
8857
8858                 cpstats.depth = plylev;
8859                 cpstats.nodes = nodes;
8860                 cpstats.time = time;
8861                 cpstats.score = curscore;
8862                 cpstats.got_only_move = 0;
8863                 cpstats.movelist[0] = '\0';
8864
8865                 if (buf1[0] != NULLCHAR) {
8866                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8867                 }
8868
8869                 cpstats.ok_to_send = 0;
8870                 cpstats.line_is_book = 0;
8871                 cpstats.nr_moves = 0;
8872                 cpstats.moves_left = 0;
8873
8874                 SendProgramStatsToFrontend( cps, &cpstats );
8875             }
8876         }
8877     }
8878 }
8879
8880
8881 /* Parse a game score from the character string "game", and
8882    record it as the history of the current game.  The game
8883    score is NOT assumed to start from the standard position.
8884    The display is not updated in any way.
8885    */
8886 void
8887 ParseGameHistory (char *game)
8888 {
8889     ChessMove moveType;
8890     int fromX, fromY, toX, toY, boardIndex;
8891     char promoChar;
8892     char *p, *q;
8893     char buf[MSG_SIZ];
8894
8895     if (appData.debugMode)
8896       fprintf(debugFP, "Parsing game history: %s\n", game);
8897
8898     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8899     gameInfo.site = StrSave(appData.icsHost);
8900     gameInfo.date = PGNDate();
8901     gameInfo.round = StrSave("-");
8902
8903     /* Parse out names of players */
8904     while (*game == ' ') game++;
8905     p = buf;
8906     while (*game != ' ') *p++ = *game++;
8907     *p = NULLCHAR;
8908     gameInfo.white = StrSave(buf);
8909     while (*game == ' ') game++;
8910     p = buf;
8911     while (*game != ' ' && *game != '\n') *p++ = *game++;
8912     *p = NULLCHAR;
8913     gameInfo.black = StrSave(buf);
8914
8915     /* Parse moves */
8916     boardIndex = blackPlaysFirst ? 1 : 0;
8917     yynewstr(game);
8918     for (;;) {
8919         yyboardindex = boardIndex;
8920         moveType = (ChessMove) Myylex();
8921         switch (moveType) {
8922           case IllegalMove:             /* maybe suicide chess, etc. */
8923   if (appData.debugMode) {
8924     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8925     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8926     setbuf(debugFP, NULL);
8927   }
8928           case WhitePromotion:
8929           case BlackPromotion:
8930           case WhiteNonPromotion:
8931           case BlackNonPromotion:
8932           case NormalMove:
8933           case WhiteCapturesEnPassant:
8934           case BlackCapturesEnPassant:
8935           case WhiteKingSideCastle:
8936           case WhiteQueenSideCastle:
8937           case BlackKingSideCastle:
8938           case BlackQueenSideCastle:
8939           case WhiteKingSideCastleWild:
8940           case WhiteQueenSideCastleWild:
8941           case BlackKingSideCastleWild:
8942           case BlackQueenSideCastleWild:
8943           /* PUSH Fabien */
8944           case WhiteHSideCastleFR:
8945           case WhiteASideCastleFR:
8946           case BlackHSideCastleFR:
8947           case BlackASideCastleFR:
8948           /* POP Fabien */
8949             fromX = currentMoveString[0] - AAA;
8950             fromY = currentMoveString[1] - ONE;
8951             toX = currentMoveString[2] - AAA;
8952             toY = currentMoveString[3] - ONE;
8953             promoChar = currentMoveString[4];
8954             break;
8955           case WhiteDrop:
8956           case BlackDrop:
8957             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8958             fromX = moveType == WhiteDrop ?
8959               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8960             (int) CharToPiece(ToLower(currentMoveString[0]));
8961             fromY = DROP_RANK;
8962             toX = currentMoveString[2] - AAA;
8963             toY = currentMoveString[3] - ONE;
8964             promoChar = NULLCHAR;
8965             break;
8966           case AmbiguousMove:
8967             /* bug? */
8968             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8969   if (appData.debugMode) {
8970     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8972     setbuf(debugFP, NULL);
8973   }
8974             DisplayError(buf, 0);
8975             return;
8976           case ImpossibleMove:
8977             /* bug? */
8978             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8979   if (appData.debugMode) {
8980     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8981     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8982     setbuf(debugFP, NULL);
8983   }
8984             DisplayError(buf, 0);
8985             return;
8986           case EndOfFile:
8987             if (boardIndex < backwardMostMove) {
8988                 /* Oops, gap.  How did that happen? */
8989                 DisplayError(_("Gap in move list"), 0);
8990                 return;
8991             }
8992             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8993             if (boardIndex > forwardMostMove) {
8994                 forwardMostMove = boardIndex;
8995             }
8996             return;
8997           case ElapsedTime:
8998             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8999                 strcat(parseList[boardIndex-1], " ");
9000                 strcat(parseList[boardIndex-1], yy_text);
9001             }
9002             continue;
9003           case Comment:
9004           case PGNTag:
9005           case NAG:
9006           default:
9007             /* ignore */
9008             continue;
9009           case WhiteWins:
9010           case BlackWins:
9011           case GameIsDrawn:
9012           case GameUnfinished:
9013             if (gameMode == IcsExamining) {
9014                 if (boardIndex < backwardMostMove) {
9015                     /* Oops, gap.  How did that happen? */
9016                     return;
9017                 }
9018                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9019                 return;
9020             }
9021             gameInfo.result = moveType;
9022             p = strchr(yy_text, '{');
9023             if (p == NULL) p = strchr(yy_text, '(');
9024             if (p == NULL) {
9025                 p = yy_text;
9026                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9027             } else {
9028                 q = strchr(p, *p == '{' ? '}' : ')');
9029                 if (q != NULL) *q = NULLCHAR;
9030                 p++;
9031             }
9032             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9033             gameInfo.resultDetails = StrSave(p);
9034             continue;
9035         }
9036         if (boardIndex >= forwardMostMove &&
9037             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9038             backwardMostMove = blackPlaysFirst ? 1 : 0;
9039             return;
9040         }
9041         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9042                                  fromY, fromX, toY, toX, promoChar,
9043                                  parseList[boardIndex]);
9044         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9045         /* currentMoveString is set as a side-effect of yylex */
9046         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9047         strcat(moveList[boardIndex], "\n");
9048         boardIndex++;
9049         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9050         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9051           case MT_NONE:
9052           case MT_STALEMATE:
9053           default:
9054             break;
9055           case MT_CHECK:
9056             if(gameInfo.variant != VariantShogi)
9057                 strcat(parseList[boardIndex - 1], "+");
9058             break;
9059           case MT_CHECKMATE:
9060           case MT_STAINMATE:
9061             strcat(parseList[boardIndex - 1], "#");
9062             break;
9063         }
9064     }
9065 }
9066
9067
9068 /* Apply a move to the given board  */
9069 void
9070 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9071 {
9072   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9073   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9074
9075     /* [HGM] compute & store e.p. status and castling rights for new position */
9076     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9077
9078       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9079       oldEP = (signed char)board[EP_STATUS];
9080       board[EP_STATUS] = EP_NONE;
9081
9082   if (fromY == DROP_RANK) {
9083         /* must be first */
9084         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9085             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9086             return;
9087         }
9088         piece = board[toY][toX] = (ChessSquare) fromX;
9089   } else {
9090       int i;
9091
9092       if( board[toY][toX] != EmptySquare )
9093            board[EP_STATUS] = EP_CAPTURE;
9094
9095       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9096            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9097                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9098       } else
9099       if( board[fromY][fromX] == WhitePawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY==2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110       } else
9111       if( board[fromY][fromX] == BlackPawn ) {
9112            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9113                board[EP_STATUS] = EP_PAWN_MOVE;
9114            if( toY-fromY== -2) {
9115                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9116                         gameInfo.variant != VariantBerolina || toX < fromX)
9117                       board[EP_STATUS] = toX | berolina;
9118                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9119                         gameInfo.variant != VariantBerolina || toX > fromX)
9120                       board[EP_STATUS] = toX;
9121            }
9122        }
9123
9124        for(i=0; i<nrCastlingRights; i++) {
9125            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9126               board[CASTLING][i] == toX   && castlingRank[i] == toY
9127              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9128        }
9129
9130      if (fromX == toX && fromY == toY) return;
9131
9132      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9133      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9134      if(gameInfo.variant == VariantKnightmate)
9135          king += (int) WhiteUnicorn - (int) WhiteKing;
9136
9137     /* Code added by Tord: */
9138     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9139     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9140         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == WhiteRook)) {
9144         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9145       } else {
9146         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9147       }
9148     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9149                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9150       board[fromY][fromX] = EmptySquare;
9151       board[toY][toX] = EmptySquare;
9152       if((toX > fromX) != (piece == BlackRook)) {
9153         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9154       } else {
9155         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9156       }
9157     /* End of code added by Tord */
9158
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161         && toY == fromY && toX > fromX+1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9165         board[fromY][BOARD_RGHT-1] = EmptySquare;
9166     } else if (board[fromY][fromX] == king
9167         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9168                && toY == fromY && toX < fromX-1) {
9169         board[fromY][fromX] = EmptySquare;
9170         board[toY][toX] = king;
9171         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9172         board[fromY][BOARD_LEFT] = EmptySquare;
9173     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9174                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9175                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9176                ) {
9177         /* white pawn promotion */
9178         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9179         if(gameInfo.variant==VariantBughouse ||
9180            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9181             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9182         board[fromY][fromX] = EmptySquare;
9183     } else if ((fromY >= BOARD_HEIGHT>>1)
9184                && (toX != fromX)
9185                && gameInfo.variant != VariantXiangqi
9186                && gameInfo.variant != VariantBerolina
9187                && (board[fromY][fromX] == WhitePawn)
9188                && (board[toY][toX] == EmptySquare)) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = WhitePawn;
9191         captured = board[toY - 1][toX];
9192         board[toY - 1][toX] = EmptySquare;
9193     } else if ((fromY == BOARD_HEIGHT-4)
9194                && (toX == fromX)
9195                && gameInfo.variant == VariantBerolina
9196                && (board[fromY][fromX] == WhitePawn)
9197                && (board[toY][toX] == EmptySquare)) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = WhitePawn;
9200         if(oldEP & EP_BEROLIN_A) {
9201                 captured = board[fromY][fromX-1];
9202                 board[fromY][fromX-1] = EmptySquare;
9203         }else{  captured = board[fromY][fromX+1];
9204                 board[fromY][fromX+1] = EmptySquare;
9205         }
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX > fromX+1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9212         board[fromY][BOARD_RGHT-1] = EmptySquare;
9213     } else if (board[fromY][fromX] == king
9214         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9215                && toY == fromY && toX < fromX-1) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = king;
9218         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9219         board[fromY][BOARD_LEFT] = EmptySquare;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 5) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][7] = EmptySquare;
9226         board[toY][4] = BlackRook;
9227     } else if (fromY == 7 && fromX == 3
9228                && board[fromY][fromX] == BlackKing
9229                && toY == 7 && toX == 1) {
9230         board[fromY][fromX] = EmptySquare;
9231         board[toY][toX] = BlackKing;
9232         board[fromY][0] = EmptySquare;
9233         board[toY][2] = BlackRook;
9234     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9235                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9236                && toY < promoRank && promoChar
9237                ) {
9238         /* black pawn promotion */
9239         board[toY][toX] = CharToPiece(ToLower(promoChar));
9240         if(gameInfo.variant==VariantBughouse ||
9241            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9242             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9243         board[fromY][fromX] = EmptySquare;
9244     } else if ((fromY < BOARD_HEIGHT>>1)
9245                && (toX != fromX)
9246                && gameInfo.variant != VariantXiangqi
9247                && gameInfo.variant != VariantBerolina
9248                && (board[fromY][fromX] == BlackPawn)
9249                && (board[toY][toX] == EmptySquare)) {
9250         board[fromY][fromX] = EmptySquare;
9251         board[toY][toX] = BlackPawn;
9252         captured = board[toY + 1][toX];
9253         board[toY + 1][toX] = EmptySquare;
9254     } else if ((fromY == 3)
9255                && (toX == fromX)
9256                && gameInfo.variant == VariantBerolina
9257                && (board[fromY][fromX] == BlackPawn)
9258                && (board[toY][toX] == EmptySquare)) {
9259         board[fromY][fromX] = EmptySquare;
9260         board[toY][toX] = BlackPawn;
9261         if(oldEP & EP_BEROLIN_A) {
9262                 captured = board[fromY][fromX-1];
9263                 board[fromY][fromX-1] = EmptySquare;
9264         }else{  captured = board[fromY][fromX+1];
9265                 board[fromY][fromX+1] = EmptySquare;
9266         }
9267     } else {
9268         board[toY][toX] = board[fromY][fromX];
9269         board[fromY][fromX] = EmptySquare;
9270     }
9271   }
9272
9273     if (gameInfo.holdingsWidth != 0) {
9274
9275       /* !!A lot more code needs to be written to support holdings  */
9276       /* [HGM] OK, so I have written it. Holdings are stored in the */
9277       /* penultimate board files, so they are automaticlly stored   */
9278       /* in the game history.                                       */
9279       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9280                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9281         /* Delete from holdings, by decreasing count */
9282         /* and erasing image if necessary            */
9283         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9284         if(p < (int) BlackPawn) { /* white drop */
9285              p -= (int)WhitePawn;
9286                  p = PieceToNumber((ChessSquare)p);
9287              if(p >= gameInfo.holdingsSize) p = 0;
9288              if(--board[p][BOARD_WIDTH-2] <= 0)
9289                   board[p][BOARD_WIDTH-1] = EmptySquare;
9290              if((int)board[p][BOARD_WIDTH-2] < 0)
9291                         board[p][BOARD_WIDTH-2] = 0;
9292         } else {                  /* black drop */
9293              p -= (int)BlackPawn;
9294                  p = PieceToNumber((ChessSquare)p);
9295              if(p >= gameInfo.holdingsSize) p = 0;
9296              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9297                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9298              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9299                         board[BOARD_HEIGHT-1-p][1] = 0;
9300         }
9301       }
9302       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9303           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9304         /* [HGM] holdings: Add to holdings, if holdings exist */
9305         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9306                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9307                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9308         }
9309         p = (int) captured;
9310         if (p >= (int) BlackPawn) {
9311           p -= (int)BlackPawn;
9312           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9313                   /* in Shogi restore piece to its original  first */
9314                   captured = (ChessSquare) (DEMOTED captured);
9315                   p = DEMOTED p;
9316           }
9317           p = PieceToNumber((ChessSquare)p);
9318           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9319           board[p][BOARD_WIDTH-2]++;
9320           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9321         } else {
9322           p -= (int)WhitePawn;
9323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9324                   captured = (ChessSquare) (DEMOTED captured);
9325                   p = DEMOTED p;
9326           }
9327           p = PieceToNumber((ChessSquare)p);
9328           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9329           board[BOARD_HEIGHT-1-p][1]++;
9330           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9331         }
9332       }
9333     } else if (gameInfo.variant == VariantAtomic) {
9334       if (captured != EmptySquare) {
9335         int y, x;
9336         for (y = toY-1; y <= toY+1; y++) {
9337           for (x = toX-1; x <= toX+1; x++) {
9338             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9339                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9340               board[y][x] = EmptySquare;
9341             }
9342           }
9343         }
9344         board[toY][toX] = EmptySquare;
9345       }
9346     }
9347     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9348         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9349     } else
9350     if(promoChar == '+') {
9351         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9352         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9353     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9354         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9355     }
9356     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9357                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9358         // [HGM] superchess: take promotion piece out of holdings
9359         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9360         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9361             if(!--board[k][BOARD_WIDTH-2])
9362                 board[k][BOARD_WIDTH-1] = EmptySquare;
9363         } else {
9364             if(!--board[BOARD_HEIGHT-1-k][1])
9365                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9366         }
9367     }
9368
9369 }
9370
9371 /* Updates forwardMostMove */
9372 void
9373 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9374 {
9375 //    forwardMostMove++; // [HGM] bare: moved downstream
9376
9377     (void) CoordsToAlgebraic(boards[forwardMostMove],
9378                              PosFlags(forwardMostMove),
9379                              fromY, fromX, toY, toX, promoChar,
9380                              parseList[forwardMostMove]);
9381
9382     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9383         int timeLeft; static int lastLoadFlag=0; int king, piece;
9384         piece = boards[forwardMostMove][fromY][fromX];
9385         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9386         if(gameInfo.variant == VariantKnightmate)
9387             king += (int) WhiteUnicorn - (int) WhiteKing;
9388         if(forwardMostMove == 0) {
9389             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9390                 fprintf(serverMoves, "%s;", UserName());
9391             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9392                 fprintf(serverMoves, "%s;", second.tidy);
9393             fprintf(serverMoves, "%s;", first.tidy);
9394             if(gameMode == MachinePlaysWhite)
9395                 fprintf(serverMoves, "%s;", UserName());
9396             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9397                 fprintf(serverMoves, "%s;", second.tidy);
9398         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9399         lastLoadFlag = loadFlag;
9400         // print base move
9401         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9402         // print castling suffix
9403         if( toY == fromY && piece == king ) {
9404             if(toX-fromX > 1)
9405                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9406             if(fromX-toX >1)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9408         }
9409         // e.p. suffix
9410         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9411              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9412              boards[forwardMostMove][toY][toX] == EmptySquare
9413              && fromX != toX && fromY != toY)
9414                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9415         // promotion suffix
9416         if(promoChar != NULLCHAR)
9417                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9418         if(!loadFlag) {
9419                 char buf[MOVE_LEN*2], *p; int len;
9420             fprintf(serverMoves, "/%d/%d",
9421                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9422             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9423             else                      timeLeft = blackTimeRemaining/1000;
9424             fprintf(serverMoves, "/%d", timeLeft);
9425                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9426                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9427                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9428             fprintf(serverMoves, "/%s", buf);
9429         }
9430         fflush(serverMoves);
9431     }
9432
9433     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9434         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9435       return;
9436     }
9437     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9438     if (commentList[forwardMostMove+1] != NULL) {
9439         free(commentList[forwardMostMove+1]);
9440         commentList[forwardMostMove+1] = NULL;
9441     }
9442     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9443     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9444     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9445     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9446     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9447     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9448     adjustedClock = FALSE;
9449     gameInfo.result = GameUnfinished;
9450     if (gameInfo.resultDetails != NULL) {
9451         free(gameInfo.resultDetails);
9452         gameInfo.resultDetails = NULL;
9453     }
9454     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9455                               moveList[forwardMostMove - 1]);
9456     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9457       case MT_NONE:
9458       case MT_STALEMATE:
9459       default:
9460         break;
9461       case MT_CHECK:
9462         if(gameInfo.variant != VariantShogi)
9463             strcat(parseList[forwardMostMove - 1], "+");
9464         break;
9465       case MT_CHECKMATE:
9466       case MT_STAINMATE:
9467         strcat(parseList[forwardMostMove - 1], "#");
9468         break;
9469     }
9470
9471 }
9472
9473 /* Updates currentMove if not pausing */
9474 void
9475 ShowMove (int fromX, int fromY, int toX, int toY)
9476 {
9477     int instant = (gameMode == PlayFromGameFile) ?
9478         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9479     if(appData.noGUI) return;
9480     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9481         if (!instant) {
9482             if (forwardMostMove == currentMove + 1) {
9483                 AnimateMove(boards[forwardMostMove - 1],
9484                             fromX, fromY, toX, toY);
9485             }
9486             if (appData.highlightLastMove) {
9487                 SetHighlights(fromX, fromY, toX, toY);
9488             }
9489         }
9490         currentMove = forwardMostMove;
9491     }
9492
9493     if (instant) return;
9494
9495     DisplayMove(currentMove - 1);
9496     DrawPosition(FALSE, boards[currentMove]);
9497     DisplayBothClocks();
9498     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9499 }
9500
9501 void
9502 SendEgtPath (ChessProgramState *cps)
9503 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9504         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9505
9506         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9507
9508         while(*p) {
9509             char c, *q = name+1, *r, *s;
9510
9511             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9512             while(*p && *p != ',') *q++ = *p++;
9513             *q++ = ':'; *q = 0;
9514             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9515                 strcmp(name, ",nalimov:") == 0 ) {
9516                 // take nalimov path from the menu-changeable option first, if it is defined
9517               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9518                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9519             } else
9520             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9521                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9522                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9523                 s = r = StrStr(s, ":") + 1; // beginning of path info
9524                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9525                 c = *r; *r = 0;             // temporarily null-terminate path info
9526                     *--q = 0;               // strip of trailig ':' from name
9527                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9528                 *r = c;
9529                 SendToProgram(buf,cps);     // send egtbpath command for this format
9530             }
9531             if(*p == ',') p++; // read away comma to position for next format name
9532         }
9533 }
9534
9535 void
9536 InitChessProgram (ChessProgramState *cps, int setup)
9537 /* setup needed to setup FRC opening position */
9538 {
9539     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9540     if (appData.noChessProgram) return;
9541     hintRequested = FALSE;
9542     bookRequested = FALSE;
9543
9544     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9545     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9546     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9547     if(cps->memSize) { /* [HGM] memory */
9548       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9549         SendToProgram(buf, cps);
9550     }
9551     SendEgtPath(cps); /* [HGM] EGT */
9552     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9553       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9554         SendToProgram(buf, cps);
9555     }
9556
9557     SendToProgram(cps->initString, cps);
9558     if (gameInfo.variant != VariantNormal &&
9559         gameInfo.variant != VariantLoadable
9560         /* [HGM] also send variant if board size non-standard */
9561         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9562                                             ) {
9563       char *v = VariantName(gameInfo.variant);
9564       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9565         /* [HGM] in protocol 1 we have to assume all variants valid */
9566         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9567         DisplayFatalError(buf, 0, 1);
9568         return;
9569       }
9570
9571       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9572       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9573       if( gameInfo.variant == VariantXiangqi )
9574            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9575       if( gameInfo.variant == VariantShogi )
9576            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9577       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9579       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9580           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9582       if( gameInfo.variant == VariantCourier )
9583            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9584       if( gameInfo.variant == VariantSuper )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9586       if( gameInfo.variant == VariantGreat )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9588       if( gameInfo.variant == VariantSChess )
9589            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9590       if( gameInfo.variant == VariantGrand )
9591            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9592
9593       if(overruled) {
9594         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9595                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9596            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9597            if(StrStr(cps->variants, b) == NULL) {
9598                // specific sized variant not known, check if general sizing allowed
9599                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9600                    if(StrStr(cps->variants, "boardsize") == NULL) {
9601                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9602                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9603                        DisplayFatalError(buf, 0, 1);
9604                        return;
9605                    }
9606                    /* [HGM] here we really should compare with the maximum supported board size */
9607                }
9608            }
9609       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9610       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9611       SendToProgram(buf, cps);
9612     }
9613     currentlyInitializedVariant = gameInfo.variant;
9614
9615     /* [HGM] send opening position in FRC to first engine */
9616     if(setup) {
9617           SendToProgram("force\n", cps);
9618           SendBoard(cps, 0);
9619           /* engine is now in force mode! Set flag to wake it up after first move. */
9620           setboardSpoiledMachineBlack = 1;
9621     }
9622
9623     if (cps->sendICS) {
9624       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9625       SendToProgram(buf, cps);
9626     }
9627     cps->maybeThinking = FALSE;
9628     cps->offeredDraw = 0;
9629     if (!appData.icsActive) {
9630         SendTimeControl(cps, movesPerSession, timeControl,
9631                         timeIncrement, appData.searchDepth,
9632                         searchTime);
9633     }
9634     if (appData.showThinking
9635         // [HGM] thinking: four options require thinking output to be sent
9636         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9637                                 ) {
9638         SendToProgram("post\n", cps);
9639     }
9640     SendToProgram("hard\n", cps);
9641     if (!appData.ponderNextMove) {
9642         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9643            it without being sure what state we are in first.  "hard"
9644            is not a toggle, so that one is OK.
9645          */
9646         SendToProgram("easy\n", cps);
9647     }
9648     if (cps->usePing) {
9649       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9650       SendToProgram(buf, cps);
9651     }
9652     cps->initDone = TRUE;
9653     ClearEngineOutputPane(cps == &second);
9654 }
9655
9656
9657 void
9658 StartChessProgram (ChessProgramState *cps)
9659 {
9660     char buf[MSG_SIZ];
9661     int err;
9662
9663     if (appData.noChessProgram) return;
9664     cps->initDone = FALSE;
9665
9666     if (strcmp(cps->host, "localhost") == 0) {
9667         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9668     } else if (*appData.remoteShell == NULLCHAR) {
9669         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9670     } else {
9671         if (*appData.remoteUser == NULLCHAR) {
9672           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9673                     cps->program);
9674         } else {
9675           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9676                     cps->host, appData.remoteUser, cps->program);
9677         }
9678         err = StartChildProcess(buf, "", &cps->pr);
9679     }
9680
9681     if (err != 0) {
9682       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9683         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9684         if(cps != &first) return;
9685         appData.noChessProgram = TRUE;
9686         ThawUI();
9687         SetNCPMode();
9688 //      DisplayFatalError(buf, err, 1);
9689 //      cps->pr = NoProc;
9690 //      cps->isr = NULL;
9691         return;
9692     }
9693
9694     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9695     if (cps->protocolVersion > 1) {
9696       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9697       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9698       cps->comboCnt = 0;  //                and values of combo boxes
9699       SendToProgram(buf, cps);
9700     } else {
9701       SendToProgram("xboard\n", cps);
9702     }
9703 }
9704
9705 void
9706 TwoMachinesEventIfReady P((void))
9707 {
9708   static int curMess = 0;
9709   if (first.lastPing != first.lastPong) {
9710     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9711     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9712     return;
9713   }
9714   if (second.lastPing != second.lastPong) {
9715     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9716     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9717     return;
9718   }
9719   DisplayMessage("", ""); curMess = 0;
9720   ThawUI();
9721   TwoMachinesEvent();
9722 }
9723
9724 char *
9725 MakeName (char *template)
9726 {
9727     time_t clock;
9728     struct tm *tm;
9729     static char buf[MSG_SIZ];
9730     char *p = buf;
9731     int i;
9732
9733     clock = time((time_t *)NULL);
9734     tm = localtime(&clock);
9735
9736     while(*p++ = *template++) if(p[-1] == '%') {
9737         switch(*template++) {
9738           case 0:   *p = 0; return buf;
9739           case 'Y': i = tm->tm_year+1900; break;
9740           case 'y': i = tm->tm_year-100; break;
9741           case 'M': i = tm->tm_mon+1; break;
9742           case 'd': i = tm->tm_mday; break;
9743           case 'h': i = tm->tm_hour; break;
9744           case 'm': i = tm->tm_min; break;
9745           case 's': i = tm->tm_sec; break;
9746           default:  i = 0;
9747         }
9748         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9749     }
9750     return buf;
9751 }
9752
9753 int
9754 CountPlayers (char *p)
9755 {
9756     int n = 0;
9757     while(p = strchr(p, '\n')) p++, n++; // count participants
9758     return n;
9759 }
9760
9761 FILE *
9762 WriteTourneyFile (char *results, FILE *f)
9763 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9764     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9765     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9766         // create a file with tournament description
9767         fprintf(f, "-participants {%s}\n", appData.participants);
9768         fprintf(f, "-seedBase %d\n", appData.seedBase);
9769         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9770         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9771         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9772         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9773         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9774         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9775         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9776         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9777         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9778         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9779         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9780         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9781         if(searchTime > 0)
9782                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9783         else {
9784                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9785                 fprintf(f, "-tc %s\n", appData.timeControl);
9786                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9787         }
9788         fprintf(f, "-results \"%s\"\n", results);
9789     }
9790     return f;
9791 }
9792
9793 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9794
9795 void
9796 Substitute (char *participants, int expunge)
9797 {
9798     int i, changed, changes=0, nPlayers=0;
9799     char *p, *q, *r, buf[MSG_SIZ];
9800     if(participants == NULL) return;
9801     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9802     r = p = participants; q = appData.participants;
9803     while(*p && *p == *q) {
9804         if(*p == '\n') r = p+1, nPlayers++;
9805         p++; q++;
9806     }
9807     if(*p) { // difference
9808         while(*p && *p++ != '\n');
9809         while(*q && *q++ != '\n');
9810       changed = nPlayers;
9811         changes = 1 + (strcmp(p, q) != 0);
9812     }
9813     if(changes == 1) { // a single engine mnemonic was changed
9814         q = r; while(*q) nPlayers += (*q++ == '\n');
9815         p = buf; while(*r && (*p = *r++) != '\n') p++;
9816         *p = NULLCHAR;
9817         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9818         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9819         if(mnemonic[i]) { // The substitute is valid
9820             FILE *f;
9821             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9822                 flock(fileno(f), LOCK_EX);
9823                 ParseArgsFromFile(f);
9824                 fseek(f, 0, SEEK_SET);
9825                 FREE(appData.participants); appData.participants = participants;
9826                 if(expunge) { // erase results of replaced engine
9827                     int len = strlen(appData.results), w, b, dummy;
9828                     for(i=0; i<len; i++) {
9829                         Pairing(i, nPlayers, &w, &b, &dummy);
9830                         if((w == changed || b == changed) && appData.results[i] == '*') {
9831                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9832                             fclose(f);
9833                             return;
9834                         }
9835                     }
9836                     for(i=0; i<len; i++) {
9837                         Pairing(i, nPlayers, &w, &b, &dummy);
9838                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9839                     }
9840                 }
9841                 WriteTourneyFile(appData.results, f);
9842                 fclose(f); // release lock
9843                 return;
9844             }
9845         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9846     }
9847     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9848     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9849     free(participants);
9850     return;
9851 }
9852
9853 int
9854 CreateTourney (char *name)
9855 {
9856         FILE *f;
9857         if(matchMode && strcmp(name, appData.tourneyFile)) {
9858              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9859         }
9860         if(name[0] == NULLCHAR) {
9861             if(appData.participants[0])
9862                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9863             return 0;
9864         }
9865         f = fopen(name, "r");
9866         if(f) { // file exists
9867             ASSIGN(appData.tourneyFile, name);
9868             ParseArgsFromFile(f); // parse it
9869         } else {
9870             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9871             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9872                 DisplayError(_("Not enough participants"), 0);
9873                 return 0;
9874             }
9875             ASSIGN(appData.tourneyFile, name);
9876             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9877             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9878         }
9879         fclose(f);
9880         appData.noChessProgram = FALSE;
9881         appData.clockMode = TRUE;
9882         SetGNUMode();
9883         return 1;
9884 }
9885
9886 int
9887 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9888 {
9889     char buf[MSG_SIZ], *p, *q;
9890     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9891     skip = !all && group[0]; // if group requested, we start in skip mode
9892     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9893         p = names; q = buf; header = 0;
9894         while(*p && *p != '\n') *q++ = *p++;
9895         *q = 0;
9896         if(*p == '\n') p++;
9897         if(buf[0] == '#') {
9898             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9899             depth++; // we must be entering a new group
9900             if(all) continue; // suppress printing group headers when complete list requested
9901             header = 1;
9902             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9903         }
9904         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9905         if(engineList[i]) free(engineList[i]);
9906         engineList[i] = strdup(buf);
9907         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9908         if(engineMnemonic[i]) free(engineMnemonic[i]);
9909         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9910             strcat(buf, " (");
9911             sscanf(q + 8, "%s", buf + strlen(buf));
9912             strcat(buf, ")");
9913         }
9914         engineMnemonic[i] = strdup(buf);
9915         i++;
9916     }
9917     engineList[i] = engineMnemonic[i] = NULL;
9918     return i;
9919 }
9920
9921 // following implemented as macro to avoid type limitations
9922 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9923
9924 void
9925 SwapEngines (int n)
9926 {   // swap settings for first engine and other engine (so far only some selected options)
9927     int h;
9928     char *p;
9929     if(n == 0) return;
9930     SWAP(directory, p)
9931     SWAP(chessProgram, p)
9932     SWAP(isUCI, h)
9933     SWAP(hasOwnBookUCI, h)
9934     SWAP(protocolVersion, h)
9935     SWAP(reuse, h)
9936     SWAP(scoreIsAbsolute, h)
9937     SWAP(timeOdds, h)
9938     SWAP(logo, p)
9939     SWAP(pgnName, p)
9940     SWAP(pvSAN, h)
9941     SWAP(engOptions, p)
9942 }
9943
9944 int
9945 SetPlayer (int player, char *p)
9946 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9947     int i;
9948     char buf[MSG_SIZ], *engineName;
9949     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9950     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9951     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9952     if(mnemonic[i]) {
9953         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9954         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9955         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9956         ParseArgsFromString(buf);
9957     }
9958     free(engineName);
9959     return i;
9960 }
9961
9962 char *recentEngines;
9963
9964 void
9965 RecentEngineEvent (int nr)
9966 {
9967     int n;
9968 //    SwapEngines(1); // bump first to second
9969 //    ReplaceEngine(&second, 1); // and load it there
9970     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9971     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9972     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9973         ReplaceEngine(&first, 0);
9974         FloatToFront(&appData.recentEngineList, command[n]);
9975     }
9976 }
9977
9978 int
9979 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9980 {   // determine players from game number
9981     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9982
9983     if(appData.tourneyType == 0) {
9984         roundsPerCycle = (nPlayers - 1) | 1;
9985         pairingsPerRound = nPlayers / 2;
9986     } else if(appData.tourneyType > 0) {
9987         roundsPerCycle = nPlayers - appData.tourneyType;
9988         pairingsPerRound = appData.tourneyType;
9989     }
9990     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9991     gamesPerCycle = gamesPerRound * roundsPerCycle;
9992     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9993     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9994     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9995     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9996     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9997     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9998
9999     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10000     if(appData.roundSync) *syncInterval = gamesPerRound;
10001
10002     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10003
10004     if(appData.tourneyType == 0) {
10005         if(curPairing == (nPlayers-1)/2 ) {
10006             *whitePlayer = curRound;
10007             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10008         } else {
10009             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10010             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10011             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10012             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10013         }
10014     } else if(appData.tourneyType > 1) {
10015         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10016         *whitePlayer = curRound + appData.tourneyType;
10017     } else if(appData.tourneyType > 0) {
10018         *whitePlayer = curPairing;
10019         *blackPlayer = curRound + appData.tourneyType;
10020     }
10021
10022     // take care of white/black alternation per round. 
10023     // For cycles and games this is already taken care of by default, derived from matchGame!
10024     return curRound & 1;
10025 }
10026
10027 int
10028 NextTourneyGame (int nr, int *swapColors)
10029 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10030     char *p, *q;
10031     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10032     FILE *tf;
10033     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10034     tf = fopen(appData.tourneyFile, "r");
10035     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10036     ParseArgsFromFile(tf); fclose(tf);
10037     InitTimeControls(); // TC might be altered from tourney file
10038
10039     nPlayers = CountPlayers(appData.participants); // count participants
10040     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10041     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10042
10043     if(syncInterval) {
10044         p = q = appData.results;
10045         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10046         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10047             DisplayMessage(_("Waiting for other game(s)"),"");
10048             waitingForGame = TRUE;
10049             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10050             return 0;
10051         }
10052         waitingForGame = FALSE;
10053     }
10054
10055     if(appData.tourneyType < 0) {
10056         if(nr>=0 && !pairingReceived) {
10057             char buf[1<<16];
10058             if(pairing.pr == NoProc) {
10059                 if(!appData.pairingEngine[0]) {
10060                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10061                     return 0;
10062                 }
10063                 StartChessProgram(&pairing); // starts the pairing engine
10064             }
10065             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10066             SendToProgram(buf, &pairing);
10067             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10068             SendToProgram(buf, &pairing);
10069             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10070         }
10071         pairingReceived = 0;                              // ... so we continue here 
10072         *swapColors = 0;
10073         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10074         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10075         matchGame = 1; roundNr = nr / syncInterval + 1;
10076     }
10077
10078     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10079
10080     // redefine engines, engine dir, etc.
10081     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10082     if(first.pr == NoProc) {
10083       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10084       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10085     }
10086     if(second.pr == NoProc) {
10087       SwapEngines(1);
10088       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10089       SwapEngines(1);         // and make that valid for second engine by swapping
10090       InitEngine(&second, 1);
10091     }
10092     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10093     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10094     return 1;
10095 }
10096
10097 void
10098 NextMatchGame ()
10099 {   // performs game initialization that does not invoke engines, and then tries to start the game
10100     int res, firstWhite, swapColors = 0;
10101     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10102     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
10103         char buf[MSG_SIZ];
10104         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10105         if(strcmp(buf, currentDebugFile)) { // name has changed
10106             FILE *f = fopen(buf, "w");
10107             if(f) { // if opening the new file failed, just keep using the old one
10108                 ASSIGN(currentDebugFile, buf);
10109                 fclose(debugFP);
10110                 debugFP = f;
10111             }
10112             if(appData.serverFileName) {
10113                 if(serverFP) fclose(serverFP);
10114                 serverFP = fopen(appData.serverFileName, "w");
10115                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10116                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10117             }
10118         }
10119     }
10120     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10121     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10122     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10123     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10124     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10125     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10126     Reset(FALSE, first.pr != NoProc);
10127     res = LoadGameOrPosition(matchGame); // setup game
10128     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10129     if(!res) return; // abort when bad game/pos file
10130     TwoMachinesEvent();
10131 }
10132
10133 void
10134 UserAdjudicationEvent (int result)
10135 {
10136     ChessMove gameResult = GameIsDrawn;
10137
10138     if( result > 0 ) {
10139         gameResult = WhiteWins;
10140     }
10141     else if( result < 0 ) {
10142         gameResult = BlackWins;
10143     }
10144
10145     if( gameMode == TwoMachinesPlay ) {
10146         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10147     }
10148 }
10149
10150
10151 // [HGM] save: calculate checksum of game to make games easily identifiable
10152 int
10153 StringCheckSum (char *s)
10154 {
10155         int i = 0;
10156         if(s==NULL) return 0;
10157         while(*s) i = i*259 + *s++;
10158         return i;
10159 }
10160
10161 int
10162 GameCheckSum ()
10163 {
10164         int i, sum=0;
10165         for(i=backwardMostMove; i<forwardMostMove; i++) {
10166                 sum += pvInfoList[i].depth;
10167                 sum += StringCheckSum(parseList[i]);
10168                 sum += StringCheckSum(commentList[i]);
10169                 sum *= 261;
10170         }
10171         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10172         return sum + StringCheckSum(commentList[i]);
10173 } // end of save patch
10174
10175 void
10176 GameEnds (ChessMove result, char *resultDetails, int whosays)
10177 {
10178     GameMode nextGameMode;
10179     int isIcsGame;
10180     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10181
10182     if(endingGame) return; /* [HGM] crash: forbid recursion */
10183     endingGame = 1;
10184     if(twoBoards) { // [HGM] dual: switch back to one board
10185         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10186         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10187     }
10188     if (appData.debugMode) {
10189       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10190               result, resultDetails ? resultDetails : "(null)", whosays);
10191     }
10192
10193     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10194
10195     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10196         /* If we are playing on ICS, the server decides when the
10197            game is over, but the engine can offer to draw, claim
10198            a draw, or resign.
10199          */
10200 #if ZIPPY
10201         if (appData.zippyPlay && first.initDone) {
10202             if (result == GameIsDrawn) {
10203                 /* In case draw still needs to be claimed */
10204                 SendToICS(ics_prefix);
10205                 SendToICS("draw\n");
10206             } else if (StrCaseStr(resultDetails, "resign")) {
10207                 SendToICS(ics_prefix);
10208                 SendToICS("resign\n");
10209             }
10210         }
10211 #endif
10212         endingGame = 0; /* [HGM] crash */
10213         return;
10214     }
10215
10216     /* If we're loading the game from a file, stop */
10217     if (whosays == GE_FILE) {
10218       (void) StopLoadGameTimer();
10219       gameFileFP = NULL;
10220     }
10221
10222     /* Cancel draw offers */
10223     first.offeredDraw = second.offeredDraw = 0;
10224
10225     /* If this is an ICS game, only ICS can really say it's done;
10226        if not, anyone can. */
10227     isIcsGame = (gameMode == IcsPlayingWhite ||
10228                  gameMode == IcsPlayingBlack ||
10229                  gameMode == IcsObserving    ||
10230                  gameMode == IcsExamining);
10231
10232     if (!isIcsGame || whosays == GE_ICS) {
10233         /* OK -- not an ICS game, or ICS said it was done */
10234         StopClocks();
10235         if (!isIcsGame && !appData.noChessProgram)
10236           SetUserThinkingEnables();
10237
10238         /* [HGM] if a machine claims the game end we verify this claim */
10239         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10240             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10241                 char claimer;
10242                 ChessMove trueResult = (ChessMove) -1;
10243
10244                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10245                                             first.twoMachinesColor[0] :
10246                                             second.twoMachinesColor[0] ;
10247
10248                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10249                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10250                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10251                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10252                 } else
10253                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10254                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10255                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10256                 } else
10257                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10258                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10259                 }
10260
10261                 // now verify win claims, but not in drop games, as we don't understand those yet
10262                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10263                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10264                     (result == WhiteWins && claimer == 'w' ||
10265                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10266                       if (appData.debugMode) {
10267                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10268                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10269                       }
10270                       if(result != trueResult) {
10271                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10272                               result = claimer == 'w' ? BlackWins : WhiteWins;
10273                               resultDetails = buf;
10274                       }
10275                 } else
10276                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10277                     && (forwardMostMove <= backwardMostMove ||
10278                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10279                         (claimer=='b')==(forwardMostMove&1))
10280                                                                                   ) {
10281                       /* [HGM] verify: draws that were not flagged are false claims */
10282                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10283                       result = claimer == 'w' ? BlackWins : WhiteWins;
10284                       resultDetails = buf;
10285                 }
10286                 /* (Claiming a loss is accepted no questions asked!) */
10287             }
10288             /* [HGM] bare: don't allow bare King to win */
10289             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10290                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10291                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10292                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10293                && result != GameIsDrawn)
10294             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10295                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10296                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10297                         if(p >= 0 && p <= (int)WhiteKing) k++;
10298                 }
10299                 if (appData.debugMode) {
10300                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10301                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10302                 }
10303                 if(k <= 1) {
10304                         result = GameIsDrawn;
10305                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10306                         resultDetails = buf;
10307                 }
10308             }
10309         }
10310
10311
10312         if(serverMoves != NULL && !loadFlag) { char c = '=';
10313             if(result==WhiteWins) c = '+';
10314             if(result==BlackWins) c = '-';
10315             if(resultDetails != NULL)
10316                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10317         }
10318         if (resultDetails != NULL) {
10319             gameInfo.result = result;
10320             gameInfo.resultDetails = StrSave(resultDetails);
10321
10322             /* display last move only if game was not loaded from file */
10323             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10324                 DisplayMove(currentMove - 1);
10325
10326             if (forwardMostMove != 0) {
10327                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10328                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10329                                                                 ) {
10330                     if (*appData.saveGameFile != NULLCHAR) {
10331                         SaveGameToFile(appData.saveGameFile, TRUE);
10332                     } else if (appData.autoSaveGames) {
10333                         AutoSaveGame();
10334                     }
10335                     if (*appData.savePositionFile != NULLCHAR) {
10336                         SavePositionToFile(appData.savePositionFile);
10337                     }
10338                 }
10339             }
10340
10341             /* Tell program how game ended in case it is learning */
10342             /* [HGM] Moved this to after saving the PGN, just in case */
10343             /* engine died and we got here through time loss. In that */
10344             /* case we will get a fatal error writing the pipe, which */
10345             /* would otherwise lose us the PGN.                       */
10346             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10347             /* output during GameEnds should never be fatal anymore   */
10348             if (gameMode == MachinePlaysWhite ||
10349                 gameMode == MachinePlaysBlack ||
10350                 gameMode == TwoMachinesPlay ||
10351                 gameMode == IcsPlayingWhite ||
10352                 gameMode == IcsPlayingBlack ||
10353                 gameMode == BeginningOfGame) {
10354                 char buf[MSG_SIZ];
10355                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10356                         resultDetails);
10357                 if (first.pr != NoProc) {
10358                     SendToProgram(buf, &first);
10359                 }
10360                 if (second.pr != NoProc &&
10361                     gameMode == TwoMachinesPlay) {
10362                     SendToProgram(buf, &second);
10363                 }
10364             }
10365         }
10366
10367         if (appData.icsActive) {
10368             if (appData.quietPlay &&
10369                 (gameMode == IcsPlayingWhite ||
10370                  gameMode == IcsPlayingBlack)) {
10371                 SendToICS(ics_prefix);
10372                 SendToICS("set shout 1\n");
10373             }
10374             nextGameMode = IcsIdle;
10375             ics_user_moved = FALSE;
10376             /* clean up premove.  It's ugly when the game has ended and the
10377              * premove highlights are still on the board.
10378              */
10379             if (gotPremove) {
10380               gotPremove = FALSE;
10381               ClearPremoveHighlights();
10382               DrawPosition(FALSE, boards[currentMove]);
10383             }
10384             if (whosays == GE_ICS) {
10385                 switch (result) {
10386                 case WhiteWins:
10387                     if (gameMode == IcsPlayingWhite)
10388                         PlayIcsWinSound();
10389                     else if(gameMode == IcsPlayingBlack)
10390                         PlayIcsLossSound();
10391                     break;
10392                 case BlackWins:
10393                     if (gameMode == IcsPlayingBlack)
10394                         PlayIcsWinSound();
10395                     else if(gameMode == IcsPlayingWhite)
10396                         PlayIcsLossSound();
10397                     break;
10398                 case GameIsDrawn:
10399                     PlayIcsDrawSound();
10400                     break;
10401                 default:
10402                     PlayIcsUnfinishedSound();
10403                 }
10404             }
10405         } else if (gameMode == EditGame ||
10406                    gameMode == PlayFromGameFile ||
10407                    gameMode == AnalyzeMode ||
10408                    gameMode == AnalyzeFile) {
10409             nextGameMode = gameMode;
10410         } else {
10411             nextGameMode = EndOfGame;
10412         }
10413         pausing = FALSE;
10414         ModeHighlight();
10415     } else {
10416         nextGameMode = gameMode;
10417     }
10418
10419     if (appData.noChessProgram) {
10420         gameMode = nextGameMode;
10421         ModeHighlight();
10422         endingGame = 0; /* [HGM] crash */
10423         return;
10424     }
10425
10426     if (first.reuse) {
10427         /* Put first chess program into idle state */
10428         if (first.pr != NoProc &&
10429             (gameMode == MachinePlaysWhite ||
10430              gameMode == MachinePlaysBlack ||
10431              gameMode == TwoMachinesPlay ||
10432              gameMode == IcsPlayingWhite ||
10433              gameMode == IcsPlayingBlack ||
10434              gameMode == BeginningOfGame)) {
10435             SendToProgram("force\n", &first);
10436             if (first.usePing) {
10437               char buf[MSG_SIZ];
10438               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10439               SendToProgram(buf, &first);
10440             }
10441         }
10442     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10443         /* Kill off first chess program */
10444         if (first.isr != NULL)
10445           RemoveInputSource(first.isr);
10446         first.isr = NULL;
10447
10448         if (first.pr != NoProc) {
10449             ExitAnalyzeMode();
10450             DoSleep( appData.delayBeforeQuit );
10451             SendToProgram("quit\n", &first);
10452             DoSleep( appData.delayAfterQuit );
10453             DestroyChildProcess(first.pr, first.useSigterm);
10454         }
10455         first.pr = NoProc;
10456     }
10457     if (second.reuse) {
10458         /* Put second chess program into idle state */
10459         if (second.pr != NoProc &&
10460             gameMode == TwoMachinesPlay) {
10461             SendToProgram("force\n", &second);
10462             if (second.usePing) {
10463               char buf[MSG_SIZ];
10464               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10465               SendToProgram(buf, &second);
10466             }
10467         }
10468     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10469         /* Kill off second chess program */
10470         if (second.isr != NULL)
10471           RemoveInputSource(second.isr);
10472         second.isr = NULL;
10473
10474         if (second.pr != NoProc) {
10475             DoSleep( appData.delayBeforeQuit );
10476             SendToProgram("quit\n", &second);
10477             DoSleep( appData.delayAfterQuit );
10478             DestroyChildProcess(second.pr, second.useSigterm);
10479         }
10480         second.pr = NoProc;
10481     }
10482
10483     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10484         char resChar = '=';
10485         switch (result) {
10486         case WhiteWins:
10487           resChar = '+';
10488           if (first.twoMachinesColor[0] == 'w') {
10489             first.matchWins++;
10490           } else {
10491             second.matchWins++;
10492           }
10493           break;
10494         case BlackWins:
10495           resChar = '-';
10496           if (first.twoMachinesColor[0] == 'b') {
10497             first.matchWins++;
10498           } else {
10499             second.matchWins++;
10500           }
10501           break;
10502         case GameUnfinished:
10503           resChar = ' ';
10504         default:
10505           break;
10506         }
10507
10508         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10509         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10510             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10511             ReserveGame(nextGame, resChar); // sets nextGame
10512             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10513             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10514         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10515
10516         if (nextGame <= appData.matchGames && !abortMatch) {
10517             gameMode = nextGameMode;
10518             matchGame = nextGame; // this will be overruled in tourney mode!
10519             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10520             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10521             endingGame = 0; /* [HGM] crash */
10522             return;
10523         } else {
10524             gameMode = nextGameMode;
10525             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10526                      first.tidy, second.tidy,
10527                      first.matchWins, second.matchWins,
10528                      appData.matchGames - (first.matchWins + second.matchWins));
10529             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10530             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10531             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10532             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10533                 first.twoMachinesColor = "black\n";
10534                 second.twoMachinesColor = "white\n";
10535             } else {
10536                 first.twoMachinesColor = "white\n";
10537                 second.twoMachinesColor = "black\n";
10538             }
10539         }
10540     }
10541     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10542         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10543       ExitAnalyzeMode();
10544     gameMode = nextGameMode;
10545     ModeHighlight();
10546     endingGame = 0;  /* [HGM] crash */
10547     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10548         if(matchMode == TRUE) { // match through command line: exit with or without popup
10549             if(ranking) {
10550                 ToNrEvent(forwardMostMove);
10551                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10552                 else ExitEvent(0);
10553             } else DisplayFatalError(buf, 0, 0);
10554         } else { // match through menu; just stop, with or without popup
10555             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10556             ModeHighlight();
10557             if(ranking){
10558                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10559             } else DisplayNote(buf);
10560       }
10561       if(ranking) free(ranking);
10562     }
10563 }
10564
10565 /* Assumes program was just initialized (initString sent).
10566    Leaves program in force mode. */
10567 void
10568 FeedMovesToProgram (ChessProgramState *cps, int upto)
10569 {
10570     int i;
10571
10572     if (appData.debugMode)
10573       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10574               startedFromSetupPosition ? "position and " : "",
10575               backwardMostMove, upto, cps->which);
10576     if(currentlyInitializedVariant != gameInfo.variant) {
10577       char buf[MSG_SIZ];
10578         // [HGM] variantswitch: make engine aware of new variant
10579         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10580                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10581         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10582         SendToProgram(buf, cps);
10583         currentlyInitializedVariant = gameInfo.variant;
10584     }
10585     SendToProgram("force\n", cps);
10586     if (startedFromSetupPosition) {
10587         SendBoard(cps, backwardMostMove);
10588     if (appData.debugMode) {
10589         fprintf(debugFP, "feedMoves\n");
10590     }
10591     }
10592     for (i = backwardMostMove; i < upto; i++) {
10593         SendMoveToProgram(i, cps);
10594     }
10595 }
10596
10597
10598 int
10599 ResurrectChessProgram ()
10600 {
10601      /* The chess program may have exited.
10602         If so, restart it and feed it all the moves made so far. */
10603     static int doInit = 0;
10604
10605     if (appData.noChessProgram) return 1;
10606
10607     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10608         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10609         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10610         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10611     } else {
10612         if (first.pr != NoProc) return 1;
10613         StartChessProgram(&first);
10614     }
10615     InitChessProgram(&first, FALSE);
10616     FeedMovesToProgram(&first, currentMove);
10617
10618     if (!first.sendTime) {
10619         /* can't tell gnuchess what its clock should read,
10620            so we bow to its notion. */
10621         ResetClocks();
10622         timeRemaining[0][currentMove] = whiteTimeRemaining;
10623         timeRemaining[1][currentMove] = blackTimeRemaining;
10624     }
10625
10626     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10627                 appData.icsEngineAnalyze) && first.analysisSupport) {
10628       SendToProgram("analyze\n", &first);
10629       first.analyzing = TRUE;
10630     }
10631     return 1;
10632 }
10633
10634 /*
10635  * Button procedures
10636  */
10637 void
10638 Reset (int redraw, int init)
10639 {
10640     int i;
10641
10642     if (appData.debugMode) {
10643         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10644                 redraw, init, gameMode);
10645     }
10646     CleanupTail(); // [HGM] vari: delete any stored variations
10647     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10648     pausing = pauseExamInvalid = FALSE;
10649     startedFromSetupPosition = blackPlaysFirst = FALSE;
10650     firstMove = TRUE;
10651     whiteFlag = blackFlag = FALSE;
10652     userOfferedDraw = FALSE;
10653     hintRequested = bookRequested = FALSE;
10654     first.maybeThinking = FALSE;
10655     second.maybeThinking = FALSE;
10656     first.bookSuspend = FALSE; // [HGM] book
10657     second.bookSuspend = FALSE;
10658     thinkOutput[0] = NULLCHAR;
10659     lastHint[0] = NULLCHAR;
10660     ClearGameInfo(&gameInfo);
10661     gameInfo.variant = StringToVariant(appData.variant);
10662     ics_user_moved = ics_clock_paused = FALSE;
10663     ics_getting_history = H_FALSE;
10664     ics_gamenum = -1;
10665     white_holding[0] = black_holding[0] = NULLCHAR;
10666     ClearProgramStats();
10667     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10668
10669     ResetFrontEnd();
10670     ClearHighlights();
10671     flipView = appData.flipView;
10672     ClearPremoveHighlights();
10673     gotPremove = FALSE;
10674     alarmSounded = FALSE;
10675
10676     GameEnds(EndOfFile, NULL, GE_PLAYER);
10677     if(appData.serverMovesName != NULL) {
10678         /* [HGM] prepare to make moves file for broadcasting */
10679         clock_t t = clock();
10680         if(serverMoves != NULL) fclose(serverMoves);
10681         serverMoves = fopen(appData.serverMovesName, "r");
10682         if(serverMoves != NULL) {
10683             fclose(serverMoves);
10684             /* delay 15 sec before overwriting, so all clients can see end */
10685             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10686         }
10687         serverMoves = fopen(appData.serverMovesName, "w");
10688     }
10689
10690     ExitAnalyzeMode();
10691     gameMode = BeginningOfGame;
10692     ModeHighlight();
10693     if(appData.icsActive) gameInfo.variant = VariantNormal;
10694     currentMove = forwardMostMove = backwardMostMove = 0;
10695     MarkTargetSquares(1);
10696     InitPosition(redraw);
10697     for (i = 0; i < MAX_MOVES; i++) {
10698         if (commentList[i] != NULL) {
10699             free(commentList[i]);
10700             commentList[i] = NULL;
10701         }
10702     }
10703     ResetClocks();
10704     timeRemaining[0][0] = whiteTimeRemaining;
10705     timeRemaining[1][0] = blackTimeRemaining;
10706
10707     if (first.pr == NoProc) {
10708         StartChessProgram(&first);
10709     }
10710     if (init) {
10711             InitChessProgram(&first, startedFromSetupPosition);
10712     }
10713     DisplayTitle("");
10714     DisplayMessage("", "");
10715     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10716     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10717 }
10718
10719 void
10720 AutoPlayGameLoop ()
10721 {
10722     for (;;) {
10723         if (!AutoPlayOneMove())
10724           return;
10725         if (matchMode || appData.timeDelay == 0)
10726           continue;
10727         if (appData.timeDelay < 0)
10728           return;
10729         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10730         break;
10731     }
10732 }
10733
10734
10735 int
10736 AutoPlayOneMove ()
10737 {
10738     int fromX, fromY, toX, toY;
10739
10740     if (appData.debugMode) {
10741       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10742     }
10743
10744     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10745       return FALSE;
10746
10747     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10748       pvInfoList[currentMove].depth = programStats.depth;
10749       pvInfoList[currentMove].score = programStats.score;
10750       pvInfoList[currentMove].time  = 0;
10751       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10752     }
10753
10754     if (currentMove >= forwardMostMove) {
10755       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10756 //      gameMode = EndOfGame;
10757 //      ModeHighlight();
10758
10759       /* [AS] Clear current move marker at the end of a game */
10760       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10761
10762       return FALSE;
10763     }
10764
10765     toX = moveList[currentMove][2] - AAA;
10766     toY = moveList[currentMove][3] - ONE;
10767
10768     if (moveList[currentMove][1] == '@') {
10769         if (appData.highlightLastMove) {
10770             SetHighlights(-1, -1, toX, toY);
10771         }
10772     } else {
10773         fromX = moveList[currentMove][0] - AAA;
10774         fromY = moveList[currentMove][1] - ONE;
10775
10776         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10777
10778         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10779
10780         if (appData.highlightLastMove) {
10781             SetHighlights(fromX, fromY, toX, toY);
10782         }
10783     }
10784     DisplayMove(currentMove);
10785     SendMoveToProgram(currentMove++, &first);
10786     DisplayBothClocks();
10787     DrawPosition(FALSE, boards[currentMove]);
10788     // [HGM] PV info: always display, routine tests if empty
10789     DisplayComment(currentMove - 1, commentList[currentMove]);
10790     return TRUE;
10791 }
10792
10793
10794 int
10795 LoadGameOneMove (ChessMove readAhead)
10796 {
10797     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10798     char promoChar = NULLCHAR;
10799     ChessMove moveType;
10800     char move[MSG_SIZ];
10801     char *p, *q;
10802
10803     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10804         gameMode != AnalyzeMode && gameMode != Training) {
10805         gameFileFP = NULL;
10806         return FALSE;
10807     }
10808
10809     yyboardindex = forwardMostMove;
10810     if (readAhead != EndOfFile) {
10811       moveType = readAhead;
10812     } else {
10813       if (gameFileFP == NULL)
10814           return FALSE;
10815       moveType = (ChessMove) Myylex();
10816     }
10817
10818     done = FALSE;
10819     switch (moveType) {
10820       case Comment:
10821         if (appData.debugMode)
10822           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10823         p = yy_text;
10824
10825         /* append the comment but don't display it */
10826         AppendComment(currentMove, p, FALSE);
10827         return TRUE;
10828
10829       case WhiteCapturesEnPassant:
10830       case BlackCapturesEnPassant:
10831       case WhitePromotion:
10832       case BlackPromotion:
10833       case WhiteNonPromotion:
10834       case BlackNonPromotion:
10835       case NormalMove:
10836       case WhiteKingSideCastle:
10837       case WhiteQueenSideCastle:
10838       case BlackKingSideCastle:
10839       case BlackQueenSideCastle:
10840       case WhiteKingSideCastleWild:
10841       case WhiteQueenSideCastleWild:
10842       case BlackKingSideCastleWild:
10843       case BlackQueenSideCastleWild:
10844       /* PUSH Fabien */
10845       case WhiteHSideCastleFR:
10846       case WhiteASideCastleFR:
10847       case BlackHSideCastleFR:
10848       case BlackASideCastleFR:
10849       /* POP Fabien */
10850         if (appData.debugMode)
10851           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10852         fromX = currentMoveString[0] - AAA;
10853         fromY = currentMoveString[1] - ONE;
10854         toX = currentMoveString[2] - AAA;
10855         toY = currentMoveString[3] - ONE;
10856         promoChar = currentMoveString[4];
10857         break;
10858
10859       case WhiteDrop:
10860       case BlackDrop:
10861         if (appData.debugMode)
10862           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10863         fromX = moveType == WhiteDrop ?
10864           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10865         (int) CharToPiece(ToLower(currentMoveString[0]));
10866         fromY = DROP_RANK;
10867         toX = currentMoveString[2] - AAA;
10868         toY = currentMoveString[3] - ONE;
10869         break;
10870
10871       case WhiteWins:
10872       case BlackWins:
10873       case GameIsDrawn:
10874       case GameUnfinished:
10875         if (appData.debugMode)
10876           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10877         p = strchr(yy_text, '{');
10878         if (p == NULL) p = strchr(yy_text, '(');
10879         if (p == NULL) {
10880             p = yy_text;
10881             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10882         } else {
10883             q = strchr(p, *p == '{' ? '}' : ')');
10884             if (q != NULL) *q = NULLCHAR;
10885             p++;
10886         }
10887         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10888         GameEnds(moveType, p, GE_FILE);
10889         done = TRUE;
10890         if (cmailMsgLoaded) {
10891             ClearHighlights();
10892             flipView = WhiteOnMove(currentMove);
10893             if (moveType == GameUnfinished) flipView = !flipView;
10894             if (appData.debugMode)
10895               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10896         }
10897         break;
10898
10899       case EndOfFile:
10900         if (appData.debugMode)
10901           fprintf(debugFP, "Parser hit end of file\n");
10902         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10903           case MT_NONE:
10904           case MT_CHECK:
10905             break;
10906           case MT_CHECKMATE:
10907           case MT_STAINMATE:
10908             if (WhiteOnMove(currentMove)) {
10909                 GameEnds(BlackWins, "Black mates", GE_FILE);
10910             } else {
10911                 GameEnds(WhiteWins, "White mates", GE_FILE);
10912             }
10913             break;
10914           case MT_STALEMATE:
10915             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10916             break;
10917         }
10918         done = TRUE;
10919         break;
10920
10921       case MoveNumberOne:
10922         if (lastLoadGameStart == GNUChessGame) {
10923             /* GNUChessGames have numbers, but they aren't move numbers */
10924             if (appData.debugMode)
10925               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10926                       yy_text, (int) moveType);
10927             return LoadGameOneMove(EndOfFile); /* tail recursion */
10928         }
10929         /* else fall thru */
10930
10931       case XBoardGame:
10932       case GNUChessGame:
10933       case PGNTag:
10934         /* Reached start of next game in file */
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10937         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10938           case MT_NONE:
10939           case MT_CHECK:
10940             break;
10941           case MT_CHECKMATE:
10942           case MT_STAINMATE:
10943             if (WhiteOnMove(currentMove)) {
10944                 GameEnds(BlackWins, "Black mates", GE_FILE);
10945             } else {
10946                 GameEnds(WhiteWins, "White mates", GE_FILE);
10947             }
10948             break;
10949           case MT_STALEMATE:
10950             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10951             break;
10952         }
10953         done = TRUE;
10954         break;
10955
10956       case PositionDiagram:     /* should not happen; ignore */
10957       case ElapsedTime:         /* ignore */
10958       case NAG:                 /* ignore */
10959         if (appData.debugMode)
10960           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10961                   yy_text, (int) moveType);
10962         return LoadGameOneMove(EndOfFile); /* tail recursion */
10963
10964       case IllegalMove:
10965         if (appData.testLegality) {
10966             if (appData.debugMode)
10967               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10968             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10969                     (forwardMostMove / 2) + 1,
10970                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10971             DisplayError(move, 0);
10972             done = TRUE;
10973         } else {
10974             if (appData.debugMode)
10975               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10976                       yy_text, currentMoveString);
10977             fromX = currentMoveString[0] - AAA;
10978             fromY = currentMoveString[1] - ONE;
10979             toX = currentMoveString[2] - AAA;
10980             toY = currentMoveString[3] - ONE;
10981             promoChar = currentMoveString[4];
10982         }
10983         break;
10984
10985       case AmbiguousMove:
10986         if (appData.debugMode)
10987           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10988         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10989                 (forwardMostMove / 2) + 1,
10990                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10991         DisplayError(move, 0);
10992         done = TRUE;
10993         break;
10994
10995       default:
10996       case ImpossibleMove:
10997         if (appData.debugMode)
10998           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10999         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11000                 (forwardMostMove / 2) + 1,
11001                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11002         DisplayError(move, 0);
11003         done = TRUE;
11004         break;
11005     }
11006
11007     if (done) {
11008         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11009             DrawPosition(FALSE, boards[currentMove]);
11010             DisplayBothClocks();
11011             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11012               DisplayComment(currentMove - 1, commentList[currentMove]);
11013         }
11014         (void) StopLoadGameTimer();
11015         gameFileFP = NULL;
11016         cmailOldMove = forwardMostMove;
11017         return FALSE;
11018     } else {
11019         /* currentMoveString is set as a side-effect of yylex */
11020
11021         thinkOutput[0] = NULLCHAR;
11022         MakeMove(fromX, fromY, toX, toY, promoChar);
11023         currentMove = forwardMostMove;
11024         return TRUE;
11025     }
11026 }
11027
11028 /* Load the nth game from the given file */
11029 int
11030 LoadGameFromFile (char *filename, int n, char *title, int useList)
11031 {
11032     FILE *f;
11033     char buf[MSG_SIZ];
11034
11035     if (strcmp(filename, "-") == 0) {
11036         f = stdin;
11037         title = "stdin";
11038     } else {
11039         f = fopen(filename, "rb");
11040         if (f == NULL) {
11041           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11042             DisplayError(buf, errno);
11043             return FALSE;
11044         }
11045     }
11046     if (fseek(f, 0, 0) == -1) {
11047         /* f is not seekable; probably a pipe */
11048         useList = FALSE;
11049     }
11050     if (useList && n == 0) {
11051         int error = GameListBuild(f);
11052         if (error) {
11053             DisplayError(_("Cannot build game list"), error);
11054         } else if (!ListEmpty(&gameList) &&
11055                    ((ListGame *) gameList.tailPred)->number > 1) {
11056             GameListPopUp(f, title);
11057             return TRUE;
11058         }
11059         GameListDestroy();
11060         n = 1;
11061     }
11062     if (n == 0) n = 1;
11063     return LoadGame(f, n, title, FALSE);
11064 }
11065
11066
11067 void
11068 MakeRegisteredMove ()
11069 {
11070     int fromX, fromY, toX, toY;
11071     char promoChar;
11072     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11073         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11074           case CMAIL_MOVE:
11075           case CMAIL_DRAW:
11076             if (appData.debugMode)
11077               fprintf(debugFP, "Restoring %s for game %d\n",
11078                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11079
11080             thinkOutput[0] = NULLCHAR;
11081             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11082             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11083             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11084             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11085             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11086             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11087             MakeMove(fromX, fromY, toX, toY, promoChar);
11088             ShowMove(fromX, fromY, toX, toY);
11089
11090             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11091               case MT_NONE:
11092               case MT_CHECK:
11093                 break;
11094
11095               case MT_CHECKMATE:
11096               case MT_STAINMATE:
11097                 if (WhiteOnMove(currentMove)) {
11098                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11099                 } else {
11100                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11101                 }
11102                 break;
11103
11104               case MT_STALEMATE:
11105                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11106                 break;
11107             }
11108
11109             break;
11110
11111           case CMAIL_RESIGN:
11112             if (WhiteOnMove(currentMove)) {
11113                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11114             } else {
11115                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11116             }
11117             break;
11118
11119           case CMAIL_ACCEPT:
11120             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11121             break;
11122
11123           default:
11124             break;
11125         }
11126     }
11127
11128     return;
11129 }
11130
11131 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11132 int
11133 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11134 {
11135     int retVal;
11136
11137     if (gameNumber > nCmailGames) {
11138         DisplayError(_("No more games in this message"), 0);
11139         return FALSE;
11140     }
11141     if (f == lastLoadGameFP) {
11142         int offset = gameNumber - lastLoadGameNumber;
11143         if (offset == 0) {
11144             cmailMsg[0] = NULLCHAR;
11145             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11146                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11147                 nCmailMovesRegistered--;
11148             }
11149             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11150             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11151                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11152             }
11153         } else {
11154             if (! RegisterMove()) return FALSE;
11155         }
11156     }
11157
11158     retVal = LoadGame(f, gameNumber, title, useList);
11159
11160     /* Make move registered during previous look at this game, if any */
11161     MakeRegisteredMove();
11162
11163     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11164         commentList[currentMove]
11165           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11166         DisplayComment(currentMove - 1, commentList[currentMove]);
11167     }
11168
11169     return retVal;
11170 }
11171
11172 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11173 int
11174 ReloadGame (int offset)
11175 {
11176     int gameNumber = lastLoadGameNumber + offset;
11177     if (lastLoadGameFP == NULL) {
11178         DisplayError(_("No game has been loaded yet"), 0);
11179         return FALSE;
11180     }
11181     if (gameNumber <= 0) {
11182         DisplayError(_("Can't back up any further"), 0);
11183         return FALSE;
11184     }
11185     if (cmailMsgLoaded) {
11186         return CmailLoadGame(lastLoadGameFP, gameNumber,
11187                              lastLoadGameTitle, lastLoadGameUseList);
11188     } else {
11189         return LoadGame(lastLoadGameFP, gameNumber,
11190                         lastLoadGameTitle, lastLoadGameUseList);
11191     }
11192 }
11193
11194 int keys[EmptySquare+1];
11195
11196 int
11197 PositionMatches (Board b1, Board b2)
11198 {
11199     int r, f, sum=0;
11200     switch(appData.searchMode) {
11201         case 1: return CompareWithRights(b1, b2);
11202         case 2:
11203             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11204                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11205             }
11206             return TRUE;
11207         case 3:
11208             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11209               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11210                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11211             }
11212             return sum==0;
11213         case 4:
11214             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11215                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11216             }
11217             return sum==0;
11218     }
11219     return TRUE;
11220 }
11221
11222 #define Q_PROMO  4
11223 #define Q_EP     3
11224 #define Q_BCASTL 2
11225 #define Q_WCASTL 1
11226
11227 int pieceList[256], quickBoard[256];
11228 ChessSquare pieceType[256] = { EmptySquare };
11229 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11230 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11231 int soughtTotal, turn;
11232 Boolean epOK, flipSearch;
11233
11234 typedef struct {
11235     unsigned char piece, to;
11236 } Move;
11237
11238 #define DSIZE (250000)
11239
11240 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11241 Move *moveDatabase = initialSpace;
11242 unsigned int movePtr, dataSize = DSIZE;
11243
11244 int
11245 MakePieceList (Board board, int *counts)
11246 {
11247     int r, f, n=Q_PROMO, total=0;
11248     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11249     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11250         int sq = f + (r<<4);
11251         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11252             quickBoard[sq] = ++n;
11253             pieceList[n] = sq;
11254             pieceType[n] = board[r][f];
11255             counts[board[r][f]]++;
11256             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11257             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11258             total++;
11259         }
11260     }
11261     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11262     return total;
11263 }
11264
11265 void
11266 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11267 {
11268     int sq = fromX + (fromY<<4);
11269     int piece = quickBoard[sq];
11270     quickBoard[sq] = 0;
11271     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11272     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11273         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11274         moveDatabase[movePtr++].piece = Q_WCASTL;
11275         quickBoard[sq] = piece;
11276         piece = quickBoard[from]; quickBoard[from] = 0;
11277         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11278     } else
11279     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11280         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11281         moveDatabase[movePtr++].piece = Q_BCASTL;
11282         quickBoard[sq] = piece;
11283         piece = quickBoard[from]; quickBoard[from] = 0;
11284         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11285     } else
11286     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11287         quickBoard[(fromY<<4)+toX] = 0;
11288         moveDatabase[movePtr].piece = Q_EP;
11289         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11290         moveDatabase[movePtr].to = sq;
11291     } else
11292     if(promoPiece != pieceType[piece]) {
11293         moveDatabase[movePtr++].piece = Q_PROMO;
11294         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11295     }
11296     moveDatabase[movePtr].piece = piece;
11297     quickBoard[sq] = piece;
11298     movePtr++;
11299 }
11300
11301 int
11302 PackGame (Board board)
11303 {
11304     Move *newSpace = NULL;
11305     moveDatabase[movePtr].piece = 0; // terminate previous game
11306     if(movePtr > dataSize) {
11307         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11308         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11309         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11310         if(newSpace) {
11311             int i;
11312             Move *p = moveDatabase, *q = newSpace;
11313             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11314             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11315             moveDatabase = newSpace;
11316         } else { // calloc failed, we must be out of memory. Too bad...
11317             dataSize = 0; // prevent calloc events for all subsequent games
11318             return 0;     // and signal this one isn't cached
11319         }
11320     }
11321     movePtr++;
11322     MakePieceList(board, counts);
11323     return movePtr;
11324 }
11325
11326 int
11327 QuickCompare (Board board, int *minCounts, int *maxCounts)
11328 {   // compare according to search mode
11329     int r, f;
11330     switch(appData.searchMode)
11331     {
11332       case 1: // exact position match
11333         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11334         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11335             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11336         }
11337         break;
11338       case 2: // can have extra material on empty squares
11339         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11340             if(board[r][f] == EmptySquare) continue;
11341             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11342         }
11343         break;
11344       case 3: // material with exact Pawn structure
11345         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11346             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11347             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11348         } // fall through to material comparison
11349       case 4: // exact material
11350         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11351         break;
11352       case 6: // material range with given imbalance
11353         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11354         // fall through to range comparison
11355       case 5: // material range
11356         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11357     }
11358     return TRUE;
11359 }
11360
11361 int
11362 QuickScan (Board board, Move *move)
11363 {   // reconstruct game,and compare all positions in it
11364     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11365     do {
11366         int piece = move->piece;
11367         int to = move->to, from = pieceList[piece];
11368         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11369           if(!piece) return -1;
11370           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11371             piece = (++move)->piece;
11372             from = pieceList[piece];
11373             counts[pieceType[piece]]--;
11374             pieceType[piece] = (ChessSquare) move->to;
11375             counts[move->to]++;
11376           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11377             counts[pieceType[quickBoard[to]]]--;
11378             quickBoard[to] = 0; total--;
11379             move++;
11380             continue;
11381           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11382             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11383             from  = pieceList[piece]; // so this must be King
11384             quickBoard[from] = 0;
11385             quickBoard[to] = piece;
11386             pieceList[piece] = to;
11387             move++;
11388             continue;
11389           }
11390         }
11391         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11392         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11393         quickBoard[from] = 0;
11394         quickBoard[to] = piece;
11395         pieceList[piece] = to;
11396         cnt++; turn ^= 3;
11397         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11398            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11399            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11400                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11401           ) {
11402             static int lastCounts[EmptySquare+1];
11403             int i;
11404             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11405             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11406         } else stretch = 0;
11407         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11408         move++;
11409     } while(1);
11410 }
11411
11412 void
11413 InitSearch ()
11414 {
11415     int r, f;
11416     flipSearch = FALSE;
11417     CopyBoard(soughtBoard, boards[currentMove]);
11418     soughtTotal = MakePieceList(soughtBoard, maxSought);
11419     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11420     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11421     CopyBoard(reverseBoard, boards[currentMove]);
11422     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11423         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11424         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11425         reverseBoard[r][f] = piece;
11426     }
11427     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11428     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11429     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11430                  || (boards[currentMove][CASTLING][2] == NoRights || 
11431                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11432                  && (boards[currentMove][CASTLING][5] == NoRights || 
11433                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11434       ) {
11435         flipSearch = TRUE;
11436         CopyBoard(flipBoard, soughtBoard);
11437         CopyBoard(rotateBoard, reverseBoard);
11438         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11439             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11440             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11441         }
11442     }
11443     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11444     if(appData.searchMode >= 5) {
11445         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11446         MakePieceList(soughtBoard, minSought);
11447         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11448     }
11449     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11450         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11451 }
11452
11453 GameInfo dummyInfo;
11454
11455 int
11456 GameContainsPosition (FILE *f, ListGame *lg)
11457 {
11458     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11459     int fromX, fromY, toX, toY;
11460     char promoChar;
11461     static int initDone=FALSE;
11462
11463     // weed out games based on numerical tag comparison
11464     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11465     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11466     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11467     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11468     if(!initDone) {
11469         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11470         initDone = TRUE;
11471     }
11472     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11473     else CopyBoard(boards[scratch], initialPosition); // default start position
11474     if(lg->moves) {
11475         turn = btm + 1;
11476         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11477         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11478     }
11479     if(btm) plyNr++;
11480     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11481     fseek(f, lg->offset, 0);
11482     yynewfile(f);
11483     while(1) {
11484         yyboardindex = scratch;
11485         quickFlag = plyNr+1;
11486         next = Myylex();
11487         quickFlag = 0;
11488         switch(next) {
11489             case PGNTag:
11490                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11491             default:
11492                 continue;
11493
11494             case XBoardGame:
11495             case GNUChessGame:
11496                 if(plyNr) return -1; // after we have seen moves, this is for new game
11497               continue;
11498
11499             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11500             case ImpossibleMove:
11501             case WhiteWins: // game ends here with these four
11502             case BlackWins:
11503             case GameIsDrawn:
11504             case GameUnfinished:
11505                 return -1;
11506
11507             case IllegalMove:
11508                 if(appData.testLegality) return -1;
11509             case WhiteCapturesEnPassant:
11510             case BlackCapturesEnPassant:
11511             case WhitePromotion:
11512             case BlackPromotion:
11513             case WhiteNonPromotion:
11514             case BlackNonPromotion:
11515             case NormalMove:
11516             case WhiteKingSideCastle:
11517             case WhiteQueenSideCastle:
11518             case BlackKingSideCastle:
11519             case BlackQueenSideCastle:
11520             case WhiteKingSideCastleWild:
11521             case WhiteQueenSideCastleWild:
11522             case BlackKingSideCastleWild:
11523             case BlackQueenSideCastleWild:
11524             case WhiteHSideCastleFR:
11525             case WhiteASideCastleFR:
11526             case BlackHSideCastleFR:
11527             case BlackASideCastleFR:
11528                 fromX = currentMoveString[0] - AAA;
11529                 fromY = currentMoveString[1] - ONE;
11530                 toX = currentMoveString[2] - AAA;
11531                 toY = currentMoveString[3] - ONE;
11532                 promoChar = currentMoveString[4];
11533                 break;
11534             case WhiteDrop:
11535             case BlackDrop:
11536                 fromX = next == WhiteDrop ?
11537                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11538                   (int) CharToPiece(ToLower(currentMoveString[0]));
11539                 fromY = DROP_RANK;
11540                 toX = currentMoveString[2] - AAA;
11541                 toY = currentMoveString[3] - ONE;
11542                 promoChar = 0;
11543                 break;
11544         }
11545         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11546         plyNr++;
11547         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11548         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11549         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11550         if(appData.findMirror) {
11551             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11552             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11553         }
11554     }
11555 }
11556
11557 /* Load the nth game from open file f */
11558 int
11559 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11560 {
11561     ChessMove cm;
11562     char buf[MSG_SIZ];
11563     int gn = gameNumber;
11564     ListGame *lg = NULL;
11565     int numPGNTags = 0;
11566     int err, pos = -1;
11567     GameMode oldGameMode;
11568     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11569
11570     if (appData.debugMode)
11571         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11572
11573     if (gameMode == Training )
11574         SetTrainingModeOff();
11575
11576     oldGameMode = gameMode;
11577     if (gameMode != BeginningOfGame) {
11578       Reset(FALSE, TRUE);
11579     }
11580
11581     gameFileFP = f;
11582     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11583         fclose(lastLoadGameFP);
11584     }
11585
11586     if (useList) {
11587         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11588
11589         if (lg) {
11590             fseek(f, lg->offset, 0);
11591             GameListHighlight(gameNumber);
11592             pos = lg->position;
11593             gn = 1;
11594         }
11595         else {
11596             DisplayError(_("Game number out of range"), 0);
11597             return FALSE;
11598         }
11599     } else {
11600         GameListDestroy();
11601         if (fseek(f, 0, 0) == -1) {
11602             if (f == lastLoadGameFP ?
11603                 gameNumber == lastLoadGameNumber + 1 :
11604                 gameNumber == 1) {
11605                 gn = 1;
11606             } else {
11607                 DisplayError(_("Can't seek on game file"), 0);
11608                 return FALSE;
11609             }
11610         }
11611     }
11612     lastLoadGameFP = f;
11613     lastLoadGameNumber = gameNumber;
11614     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11615     lastLoadGameUseList = useList;
11616
11617     yynewfile(f);
11618
11619     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11620       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11621                 lg->gameInfo.black);
11622             DisplayTitle(buf);
11623     } else if (*title != NULLCHAR) {
11624         if (gameNumber > 1) {
11625           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11626             DisplayTitle(buf);
11627         } else {
11628             DisplayTitle(title);
11629         }
11630     }
11631
11632     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11633         gameMode = PlayFromGameFile;
11634         ModeHighlight();
11635     }
11636
11637     currentMove = forwardMostMove = backwardMostMove = 0;
11638     CopyBoard(boards[0], initialPosition);
11639     StopClocks();
11640
11641     /*
11642      * Skip the first gn-1 games in the file.
11643      * Also skip over anything that precedes an identifiable
11644      * start of game marker, to avoid being confused by
11645      * garbage at the start of the file.  Currently
11646      * recognized start of game markers are the move number "1",
11647      * the pattern "gnuchess .* game", the pattern
11648      * "^[#;%] [^ ]* game file", and a PGN tag block.
11649      * A game that starts with one of the latter two patterns
11650      * will also have a move number 1, possibly
11651      * following a position diagram.
11652      * 5-4-02: Let's try being more lenient and allowing a game to
11653      * start with an unnumbered move.  Does that break anything?
11654      */
11655     cm = lastLoadGameStart = EndOfFile;
11656     while (gn > 0) {
11657         yyboardindex = forwardMostMove;
11658         cm = (ChessMove) Myylex();
11659         switch (cm) {
11660           case EndOfFile:
11661             if (cmailMsgLoaded) {
11662                 nCmailGames = CMAIL_MAX_GAMES - gn;
11663             } else {
11664                 Reset(TRUE, TRUE);
11665                 DisplayError(_("Game not found in file"), 0);
11666             }
11667             return FALSE;
11668
11669           case GNUChessGame:
11670           case XBoardGame:
11671             gn--;
11672             lastLoadGameStart = cm;
11673             break;
11674
11675           case MoveNumberOne:
11676             switch (lastLoadGameStart) {
11677               case GNUChessGame:
11678               case XBoardGame:
11679               case PGNTag:
11680                 break;
11681               case MoveNumberOne:
11682               case EndOfFile:
11683                 gn--;           /* count this game */
11684                 lastLoadGameStart = cm;
11685                 break;
11686               default:
11687                 /* impossible */
11688                 break;
11689             }
11690             break;
11691
11692           case PGNTag:
11693             switch (lastLoadGameStart) {
11694               case GNUChessGame:
11695               case PGNTag:
11696               case MoveNumberOne:
11697               case EndOfFile:
11698                 gn--;           /* count this game */
11699                 lastLoadGameStart = cm;
11700                 break;
11701               case XBoardGame:
11702                 lastLoadGameStart = cm; /* game counted already */
11703                 break;
11704               default:
11705                 /* impossible */
11706                 break;
11707             }
11708             if (gn > 0) {
11709                 do {
11710                     yyboardindex = forwardMostMove;
11711                     cm = (ChessMove) Myylex();
11712                 } while (cm == PGNTag || cm == Comment);
11713             }
11714             break;
11715
11716           case WhiteWins:
11717           case BlackWins:
11718           case GameIsDrawn:
11719             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11720                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11721                     != CMAIL_OLD_RESULT) {
11722                     nCmailResults ++ ;
11723                     cmailResult[  CMAIL_MAX_GAMES
11724                                 - gn - 1] = CMAIL_OLD_RESULT;
11725                 }
11726             }
11727             break;
11728
11729           case NormalMove:
11730             /* Only a NormalMove can be at the start of a game
11731              * without a position diagram. */
11732             if (lastLoadGameStart == EndOfFile ) {
11733               gn--;
11734               lastLoadGameStart = MoveNumberOne;
11735             }
11736             break;
11737
11738           default:
11739             break;
11740         }
11741     }
11742
11743     if (appData.debugMode)
11744       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11745
11746     if (cm == XBoardGame) {
11747         /* Skip any header junk before position diagram and/or move 1 */
11748         for (;;) {
11749             yyboardindex = forwardMostMove;
11750             cm = (ChessMove) Myylex();
11751
11752             if (cm == EndOfFile ||
11753                 cm == GNUChessGame || cm == XBoardGame) {
11754                 /* Empty game; pretend end-of-file and handle later */
11755                 cm = EndOfFile;
11756                 break;
11757             }
11758
11759             if (cm == MoveNumberOne || cm == PositionDiagram ||
11760                 cm == PGNTag || cm == Comment)
11761               break;
11762         }
11763     } else if (cm == GNUChessGame) {
11764         if (gameInfo.event != NULL) {
11765             free(gameInfo.event);
11766         }
11767         gameInfo.event = StrSave(yy_text);
11768     }
11769
11770     startedFromSetupPosition = FALSE;
11771     while (cm == PGNTag) {
11772         if (appData.debugMode)
11773           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11774         err = ParsePGNTag(yy_text, &gameInfo);
11775         if (!err) numPGNTags++;
11776
11777         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11778         if(gameInfo.variant != oldVariant) {
11779             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11780             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11781             InitPosition(TRUE);
11782             oldVariant = gameInfo.variant;
11783             if (appData.debugMode)
11784               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11785         }
11786
11787
11788         if (gameInfo.fen != NULL) {
11789           Board initial_position;
11790           startedFromSetupPosition = TRUE;
11791           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11792             Reset(TRUE, TRUE);
11793             DisplayError(_("Bad FEN position in file"), 0);
11794             return FALSE;
11795           }
11796           CopyBoard(boards[0], initial_position);
11797           if (blackPlaysFirst) {
11798             currentMove = forwardMostMove = backwardMostMove = 1;
11799             CopyBoard(boards[1], initial_position);
11800             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11801             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11802             timeRemaining[0][1] = whiteTimeRemaining;
11803             timeRemaining[1][1] = blackTimeRemaining;
11804             if (commentList[0] != NULL) {
11805               commentList[1] = commentList[0];
11806               commentList[0] = NULL;
11807             }
11808           } else {
11809             currentMove = forwardMostMove = backwardMostMove = 0;
11810           }
11811           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11812           {   int i;
11813               initialRulePlies = FENrulePlies;
11814               for( i=0; i< nrCastlingRights; i++ )
11815                   initialRights[i] = initial_position[CASTLING][i];
11816           }
11817           yyboardindex = forwardMostMove;
11818           free(gameInfo.fen);
11819           gameInfo.fen = NULL;
11820         }
11821
11822         yyboardindex = forwardMostMove;
11823         cm = (ChessMove) Myylex();
11824
11825         /* Handle comments interspersed among the tags */
11826         while (cm == Comment) {
11827             char *p;
11828             if (appData.debugMode)
11829               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11830             p = yy_text;
11831             AppendComment(currentMove, p, FALSE);
11832             yyboardindex = forwardMostMove;
11833             cm = (ChessMove) Myylex();
11834         }
11835     }
11836
11837     /* don't rely on existence of Event tag since if game was
11838      * pasted from clipboard the Event tag may not exist
11839      */
11840     if (numPGNTags > 0){
11841         char *tags;
11842         if (gameInfo.variant == VariantNormal) {
11843           VariantClass v = StringToVariant(gameInfo.event);
11844           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11845           if(v < VariantShogi) gameInfo.variant = v;
11846         }
11847         if (!matchMode) {
11848           if( appData.autoDisplayTags ) {
11849             tags = PGNTags(&gameInfo);
11850             TagsPopUp(tags, CmailMsg());
11851             free(tags);
11852           }
11853         }
11854     } else {
11855         /* Make something up, but don't display it now */
11856         SetGameInfo();
11857         TagsPopDown();
11858     }
11859
11860     if (cm == PositionDiagram) {
11861         int i, j;
11862         char *p;
11863         Board initial_position;
11864
11865         if (appData.debugMode)
11866           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11867
11868         if (!startedFromSetupPosition) {
11869             p = yy_text;
11870             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11871               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11872                 switch (*p) {
11873                   case '{':
11874                   case '[':
11875                   case '-':
11876                   case ' ':
11877                   case '\t':
11878                   case '\n':
11879                   case '\r':
11880                     break;
11881                   default:
11882                     initial_position[i][j++] = CharToPiece(*p);
11883                     break;
11884                 }
11885             while (*p == ' ' || *p == '\t' ||
11886                    *p == '\n' || *p == '\r') p++;
11887
11888             if (strncmp(p, "black", strlen("black"))==0)
11889               blackPlaysFirst = TRUE;
11890             else
11891               blackPlaysFirst = FALSE;
11892             startedFromSetupPosition = TRUE;
11893
11894             CopyBoard(boards[0], initial_position);
11895             if (blackPlaysFirst) {
11896                 currentMove = forwardMostMove = backwardMostMove = 1;
11897                 CopyBoard(boards[1], initial_position);
11898                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11899                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11900                 timeRemaining[0][1] = whiteTimeRemaining;
11901                 timeRemaining[1][1] = blackTimeRemaining;
11902                 if (commentList[0] != NULL) {
11903                     commentList[1] = commentList[0];
11904                     commentList[0] = NULL;
11905                 }
11906             } else {
11907                 currentMove = forwardMostMove = backwardMostMove = 0;
11908             }
11909         }
11910         yyboardindex = forwardMostMove;
11911         cm = (ChessMove) Myylex();
11912     }
11913
11914     if (first.pr == NoProc) {
11915         StartChessProgram(&first);
11916     }
11917     InitChessProgram(&first, FALSE);
11918     SendToProgram("force\n", &first);
11919     if (startedFromSetupPosition) {
11920         SendBoard(&first, forwardMostMove);
11921     if (appData.debugMode) {
11922         fprintf(debugFP, "Load Game\n");
11923     }
11924         DisplayBothClocks();
11925     }
11926
11927     /* [HGM] server: flag to write setup moves in broadcast file as one */
11928     loadFlag = appData.suppressLoadMoves;
11929
11930     while (cm == Comment) {
11931         char *p;
11932         if (appData.debugMode)
11933           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11934         p = yy_text;
11935         AppendComment(currentMove, p, FALSE);
11936         yyboardindex = forwardMostMove;
11937         cm = (ChessMove) Myylex();
11938     }
11939
11940     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11941         cm == WhiteWins || cm == BlackWins ||
11942         cm == GameIsDrawn || cm == GameUnfinished) {
11943         DisplayMessage("", _("No moves in game"));
11944         if (cmailMsgLoaded) {
11945             if (appData.debugMode)
11946               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11947             ClearHighlights();
11948             flipView = FALSE;
11949         }
11950         DrawPosition(FALSE, boards[currentMove]);
11951         DisplayBothClocks();
11952         gameMode = EditGame;
11953         ModeHighlight();
11954         gameFileFP = NULL;
11955         cmailOldMove = 0;
11956         return TRUE;
11957     }
11958
11959     // [HGM] PV info: routine tests if comment empty
11960     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11961         DisplayComment(currentMove - 1, commentList[currentMove]);
11962     }
11963     if (!matchMode && appData.timeDelay != 0)
11964       DrawPosition(FALSE, boards[currentMove]);
11965
11966     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11967       programStats.ok_to_send = 1;
11968     }
11969
11970     /* if the first token after the PGN tags is a move
11971      * and not move number 1, retrieve it from the parser
11972      */
11973     if (cm != MoveNumberOne)
11974         LoadGameOneMove(cm);
11975
11976     /* load the remaining moves from the file */
11977     while (LoadGameOneMove(EndOfFile)) {
11978       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11979       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11980     }
11981
11982     /* rewind to the start of the game */
11983     currentMove = backwardMostMove;
11984
11985     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11986
11987     if (oldGameMode == AnalyzeFile ||
11988         oldGameMode == AnalyzeMode) {
11989       AnalyzeFileEvent();
11990     }
11991
11992     if (!matchMode && pos >= 0) {
11993         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11994     } else
11995     if (matchMode || appData.timeDelay == 0) {
11996       ToEndEvent();
11997     } else if (appData.timeDelay > 0) {
11998       AutoPlayGameLoop();
11999     }
12000
12001     if (appData.debugMode)
12002         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12003
12004     loadFlag = 0; /* [HGM] true game starts */
12005     return TRUE;
12006 }
12007
12008 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12009 int
12010 ReloadPosition (int offset)
12011 {
12012     int positionNumber = lastLoadPositionNumber + offset;
12013     if (lastLoadPositionFP == NULL) {
12014         DisplayError(_("No position has been loaded yet"), 0);
12015         return FALSE;
12016     }
12017     if (positionNumber <= 0) {
12018         DisplayError(_("Can't back up any further"), 0);
12019         return FALSE;
12020     }
12021     return LoadPosition(lastLoadPositionFP, positionNumber,
12022                         lastLoadPositionTitle);
12023 }
12024
12025 /* Load the nth position from the given file */
12026 int
12027 LoadPositionFromFile (char *filename, int n, char *title)
12028 {
12029     FILE *f;
12030     char buf[MSG_SIZ];
12031
12032     if (strcmp(filename, "-") == 0) {
12033         return LoadPosition(stdin, n, "stdin");
12034     } else {
12035         f = fopen(filename, "rb");
12036         if (f == NULL) {
12037             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12038             DisplayError(buf, errno);
12039             return FALSE;
12040         } else {
12041             return LoadPosition(f, n, title);
12042         }
12043     }
12044 }
12045
12046 /* Load the nth position from the given open file, and close it */
12047 int
12048 LoadPosition (FILE *f, int positionNumber, char *title)
12049 {
12050     char *p, line[MSG_SIZ];
12051     Board initial_position;
12052     int i, j, fenMode, pn;
12053
12054     if (gameMode == Training )
12055         SetTrainingModeOff();
12056
12057     if (gameMode != BeginningOfGame) {
12058         Reset(FALSE, TRUE);
12059     }
12060     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12061         fclose(lastLoadPositionFP);
12062     }
12063     if (positionNumber == 0) positionNumber = 1;
12064     lastLoadPositionFP = f;
12065     lastLoadPositionNumber = positionNumber;
12066     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12067     if (first.pr == NoProc && !appData.noChessProgram) {
12068       StartChessProgram(&first);
12069       InitChessProgram(&first, FALSE);
12070     }
12071     pn = positionNumber;
12072     if (positionNumber < 0) {
12073         /* Negative position number means to seek to that byte offset */
12074         if (fseek(f, -positionNumber, 0) == -1) {
12075             DisplayError(_("Can't seek on position file"), 0);
12076             return FALSE;
12077         };
12078         pn = 1;
12079     } else {
12080         if (fseek(f, 0, 0) == -1) {
12081             if (f == lastLoadPositionFP ?
12082                 positionNumber == lastLoadPositionNumber + 1 :
12083                 positionNumber == 1) {
12084                 pn = 1;
12085             } else {
12086                 DisplayError(_("Can't seek on position file"), 0);
12087                 return FALSE;
12088             }
12089         }
12090     }
12091     /* See if this file is FEN or old-style xboard */
12092     if (fgets(line, MSG_SIZ, f) == NULL) {
12093         DisplayError(_("Position not found in file"), 0);
12094         return FALSE;
12095     }
12096     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12097     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12098
12099     if (pn >= 2) {
12100         if (fenMode || line[0] == '#') pn--;
12101         while (pn > 0) {
12102             /* skip positions before number pn */
12103             if (fgets(line, MSG_SIZ, f) == NULL) {
12104                 Reset(TRUE, TRUE);
12105                 DisplayError(_("Position not found in file"), 0);
12106                 return FALSE;
12107             }
12108             if (fenMode || line[0] == '#') pn--;
12109         }
12110     }
12111
12112     if (fenMode) {
12113         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12114             DisplayError(_("Bad FEN position in file"), 0);
12115             return FALSE;
12116         }
12117     } else {
12118         (void) fgets(line, MSG_SIZ, f);
12119         (void) fgets(line, MSG_SIZ, f);
12120
12121         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12122             (void) fgets(line, MSG_SIZ, f);
12123             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12124                 if (*p == ' ')
12125                   continue;
12126                 initial_position[i][j++] = CharToPiece(*p);
12127             }
12128         }
12129
12130         blackPlaysFirst = FALSE;
12131         if (!feof(f)) {
12132             (void) fgets(line, MSG_SIZ, f);
12133             if (strncmp(line, "black", strlen("black"))==0)
12134               blackPlaysFirst = TRUE;
12135         }
12136     }
12137     startedFromSetupPosition = TRUE;
12138
12139     CopyBoard(boards[0], initial_position);
12140     if (blackPlaysFirst) {
12141         currentMove = forwardMostMove = backwardMostMove = 1;
12142         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12143         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12144         CopyBoard(boards[1], initial_position);
12145         DisplayMessage("", _("Black to play"));
12146     } else {
12147         currentMove = forwardMostMove = backwardMostMove = 0;
12148         DisplayMessage("", _("White to play"));
12149     }
12150     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12151     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12152         SendToProgram("force\n", &first);
12153         SendBoard(&first, forwardMostMove);
12154     }
12155     if (appData.debugMode) {
12156 int i, j;
12157   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12158   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12159         fprintf(debugFP, "Load Position\n");
12160     }
12161
12162     if (positionNumber > 1) {
12163       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12164         DisplayTitle(line);
12165     } else {
12166         DisplayTitle(title);
12167     }
12168     gameMode = EditGame;
12169     ModeHighlight();
12170     ResetClocks();
12171     timeRemaining[0][1] = whiteTimeRemaining;
12172     timeRemaining[1][1] = blackTimeRemaining;
12173     DrawPosition(FALSE, boards[currentMove]);
12174
12175     return TRUE;
12176 }
12177
12178
12179 void
12180 CopyPlayerNameIntoFileName (char **dest, char *src)
12181 {
12182     while (*src != NULLCHAR && *src != ',') {
12183         if (*src == ' ') {
12184             *(*dest)++ = '_';
12185             src++;
12186         } else {
12187             *(*dest)++ = *src++;
12188         }
12189     }
12190 }
12191
12192 char *
12193 DefaultFileName (char *ext)
12194 {
12195     static char def[MSG_SIZ];
12196     char *p;
12197
12198     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12199         p = def;
12200         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12201         *p++ = '-';
12202         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12203         *p++ = '.';
12204         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12205     } else {
12206         def[0] = NULLCHAR;
12207     }
12208     return def;
12209 }
12210
12211 /* Save the current game to the given file */
12212 int
12213 SaveGameToFile (char *filename, int append)
12214 {
12215     FILE *f;
12216     char buf[MSG_SIZ];
12217     int result, i, t,tot=0;
12218
12219     if (strcmp(filename, "-") == 0) {
12220         return SaveGame(stdout, 0, NULL);
12221     } else {
12222         for(i=0; i<10; i++) { // upto 10 tries
12223              f = fopen(filename, append ? "a" : "w");
12224              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12225              if(f || errno != 13) break;
12226              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12227              tot += t;
12228         }
12229         if (f == NULL) {
12230             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12231             DisplayError(buf, errno);
12232             return FALSE;
12233         } else {
12234             safeStrCpy(buf, lastMsg, MSG_SIZ);
12235             DisplayMessage(_("Waiting for access to save file"), "");
12236             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12237             DisplayMessage(_("Saving game"), "");
12238             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12239             result = SaveGame(f, 0, NULL);
12240             DisplayMessage(buf, "");
12241             return result;
12242         }
12243     }
12244 }
12245
12246 char *
12247 SavePart (char *str)
12248 {
12249     static char buf[MSG_SIZ];
12250     char *p;
12251
12252     p = strchr(str, ' ');
12253     if (p == NULL) return str;
12254     strncpy(buf, str, p - str);
12255     buf[p - str] = NULLCHAR;
12256     return buf;
12257 }
12258
12259 #define PGN_MAX_LINE 75
12260
12261 #define PGN_SIDE_WHITE  0
12262 #define PGN_SIDE_BLACK  1
12263
12264 static int
12265 FindFirstMoveOutOfBook (int side)
12266 {
12267     int result = -1;
12268
12269     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12270         int index = backwardMostMove;
12271         int has_book_hit = 0;
12272
12273         if( (index % 2) != side ) {
12274             index++;
12275         }
12276
12277         while( index < forwardMostMove ) {
12278             /* Check to see if engine is in book */
12279             int depth = pvInfoList[index].depth;
12280             int score = pvInfoList[index].score;
12281             int in_book = 0;
12282
12283             if( depth <= 2 ) {
12284                 in_book = 1;
12285             }
12286             else if( score == 0 && depth == 63 ) {
12287                 in_book = 1; /* Zappa */
12288             }
12289             else if( score == 2 && depth == 99 ) {
12290                 in_book = 1; /* Abrok */
12291             }
12292
12293             has_book_hit += in_book;
12294
12295             if( ! in_book ) {
12296                 result = index;
12297
12298                 break;
12299             }
12300
12301             index += 2;
12302         }
12303     }
12304
12305     return result;
12306 }
12307
12308 void
12309 GetOutOfBookInfo (char * buf)
12310 {
12311     int oob[2];
12312     int i;
12313     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12314
12315     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12316     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12317
12318     *buf = '\0';
12319
12320     if( oob[0] >= 0 || oob[1] >= 0 ) {
12321         for( i=0; i<2; i++ ) {
12322             int idx = oob[i];
12323
12324             if( idx >= 0 ) {
12325                 if( i > 0 && oob[0] >= 0 ) {
12326                     strcat( buf, "   " );
12327                 }
12328
12329                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12330                 sprintf( buf+strlen(buf), "%s%.2f",
12331                     pvInfoList[idx].score >= 0 ? "+" : "",
12332                     pvInfoList[idx].score / 100.0 );
12333             }
12334         }
12335     }
12336 }
12337
12338 /* Save game in PGN style and close the file */
12339 int
12340 SaveGamePGN (FILE *f)
12341 {
12342     int i, offset, linelen, newblock;
12343     time_t tm;
12344 //    char *movetext;
12345     char numtext[32];
12346     int movelen, numlen, blank;
12347     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12348
12349     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12350
12351     tm = time((time_t *) NULL);
12352
12353     PrintPGNTags(f, &gameInfo);
12354
12355     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12356
12357     if (backwardMostMove > 0 || startedFromSetupPosition) {
12358         char *fen = PositionToFEN(backwardMostMove, NULL);
12359         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12360         fprintf(f, "\n{--------------\n");
12361         PrintPosition(f, backwardMostMove);
12362         fprintf(f, "--------------}\n");
12363         free(fen);
12364     }
12365     else {
12366         /* [AS] Out of book annotation */
12367         if( appData.saveOutOfBookInfo ) {
12368             char buf[64];
12369
12370             GetOutOfBookInfo( buf );
12371
12372             if( buf[0] != '\0' ) {
12373                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12374             }
12375         }
12376
12377         fprintf(f, "\n");
12378     }
12379
12380     i = backwardMostMove;
12381     linelen = 0;
12382     newblock = TRUE;
12383
12384     while (i < forwardMostMove) {
12385         /* Print comments preceding this move */
12386         if (commentList[i] != NULL) {
12387             if (linelen > 0) fprintf(f, "\n");
12388             fprintf(f, "%s", commentList[i]);
12389             linelen = 0;
12390             newblock = TRUE;
12391         }
12392
12393         /* Format move number */
12394         if ((i % 2) == 0)
12395           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12396         else
12397           if (newblock)
12398             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12399           else
12400             numtext[0] = NULLCHAR;
12401
12402         numlen = strlen(numtext);
12403         newblock = FALSE;
12404
12405         /* Print move number */
12406         blank = linelen > 0 && numlen > 0;
12407         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12408             fprintf(f, "\n");
12409             linelen = 0;
12410             blank = 0;
12411         }
12412         if (blank) {
12413             fprintf(f, " ");
12414             linelen++;
12415         }
12416         fprintf(f, "%s", numtext);
12417         linelen += numlen;
12418
12419         /* Get move */
12420         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12421         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12422
12423         /* Print move */
12424         blank = linelen > 0 && movelen > 0;
12425         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12426             fprintf(f, "\n");
12427             linelen = 0;
12428             blank = 0;
12429         }
12430         if (blank) {
12431             fprintf(f, " ");
12432             linelen++;
12433         }
12434         fprintf(f, "%s", move_buffer);
12435         linelen += movelen;
12436
12437         /* [AS] Add PV info if present */
12438         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12439             /* [HGM] add time */
12440             char buf[MSG_SIZ]; int seconds;
12441
12442             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12443
12444             if( seconds <= 0)
12445               buf[0] = 0;
12446             else
12447               if( seconds < 30 )
12448                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12449               else
12450                 {
12451                   seconds = (seconds + 4)/10; // round to full seconds
12452                   if( seconds < 60 )
12453                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12454                   else
12455                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12456                 }
12457
12458             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12459                       pvInfoList[i].score >= 0 ? "+" : "",
12460                       pvInfoList[i].score / 100.0,
12461                       pvInfoList[i].depth,
12462                       buf );
12463
12464             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12465
12466             /* Print score/depth */
12467             blank = linelen > 0 && movelen > 0;
12468             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12469                 fprintf(f, "\n");
12470                 linelen = 0;
12471                 blank = 0;
12472             }
12473             if (blank) {
12474                 fprintf(f, " ");
12475                 linelen++;
12476             }
12477             fprintf(f, "%s", move_buffer);
12478             linelen += movelen;
12479         }
12480
12481         i++;
12482     }
12483
12484     /* Start a new line */
12485     if (linelen > 0) fprintf(f, "\n");
12486
12487     /* Print comments after last move */
12488     if (commentList[i] != NULL) {
12489         fprintf(f, "%s\n", commentList[i]);
12490     }
12491
12492     /* Print result */
12493     if (gameInfo.resultDetails != NULL &&
12494         gameInfo.resultDetails[0] != NULLCHAR) {
12495         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12496                 PGNResult(gameInfo.result));
12497     } else {
12498         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12499     }
12500
12501     fclose(f);
12502     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12503     return TRUE;
12504 }
12505
12506 /* Save game in old style and close the file */
12507 int
12508 SaveGameOldStyle (FILE *f)
12509 {
12510     int i, offset;
12511     time_t tm;
12512
12513     tm = time((time_t *) NULL);
12514
12515     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12516     PrintOpponents(f);
12517
12518     if (backwardMostMove > 0 || startedFromSetupPosition) {
12519         fprintf(f, "\n[--------------\n");
12520         PrintPosition(f, backwardMostMove);
12521         fprintf(f, "--------------]\n");
12522     } else {
12523         fprintf(f, "\n");
12524     }
12525
12526     i = backwardMostMove;
12527     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12528
12529     while (i < forwardMostMove) {
12530         if (commentList[i] != NULL) {
12531             fprintf(f, "[%s]\n", commentList[i]);
12532         }
12533
12534         if ((i % 2) == 1) {
12535             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12536             i++;
12537         } else {
12538             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12539             i++;
12540             if (commentList[i] != NULL) {
12541                 fprintf(f, "\n");
12542                 continue;
12543             }
12544             if (i >= forwardMostMove) {
12545                 fprintf(f, "\n");
12546                 break;
12547             }
12548             fprintf(f, "%s\n", parseList[i]);
12549             i++;
12550         }
12551     }
12552
12553     if (commentList[i] != NULL) {
12554         fprintf(f, "[%s]\n", commentList[i]);
12555     }
12556
12557     /* This isn't really the old style, but it's close enough */
12558     if (gameInfo.resultDetails != NULL &&
12559         gameInfo.resultDetails[0] != NULLCHAR) {
12560         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12561                 gameInfo.resultDetails);
12562     } else {
12563         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12564     }
12565
12566     fclose(f);
12567     return TRUE;
12568 }
12569
12570 /* Save the current game to open file f and close the file */
12571 int
12572 SaveGame (FILE *f, int dummy, char *dummy2)
12573 {
12574     if (gameMode == EditPosition) EditPositionDone(TRUE);
12575     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12576     if (appData.oldSaveStyle)
12577       return SaveGameOldStyle(f);
12578     else
12579       return SaveGamePGN(f);
12580 }
12581
12582 /* Save the current position to the given file */
12583 int
12584 SavePositionToFile (char *filename)
12585 {
12586     FILE *f;
12587     char buf[MSG_SIZ];
12588
12589     if (strcmp(filename, "-") == 0) {
12590         return SavePosition(stdout, 0, NULL);
12591     } else {
12592         f = fopen(filename, "a");
12593         if (f == NULL) {
12594             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12595             DisplayError(buf, errno);
12596             return FALSE;
12597         } else {
12598             safeStrCpy(buf, lastMsg, MSG_SIZ);
12599             DisplayMessage(_("Waiting for access to save file"), "");
12600             flock(fileno(f), LOCK_EX); // [HGM] lock
12601             DisplayMessage(_("Saving position"), "");
12602             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12603             SavePosition(f, 0, NULL);
12604             DisplayMessage(buf, "");
12605             return TRUE;
12606         }
12607     }
12608 }
12609
12610 /* Save the current position to the given open file and close the file */
12611 int
12612 SavePosition (FILE *f, int dummy, char *dummy2)
12613 {
12614     time_t tm;
12615     char *fen;
12616
12617     if (gameMode == EditPosition) EditPositionDone(TRUE);
12618     if (appData.oldSaveStyle) {
12619         tm = time((time_t *) NULL);
12620
12621         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12622         PrintOpponents(f);
12623         fprintf(f, "[--------------\n");
12624         PrintPosition(f, currentMove);
12625         fprintf(f, "--------------]\n");
12626     } else {
12627         fen = PositionToFEN(currentMove, NULL);
12628         fprintf(f, "%s\n", fen);
12629         free(fen);
12630     }
12631     fclose(f);
12632     return TRUE;
12633 }
12634
12635 void
12636 ReloadCmailMsgEvent (int unregister)
12637 {
12638 #if !WIN32
12639     static char *inFilename = NULL;
12640     static char *outFilename;
12641     int i;
12642     struct stat inbuf, outbuf;
12643     int status;
12644
12645     /* Any registered moves are unregistered if unregister is set, */
12646     /* i.e. invoked by the signal handler */
12647     if (unregister) {
12648         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12649             cmailMoveRegistered[i] = FALSE;
12650             if (cmailCommentList[i] != NULL) {
12651                 free(cmailCommentList[i]);
12652                 cmailCommentList[i] = NULL;
12653             }
12654         }
12655         nCmailMovesRegistered = 0;
12656     }
12657
12658     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12659         cmailResult[i] = CMAIL_NOT_RESULT;
12660     }
12661     nCmailResults = 0;
12662
12663     if (inFilename == NULL) {
12664         /* Because the filenames are static they only get malloced once  */
12665         /* and they never get freed                                      */
12666         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12667         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12668
12669         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12670         sprintf(outFilename, "%s.out", appData.cmailGameName);
12671     }
12672
12673     status = stat(outFilename, &outbuf);
12674     if (status < 0) {
12675         cmailMailedMove = FALSE;
12676     } else {
12677         status = stat(inFilename, &inbuf);
12678         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12679     }
12680
12681     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12682        counts the games, notes how each one terminated, etc.
12683
12684        It would be nice to remove this kludge and instead gather all
12685        the information while building the game list.  (And to keep it
12686        in the game list nodes instead of having a bunch of fixed-size
12687        parallel arrays.)  Note this will require getting each game's
12688        termination from the PGN tags, as the game list builder does
12689        not process the game moves.  --mann
12690        */
12691     cmailMsgLoaded = TRUE;
12692     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12693
12694     /* Load first game in the file or popup game menu */
12695     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12696
12697 #endif /* !WIN32 */
12698     return;
12699 }
12700
12701 int
12702 RegisterMove ()
12703 {
12704     FILE *f;
12705     char string[MSG_SIZ];
12706
12707     if (   cmailMailedMove
12708         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12709         return TRUE;            /* Allow free viewing  */
12710     }
12711
12712     /* Unregister move to ensure that we don't leave RegisterMove        */
12713     /* with the move registered when the conditions for registering no   */
12714     /* longer hold                                                       */
12715     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12716         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12717         nCmailMovesRegistered --;
12718
12719         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12720           {
12721               free(cmailCommentList[lastLoadGameNumber - 1]);
12722               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12723           }
12724     }
12725
12726     if (cmailOldMove == -1) {
12727         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12728         return FALSE;
12729     }
12730
12731     if (currentMove > cmailOldMove + 1) {
12732         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12733         return FALSE;
12734     }
12735
12736     if (currentMove < cmailOldMove) {
12737         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12738         return FALSE;
12739     }
12740
12741     if (forwardMostMove > currentMove) {
12742         /* Silently truncate extra moves */
12743         TruncateGame();
12744     }
12745
12746     if (   (currentMove == cmailOldMove + 1)
12747         || (   (currentMove == cmailOldMove)
12748             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12749                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12750         if (gameInfo.result != GameUnfinished) {
12751             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12752         }
12753
12754         if (commentList[currentMove] != NULL) {
12755             cmailCommentList[lastLoadGameNumber - 1]
12756               = StrSave(commentList[currentMove]);
12757         }
12758         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12759
12760         if (appData.debugMode)
12761           fprintf(debugFP, "Saving %s for game %d\n",
12762                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12763
12764         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12765
12766         f = fopen(string, "w");
12767         if (appData.oldSaveStyle) {
12768             SaveGameOldStyle(f); /* also closes the file */
12769
12770             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12771             f = fopen(string, "w");
12772             SavePosition(f, 0, NULL); /* also closes the file */
12773         } else {
12774             fprintf(f, "{--------------\n");
12775             PrintPosition(f, currentMove);
12776             fprintf(f, "--------------}\n\n");
12777
12778             SaveGame(f, 0, NULL); /* also closes the file*/
12779         }
12780
12781         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12782         nCmailMovesRegistered ++;
12783     } else if (nCmailGames == 1) {
12784         DisplayError(_("You have not made a move yet"), 0);
12785         return FALSE;
12786     }
12787
12788     return TRUE;
12789 }
12790
12791 void
12792 MailMoveEvent ()
12793 {
12794 #if !WIN32
12795     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12796     FILE *commandOutput;
12797     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12798     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12799     int nBuffers;
12800     int i;
12801     int archived;
12802     char *arcDir;
12803
12804     if (! cmailMsgLoaded) {
12805         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12806         return;
12807     }
12808
12809     if (nCmailGames == nCmailResults) {
12810         DisplayError(_("No unfinished games"), 0);
12811         return;
12812     }
12813
12814 #if CMAIL_PROHIBIT_REMAIL
12815     if (cmailMailedMove) {
12816       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);
12817         DisplayError(msg, 0);
12818         return;
12819     }
12820 #endif
12821
12822     if (! (cmailMailedMove || RegisterMove())) return;
12823
12824     if (   cmailMailedMove
12825         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12826       snprintf(string, MSG_SIZ, partCommandString,
12827                appData.debugMode ? " -v" : "", appData.cmailGameName);
12828         commandOutput = popen(string, "r");
12829
12830         if (commandOutput == NULL) {
12831             DisplayError(_("Failed to invoke cmail"), 0);
12832         } else {
12833             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12834                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12835             }
12836             if (nBuffers > 1) {
12837                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12838                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12839                 nBytes = MSG_SIZ - 1;
12840             } else {
12841                 (void) memcpy(msg, buffer, nBytes);
12842             }
12843             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12844
12845             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12846                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12847
12848                 archived = TRUE;
12849                 for (i = 0; i < nCmailGames; i ++) {
12850                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12851                         archived = FALSE;
12852                     }
12853                 }
12854                 if (   archived
12855                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12856                         != NULL)) {
12857                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12858                            arcDir,
12859                            appData.cmailGameName,
12860                            gameInfo.date);
12861                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12862                     cmailMsgLoaded = FALSE;
12863                 }
12864             }
12865
12866             DisplayInformation(msg);
12867             pclose(commandOutput);
12868         }
12869     } else {
12870         if ((*cmailMsg) != '\0') {
12871             DisplayInformation(cmailMsg);
12872         }
12873     }
12874
12875     return;
12876 #endif /* !WIN32 */
12877 }
12878
12879 char *
12880 CmailMsg ()
12881 {
12882 #if WIN32
12883     return NULL;
12884 #else
12885     int  prependComma = 0;
12886     char number[5];
12887     char string[MSG_SIZ];       /* Space for game-list */
12888     int  i;
12889
12890     if (!cmailMsgLoaded) return "";
12891
12892     if (cmailMailedMove) {
12893       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12894     } else {
12895         /* Create a list of games left */
12896       snprintf(string, MSG_SIZ, "[");
12897         for (i = 0; i < nCmailGames; i ++) {
12898             if (! (   cmailMoveRegistered[i]
12899                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12900                 if (prependComma) {
12901                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12902                 } else {
12903                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12904                     prependComma = 1;
12905                 }
12906
12907                 strcat(string, number);
12908             }
12909         }
12910         strcat(string, "]");
12911
12912         if (nCmailMovesRegistered + nCmailResults == 0) {
12913             switch (nCmailGames) {
12914               case 1:
12915                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12916                 break;
12917
12918               case 2:
12919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12920                 break;
12921
12922               default:
12923                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12924                          nCmailGames);
12925                 break;
12926             }
12927         } else {
12928             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12929               case 1:
12930                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12931                          string);
12932                 break;
12933
12934               case 0:
12935                 if (nCmailResults == nCmailGames) {
12936                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12937                 } else {
12938                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12939                 }
12940                 break;
12941
12942               default:
12943                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12944                          string);
12945             }
12946         }
12947     }
12948     return cmailMsg;
12949 #endif /* WIN32 */
12950 }
12951
12952 void
12953 ResetGameEvent ()
12954 {
12955     if (gameMode == Training)
12956       SetTrainingModeOff();
12957
12958     Reset(TRUE, TRUE);
12959     cmailMsgLoaded = FALSE;
12960     if (appData.icsActive) {
12961       SendToICS(ics_prefix);
12962       SendToICS("refresh\n");
12963     }
12964 }
12965
12966 void
12967 ExitEvent (int status)
12968 {
12969     exiting++;
12970     if (exiting > 2) {
12971       /* Give up on clean exit */
12972       exit(status);
12973     }
12974     if (exiting > 1) {
12975       /* Keep trying for clean exit */
12976       return;
12977     }
12978
12979     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12980
12981     if (telnetISR != NULL) {
12982       RemoveInputSource(telnetISR);
12983     }
12984     if (icsPR != NoProc) {
12985       DestroyChildProcess(icsPR, TRUE);
12986     }
12987
12988     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12989     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12990
12991     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12992     /* make sure this other one finishes before killing it!                  */
12993     if(endingGame) { int count = 0;
12994         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12995         while(endingGame && count++ < 10) DoSleep(1);
12996         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12997     }
12998
12999     /* Kill off chess programs */
13000     if (first.pr != NoProc) {
13001         ExitAnalyzeMode();
13002
13003         DoSleep( appData.delayBeforeQuit );
13004         SendToProgram("quit\n", &first);
13005         DoSleep( appData.delayAfterQuit );
13006         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13007     }
13008     if (second.pr != NoProc) {
13009         DoSleep( appData.delayBeforeQuit );
13010         SendToProgram("quit\n", &second);
13011         DoSleep( appData.delayAfterQuit );
13012         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13013     }
13014     if (first.isr != NULL) {
13015         RemoveInputSource(first.isr);
13016     }
13017     if (second.isr != NULL) {
13018         RemoveInputSource(second.isr);
13019     }
13020
13021     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13022     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13023
13024     ShutDownFrontEnd();
13025     exit(status);
13026 }
13027
13028 void
13029 PauseEvent ()
13030 {
13031     if (appData.debugMode)
13032         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13033     if (pausing) {
13034         pausing = FALSE;
13035         ModeHighlight();
13036         if (gameMode == MachinePlaysWhite ||
13037             gameMode == MachinePlaysBlack) {
13038             StartClocks();
13039         } else {
13040             DisplayBothClocks();
13041         }
13042         if (gameMode == PlayFromGameFile) {
13043             if (appData.timeDelay >= 0)
13044                 AutoPlayGameLoop();
13045         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13046             Reset(FALSE, TRUE);
13047             SendToICS(ics_prefix);
13048             SendToICS("refresh\n");
13049         } else if (currentMove < forwardMostMove) {
13050             ForwardInner(forwardMostMove);
13051         }
13052         pauseExamInvalid = FALSE;
13053     } else {
13054         switch (gameMode) {
13055           default:
13056             return;
13057           case IcsExamining:
13058             pauseExamForwardMostMove = forwardMostMove;
13059             pauseExamInvalid = FALSE;
13060             /* fall through */
13061           case IcsObserving:
13062           case IcsPlayingWhite:
13063           case IcsPlayingBlack:
13064             pausing = TRUE;
13065             ModeHighlight();
13066             return;
13067           case PlayFromGameFile:
13068             (void) StopLoadGameTimer();
13069             pausing = TRUE;
13070             ModeHighlight();
13071             break;
13072           case BeginningOfGame:
13073             if (appData.icsActive) return;
13074             /* else fall through */
13075           case MachinePlaysWhite:
13076           case MachinePlaysBlack:
13077           case TwoMachinesPlay:
13078             if (forwardMostMove == 0)
13079               return;           /* don't pause if no one has moved */
13080             if ((gameMode == MachinePlaysWhite &&
13081                  !WhiteOnMove(forwardMostMove)) ||
13082                 (gameMode == MachinePlaysBlack &&
13083                  WhiteOnMove(forwardMostMove))) {
13084                 StopClocks();
13085             }
13086             pausing = TRUE;
13087             ModeHighlight();
13088             break;
13089         }
13090     }
13091 }
13092
13093 void
13094 EditCommentEvent ()
13095 {
13096     char title[MSG_SIZ];
13097
13098     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13099       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13100     } else {
13101       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13102                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13103                parseList[currentMove - 1]);
13104     }
13105
13106     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13107 }
13108
13109
13110 void
13111 EditTagsEvent ()
13112 {
13113     char *tags = PGNTags(&gameInfo);
13114     bookUp = FALSE;
13115     EditTagsPopUp(tags, NULL);
13116     free(tags);
13117 }
13118
13119 void
13120 AnalyzeModeEvent ()
13121 {
13122     if (appData.noChessProgram || gameMode == AnalyzeMode)
13123       return;
13124
13125     if (gameMode != AnalyzeFile) {
13126         if (!appData.icsEngineAnalyze) {
13127                EditGameEvent();
13128                if (gameMode != EditGame) return;
13129         }
13130         ResurrectChessProgram();
13131         SendToProgram("analyze\n", &first);
13132         first.analyzing = TRUE;
13133         /*first.maybeThinking = TRUE;*/
13134         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13135         EngineOutputPopUp();
13136     }
13137     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13138     pausing = FALSE;
13139     ModeHighlight();
13140     SetGameInfo();
13141
13142     StartAnalysisClock();
13143     GetTimeMark(&lastNodeCountTime);
13144     lastNodeCount = 0;
13145 }
13146
13147 void
13148 AnalyzeFileEvent ()
13149 {
13150     if (appData.noChessProgram || gameMode == AnalyzeFile)
13151       return;
13152
13153     if (gameMode != AnalyzeMode) {
13154         EditGameEvent();
13155         if (gameMode != EditGame) return;
13156         ResurrectChessProgram();
13157         SendToProgram("analyze\n", &first);
13158         first.analyzing = TRUE;
13159         /*first.maybeThinking = TRUE;*/
13160         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13161         EngineOutputPopUp();
13162     }
13163     gameMode = AnalyzeFile;
13164     pausing = FALSE;
13165     ModeHighlight();
13166     SetGameInfo();
13167
13168     StartAnalysisClock();
13169     GetTimeMark(&lastNodeCountTime);
13170     lastNodeCount = 0;
13171     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13172 }
13173
13174 void
13175 MachineWhiteEvent ()
13176 {
13177     char buf[MSG_SIZ];
13178     char *bookHit = NULL;
13179
13180     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13181       return;
13182
13183
13184     if (gameMode == PlayFromGameFile ||
13185         gameMode == TwoMachinesPlay  ||
13186         gameMode == Training         ||
13187         gameMode == AnalyzeMode      ||
13188         gameMode == EndOfGame)
13189         EditGameEvent();
13190
13191     if (gameMode == EditPosition)
13192         EditPositionDone(TRUE);
13193
13194     if (!WhiteOnMove(currentMove)) {
13195         DisplayError(_("It is not White's turn"), 0);
13196         return;
13197     }
13198
13199     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13200       ExitAnalyzeMode();
13201
13202     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13203         gameMode == AnalyzeFile)
13204         TruncateGame();
13205
13206     ResurrectChessProgram();    /* in case it isn't running */
13207     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13208         gameMode = MachinePlaysWhite;
13209         ResetClocks();
13210     } else
13211     gameMode = MachinePlaysWhite;
13212     pausing = FALSE;
13213     ModeHighlight();
13214     SetGameInfo();
13215     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13216     DisplayTitle(buf);
13217     if (first.sendName) {
13218       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13219       SendToProgram(buf, &first);
13220     }
13221     if (first.sendTime) {
13222       if (first.useColors) {
13223         SendToProgram("black\n", &first); /*gnu kludge*/
13224       }
13225       SendTimeRemaining(&first, TRUE);
13226     }
13227     if (first.useColors) {
13228       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13229     }
13230     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13231     SetMachineThinkingEnables();
13232     first.maybeThinking = TRUE;
13233     StartClocks();
13234     firstMove = FALSE;
13235
13236     if (appData.autoFlipView && !flipView) {
13237       flipView = !flipView;
13238       DrawPosition(FALSE, NULL);
13239       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13240     }
13241
13242     if(bookHit) { // [HGM] book: simulate book reply
13243         static char bookMove[MSG_SIZ]; // a bit generous?
13244
13245         programStats.nodes = programStats.depth = programStats.time =
13246         programStats.score = programStats.got_only_move = 0;
13247         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13248
13249         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13250         strcat(bookMove, bookHit);
13251         HandleMachineMove(bookMove, &first);
13252     }
13253 }
13254
13255 void
13256 MachineBlackEvent ()
13257 {
13258   char buf[MSG_SIZ];
13259   char *bookHit = NULL;
13260
13261     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13262         return;
13263
13264
13265     if (gameMode == PlayFromGameFile ||
13266         gameMode == TwoMachinesPlay  ||
13267         gameMode == Training         ||
13268         gameMode == AnalyzeMode      ||
13269         gameMode == EndOfGame)
13270         EditGameEvent();
13271
13272     if (gameMode == EditPosition)
13273         EditPositionDone(TRUE);
13274
13275     if (WhiteOnMove(currentMove)) {
13276         DisplayError(_("It is not Black's turn"), 0);
13277         return;
13278     }
13279
13280     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13281       ExitAnalyzeMode();
13282
13283     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13284         gameMode == AnalyzeFile)
13285         TruncateGame();
13286
13287     ResurrectChessProgram();    /* in case it isn't running */
13288     gameMode = MachinePlaysBlack;
13289     pausing = FALSE;
13290     ModeHighlight();
13291     SetGameInfo();
13292     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13293     DisplayTitle(buf);
13294     if (first.sendName) {
13295       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13296       SendToProgram(buf, &first);
13297     }
13298     if (first.sendTime) {
13299       if (first.useColors) {
13300         SendToProgram("white\n", &first); /*gnu kludge*/
13301       }
13302       SendTimeRemaining(&first, FALSE);
13303     }
13304     if (first.useColors) {
13305       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13306     }
13307     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13308     SetMachineThinkingEnables();
13309     first.maybeThinking = TRUE;
13310     StartClocks();
13311
13312     if (appData.autoFlipView && flipView) {
13313       flipView = !flipView;
13314       DrawPosition(FALSE, NULL);
13315       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13316     }
13317     if(bookHit) { // [HGM] book: simulate book reply
13318         static char bookMove[MSG_SIZ]; // a bit generous?
13319
13320         programStats.nodes = programStats.depth = programStats.time =
13321         programStats.score = programStats.got_only_move = 0;
13322         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13323
13324         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13325         strcat(bookMove, bookHit);
13326         HandleMachineMove(bookMove, &first);
13327     }
13328 }
13329
13330
13331 void
13332 DisplayTwoMachinesTitle ()
13333 {
13334     char buf[MSG_SIZ];
13335     if (appData.matchGames > 0) {
13336         if(appData.tourneyFile[0]) {
13337           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13338                    gameInfo.white, _("vs."), gameInfo.black,
13339                    nextGame+1, appData.matchGames+1,
13340                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13341         } else 
13342         if (first.twoMachinesColor[0] == 'w') {
13343           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13344                    gameInfo.white, _("vs."),  gameInfo.black,
13345                    first.matchWins, second.matchWins,
13346                    matchGame - 1 - (first.matchWins + second.matchWins));
13347         } else {
13348           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13349                    gameInfo.white, _("vs."), gameInfo.black,
13350                    second.matchWins, first.matchWins,
13351                    matchGame - 1 - (first.matchWins + second.matchWins));
13352         }
13353     } else {
13354       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13355     }
13356     DisplayTitle(buf);
13357 }
13358
13359 void
13360 SettingsMenuIfReady ()
13361 {
13362   if (second.lastPing != second.lastPong) {
13363     DisplayMessage("", _("Waiting for second chess program"));
13364     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13365     return;
13366   }
13367   ThawUI();
13368   DisplayMessage("", "");
13369   SettingsPopUp(&second);
13370 }
13371
13372 int
13373 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13374 {
13375     char buf[MSG_SIZ];
13376     if (cps->pr == NoProc) {
13377         StartChessProgram(cps);
13378         if (cps->protocolVersion == 1) {
13379           retry();
13380         } else {
13381           /* kludge: allow timeout for initial "feature" command */
13382           FreezeUI();
13383           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13384           DisplayMessage("", buf);
13385           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13386         }
13387         return 1;
13388     }
13389     return 0;
13390 }
13391
13392 void
13393 TwoMachinesEvent P((void))
13394 {
13395     int i;
13396     char buf[MSG_SIZ];
13397     ChessProgramState *onmove;
13398     char *bookHit = NULL;
13399     static int stalling = 0;
13400     TimeMark now;
13401     long wait;
13402
13403     if (appData.noChessProgram) return;
13404
13405     switch (gameMode) {
13406       case TwoMachinesPlay:
13407         return;
13408       case MachinePlaysWhite:
13409       case MachinePlaysBlack:
13410         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13411             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13412             return;
13413         }
13414         /* fall through */
13415       case BeginningOfGame:
13416       case PlayFromGameFile:
13417       case EndOfGame:
13418         EditGameEvent();
13419         if (gameMode != EditGame) return;
13420         break;
13421       case EditPosition:
13422         EditPositionDone(TRUE);
13423         break;
13424       case AnalyzeMode:
13425       case AnalyzeFile:
13426         ExitAnalyzeMode();
13427         break;
13428       case EditGame:
13429       default:
13430         break;
13431     }
13432
13433 //    forwardMostMove = currentMove;
13434     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13435
13436     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13437
13438     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13439     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13440       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13441       return;
13442     }
13443     if(!stalling) {
13444       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13445       SendToProgram("force\n", &second);
13446       stalling = 1;
13447       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13448       return;
13449     }
13450     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13451     if(appData.matchPause>10000 || appData.matchPause<10)
13452                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13453     wait = SubtractTimeMarks(&now, &pauseStart);
13454     if(wait < appData.matchPause) {
13455         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13456         return;
13457     }
13458     // we are now committed to starting the game
13459     stalling = 0;
13460     DisplayMessage("", "");
13461     if (startedFromSetupPosition) {
13462         SendBoard(&second, backwardMostMove);
13463     if (appData.debugMode) {
13464         fprintf(debugFP, "Two Machines\n");
13465     }
13466     }
13467     for (i = backwardMostMove; i < forwardMostMove; i++) {
13468         SendMoveToProgram(i, &second);
13469     }
13470
13471     gameMode = TwoMachinesPlay;
13472     pausing = FALSE;
13473     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13474     SetGameInfo();
13475     DisplayTwoMachinesTitle();
13476     firstMove = TRUE;
13477     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13478         onmove = &first;
13479     } else {
13480         onmove = &second;
13481     }
13482     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13483     SendToProgram(first.computerString, &first);
13484     if (first.sendName) {
13485       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13486       SendToProgram(buf, &first);
13487     }
13488     SendToProgram(second.computerString, &second);
13489     if (second.sendName) {
13490       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13491       SendToProgram(buf, &second);
13492     }
13493
13494     ResetClocks();
13495     if (!first.sendTime || !second.sendTime) {
13496         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13497         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13498     }
13499     if (onmove->sendTime) {
13500       if (onmove->useColors) {
13501         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13502       }
13503       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13504     }
13505     if (onmove->useColors) {
13506       SendToProgram(onmove->twoMachinesColor, onmove);
13507     }
13508     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13509 //    SendToProgram("go\n", onmove);
13510     onmove->maybeThinking = TRUE;
13511     SetMachineThinkingEnables();
13512
13513     StartClocks();
13514
13515     if(bookHit) { // [HGM] book: simulate book reply
13516         static char bookMove[MSG_SIZ]; // a bit generous?
13517
13518         programStats.nodes = programStats.depth = programStats.time =
13519         programStats.score = programStats.got_only_move = 0;
13520         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13521
13522         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13523         strcat(bookMove, bookHit);
13524         savedMessage = bookMove; // args for deferred call
13525         savedState = onmove;
13526         ScheduleDelayedEvent(DeferredBookMove, 1);
13527     }
13528 }
13529
13530 void
13531 TrainingEvent ()
13532 {
13533     if (gameMode == Training) {
13534       SetTrainingModeOff();
13535       gameMode = PlayFromGameFile;
13536       DisplayMessage("", _("Training mode off"));
13537     } else {
13538       gameMode = Training;
13539       animateTraining = appData.animate;
13540
13541       /* make sure we are not already at the end of the game */
13542       if (currentMove < forwardMostMove) {
13543         SetTrainingModeOn();
13544         DisplayMessage("", _("Training mode on"));
13545       } else {
13546         gameMode = PlayFromGameFile;
13547         DisplayError(_("Already at end of game"), 0);
13548       }
13549     }
13550     ModeHighlight();
13551 }
13552
13553 void
13554 IcsClientEvent ()
13555 {
13556     if (!appData.icsActive) return;
13557     switch (gameMode) {
13558       case IcsPlayingWhite:
13559       case IcsPlayingBlack:
13560       case IcsObserving:
13561       case IcsIdle:
13562       case BeginningOfGame:
13563       case IcsExamining:
13564         return;
13565
13566       case EditGame:
13567         break;
13568
13569       case EditPosition:
13570         EditPositionDone(TRUE);
13571         break;
13572
13573       case AnalyzeMode:
13574       case AnalyzeFile:
13575         ExitAnalyzeMode();
13576         break;
13577
13578       default:
13579         EditGameEvent();
13580         break;
13581     }
13582
13583     gameMode = IcsIdle;
13584     ModeHighlight();
13585     return;
13586 }
13587
13588 void
13589 EditGameEvent ()
13590 {
13591     int i;
13592
13593     switch (gameMode) {
13594       case Training:
13595         SetTrainingModeOff();
13596         break;
13597       case MachinePlaysWhite:
13598       case MachinePlaysBlack:
13599       case BeginningOfGame:
13600         SendToProgram("force\n", &first);
13601         SetUserThinkingEnables();
13602         break;
13603       case PlayFromGameFile:
13604         (void) StopLoadGameTimer();
13605         if (gameFileFP != NULL) {
13606             gameFileFP = NULL;
13607         }
13608         break;
13609       case EditPosition:
13610         EditPositionDone(TRUE);
13611         break;
13612       case AnalyzeMode:
13613       case AnalyzeFile:
13614         ExitAnalyzeMode();
13615         SendToProgram("force\n", &first);
13616         break;
13617       case TwoMachinesPlay:
13618         GameEnds(EndOfFile, NULL, GE_PLAYER);
13619         ResurrectChessProgram();
13620         SetUserThinkingEnables();
13621         break;
13622       case EndOfGame:
13623         ResurrectChessProgram();
13624         break;
13625       case IcsPlayingBlack:
13626       case IcsPlayingWhite:
13627         DisplayError(_("Warning: You are still playing a game"), 0);
13628         break;
13629       case IcsObserving:
13630         DisplayError(_("Warning: You are still observing a game"), 0);
13631         break;
13632       case IcsExamining:
13633         DisplayError(_("Warning: You are still examining a game"), 0);
13634         break;
13635       case IcsIdle:
13636         break;
13637       case EditGame:
13638       default:
13639         return;
13640     }
13641
13642     pausing = FALSE;
13643     StopClocks();
13644     first.offeredDraw = second.offeredDraw = 0;
13645
13646     if (gameMode == PlayFromGameFile) {
13647         whiteTimeRemaining = timeRemaining[0][currentMove];
13648         blackTimeRemaining = timeRemaining[1][currentMove];
13649         DisplayTitle("");
13650     }
13651
13652     if (gameMode == MachinePlaysWhite ||
13653         gameMode == MachinePlaysBlack ||
13654         gameMode == TwoMachinesPlay ||
13655         gameMode == EndOfGame) {
13656         i = forwardMostMove;
13657         while (i > currentMove) {
13658             SendToProgram("undo\n", &first);
13659             i--;
13660         }
13661         if(!adjustedClock) {
13662         whiteTimeRemaining = timeRemaining[0][currentMove];
13663         blackTimeRemaining = timeRemaining[1][currentMove];
13664         DisplayBothClocks();
13665         }
13666         if (whiteFlag || blackFlag) {
13667             whiteFlag = blackFlag = 0;
13668         }
13669         DisplayTitle("");
13670     }
13671
13672     gameMode = EditGame;
13673     ModeHighlight();
13674     SetGameInfo();
13675 }
13676
13677
13678 void
13679 EditPositionEvent ()
13680 {
13681     if (gameMode == EditPosition) {
13682         EditGameEvent();
13683         return;
13684     }
13685
13686     EditGameEvent();
13687     if (gameMode != EditGame) return;
13688
13689     gameMode = EditPosition;
13690     ModeHighlight();
13691     SetGameInfo();
13692     if (currentMove > 0)
13693       CopyBoard(boards[0], boards[currentMove]);
13694
13695     blackPlaysFirst = !WhiteOnMove(currentMove);
13696     ResetClocks();
13697     currentMove = forwardMostMove = backwardMostMove = 0;
13698     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13699     DisplayMove(-1);
13700     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13701 }
13702
13703 void
13704 ExitAnalyzeMode ()
13705 {
13706     /* [DM] icsEngineAnalyze - possible call from other functions */
13707     if (appData.icsEngineAnalyze) {
13708         appData.icsEngineAnalyze = FALSE;
13709
13710         DisplayMessage("",_("Close ICS engine analyze..."));
13711     }
13712     if (first.analysisSupport && first.analyzing) {
13713       SendToProgram("exit\n", &first);
13714       first.analyzing = FALSE;
13715     }
13716     thinkOutput[0] = NULLCHAR;
13717 }
13718
13719 void
13720 EditPositionDone (Boolean fakeRights)
13721 {
13722     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13723
13724     startedFromSetupPosition = TRUE;
13725     InitChessProgram(&first, FALSE);
13726     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13727       boards[0][EP_STATUS] = EP_NONE;
13728       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13729     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13730         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13731         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13732       } else boards[0][CASTLING][2] = NoRights;
13733     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13734         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13735         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13736       } else boards[0][CASTLING][5] = NoRights;
13737     }
13738     SendToProgram("force\n", &first);
13739     if (blackPlaysFirst) {
13740         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13741         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13742         currentMove = forwardMostMove = backwardMostMove = 1;
13743         CopyBoard(boards[1], boards[0]);
13744     } else {
13745         currentMove = forwardMostMove = backwardMostMove = 0;
13746     }
13747     SendBoard(&first, forwardMostMove);
13748     if (appData.debugMode) {
13749         fprintf(debugFP, "EditPosDone\n");
13750     }
13751     DisplayTitle("");
13752     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13753     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13754     gameMode = EditGame;
13755     ModeHighlight();
13756     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13757     ClearHighlights(); /* [AS] */
13758 }
13759
13760 /* Pause for `ms' milliseconds */
13761 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13762 void
13763 TimeDelay (long ms)
13764 {
13765     TimeMark m1, m2;
13766
13767     GetTimeMark(&m1);
13768     do {
13769         GetTimeMark(&m2);
13770     } while (SubtractTimeMarks(&m2, &m1) < ms);
13771 }
13772
13773 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13774 void
13775 SendMultiLineToICS (char *buf)
13776 {
13777     char temp[MSG_SIZ+1], *p;
13778     int len;
13779
13780     len = strlen(buf);
13781     if (len > MSG_SIZ)
13782       len = MSG_SIZ;
13783
13784     strncpy(temp, buf, len);
13785     temp[len] = 0;
13786
13787     p = temp;
13788     while (*p) {
13789         if (*p == '\n' || *p == '\r')
13790           *p = ' ';
13791         ++p;
13792     }
13793
13794     strcat(temp, "\n");
13795     SendToICS(temp);
13796     SendToPlayer(temp, strlen(temp));
13797 }
13798
13799 void
13800 SetWhiteToPlayEvent ()
13801 {
13802     if (gameMode == EditPosition) {
13803         blackPlaysFirst = FALSE;
13804         DisplayBothClocks();    /* works because currentMove is 0 */
13805     } else if (gameMode == IcsExamining) {
13806         SendToICS(ics_prefix);
13807         SendToICS("tomove white\n");
13808     }
13809 }
13810
13811 void
13812 SetBlackToPlayEvent ()
13813 {
13814     if (gameMode == EditPosition) {
13815         blackPlaysFirst = TRUE;
13816         currentMove = 1;        /* kludge */
13817         DisplayBothClocks();
13818         currentMove = 0;
13819     } else if (gameMode == IcsExamining) {
13820         SendToICS(ics_prefix);
13821         SendToICS("tomove black\n");
13822     }
13823 }
13824
13825 void
13826 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13827 {
13828     char buf[MSG_SIZ];
13829     ChessSquare piece = boards[0][y][x];
13830
13831     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13832
13833     switch (selection) {
13834       case ClearBoard:
13835         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13836             SendToICS(ics_prefix);
13837             SendToICS("bsetup clear\n");
13838         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13839             SendToICS(ics_prefix);
13840             SendToICS("clearboard\n");
13841         } else {
13842             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13843                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13844                 for (y = 0; y < BOARD_HEIGHT; y++) {
13845                     if (gameMode == IcsExamining) {
13846                         if (boards[currentMove][y][x] != EmptySquare) {
13847                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13848                                     AAA + x, ONE + y);
13849                             SendToICS(buf);
13850                         }
13851                     } else {
13852                         boards[0][y][x] = p;
13853                     }
13854                 }
13855             }
13856         }
13857         if (gameMode == EditPosition) {
13858             DrawPosition(FALSE, boards[0]);
13859         }
13860         break;
13861
13862       case WhitePlay:
13863         SetWhiteToPlayEvent();
13864         break;
13865
13866       case BlackPlay:
13867         SetBlackToPlayEvent();
13868         break;
13869
13870       case EmptySquare:
13871         if (gameMode == IcsExamining) {
13872             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13873             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13874             SendToICS(buf);
13875         } else {
13876             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13877                 if(x == BOARD_LEFT-2) {
13878                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13879                     boards[0][y][1] = 0;
13880                 } else
13881                 if(x == BOARD_RGHT+1) {
13882                     if(y >= gameInfo.holdingsSize) break;
13883                     boards[0][y][BOARD_WIDTH-2] = 0;
13884                 } else break;
13885             }
13886             boards[0][y][x] = EmptySquare;
13887             DrawPosition(FALSE, boards[0]);
13888         }
13889         break;
13890
13891       case PromotePiece:
13892         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13893            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13894             selection = (ChessSquare) (PROMOTED piece);
13895         } else if(piece == EmptySquare) selection = WhiteSilver;
13896         else selection = (ChessSquare)((int)piece - 1);
13897         goto defaultlabel;
13898
13899       case DemotePiece:
13900         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13901            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13902             selection = (ChessSquare) (DEMOTED piece);
13903         } else if(piece == EmptySquare) selection = BlackSilver;
13904         else selection = (ChessSquare)((int)piece + 1);
13905         goto defaultlabel;
13906
13907       case WhiteQueen:
13908       case BlackQueen:
13909         if(gameInfo.variant == VariantShatranj ||
13910            gameInfo.variant == VariantXiangqi  ||
13911            gameInfo.variant == VariantCourier  ||
13912            gameInfo.variant == VariantMakruk     )
13913             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13914         goto defaultlabel;
13915
13916       case WhiteKing:
13917       case BlackKing:
13918         if(gameInfo.variant == VariantXiangqi)
13919             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13920         if(gameInfo.variant == VariantKnightmate)
13921             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13922       default:
13923         defaultlabel:
13924         if (gameMode == IcsExamining) {
13925             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13926             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13927                      PieceToChar(selection), AAA + x, ONE + y);
13928             SendToICS(buf);
13929         } else {
13930             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13931                 int n;
13932                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13933                     n = PieceToNumber(selection - BlackPawn);
13934                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13935                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13936                     boards[0][BOARD_HEIGHT-1-n][1]++;
13937                 } else
13938                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13939                     n = PieceToNumber(selection);
13940                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13941                     boards[0][n][BOARD_WIDTH-1] = selection;
13942                     boards[0][n][BOARD_WIDTH-2]++;
13943                 }
13944             } else
13945             boards[0][y][x] = selection;
13946             DrawPosition(TRUE, boards[0]);
13947             ClearHighlights();
13948             fromX = fromY = -1;
13949         }
13950         break;
13951     }
13952 }
13953
13954
13955 void
13956 DropMenuEvent (ChessSquare selection, int x, int y)
13957 {
13958     ChessMove moveType;
13959
13960     switch (gameMode) {
13961       case IcsPlayingWhite:
13962       case MachinePlaysBlack:
13963         if (!WhiteOnMove(currentMove)) {
13964             DisplayMoveError(_("It is Black's turn"));
13965             return;
13966         }
13967         moveType = WhiteDrop;
13968         break;
13969       case IcsPlayingBlack:
13970       case MachinePlaysWhite:
13971         if (WhiteOnMove(currentMove)) {
13972             DisplayMoveError(_("It is White's turn"));
13973             return;
13974         }
13975         moveType = BlackDrop;
13976         break;
13977       case EditGame:
13978         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13979         break;
13980       default:
13981         return;
13982     }
13983
13984     if (moveType == BlackDrop && selection < BlackPawn) {
13985       selection = (ChessSquare) ((int) selection
13986                                  + (int) BlackPawn - (int) WhitePawn);
13987     }
13988     if (boards[currentMove][y][x] != EmptySquare) {
13989         DisplayMoveError(_("That square is occupied"));
13990         return;
13991     }
13992
13993     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13994 }
13995
13996 void
13997 AcceptEvent ()
13998 {
13999     /* Accept a pending offer of any kind from opponent */
14000
14001     if (appData.icsActive) {
14002         SendToICS(ics_prefix);
14003         SendToICS("accept\n");
14004     } else if (cmailMsgLoaded) {
14005         if (currentMove == cmailOldMove &&
14006             commentList[cmailOldMove] != NULL &&
14007             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14008                    "Black offers a draw" : "White offers a draw")) {
14009             TruncateGame();
14010             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14012         } else {
14013             DisplayError(_("There is no pending offer on this move"), 0);
14014             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14015         }
14016     } else {
14017         /* Not used for offers from chess program */
14018     }
14019 }
14020
14021 void
14022 DeclineEvent ()
14023 {
14024     /* Decline a pending offer of any kind from opponent */
14025
14026     if (appData.icsActive) {
14027         SendToICS(ics_prefix);
14028         SendToICS("decline\n");
14029     } else if (cmailMsgLoaded) {
14030         if (currentMove == cmailOldMove &&
14031             commentList[cmailOldMove] != NULL &&
14032             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14033                    "Black offers a draw" : "White offers a draw")) {
14034 #ifdef NOTDEF
14035             AppendComment(cmailOldMove, "Draw declined", TRUE);
14036             DisplayComment(cmailOldMove - 1, "Draw declined");
14037 #endif /*NOTDEF*/
14038         } else {
14039             DisplayError(_("There is no pending offer on this move"), 0);
14040         }
14041     } else {
14042         /* Not used for offers from chess program */
14043     }
14044 }
14045
14046 void
14047 RematchEvent ()
14048 {
14049     /* Issue ICS rematch command */
14050     if (appData.icsActive) {
14051         SendToICS(ics_prefix);
14052         SendToICS("rematch\n");
14053     }
14054 }
14055
14056 void
14057 CallFlagEvent ()
14058 {
14059     /* Call your opponent's flag (claim a win on time) */
14060     if (appData.icsActive) {
14061         SendToICS(ics_prefix);
14062         SendToICS("flag\n");
14063     } else {
14064         switch (gameMode) {
14065           default:
14066             return;
14067           case MachinePlaysWhite:
14068             if (whiteFlag) {
14069                 if (blackFlag)
14070                   GameEnds(GameIsDrawn, "Both players ran out of time",
14071                            GE_PLAYER);
14072                 else
14073                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14074             } else {
14075                 DisplayError(_("Your opponent is not out of time"), 0);
14076             }
14077             break;
14078           case MachinePlaysBlack:
14079             if (blackFlag) {
14080                 if (whiteFlag)
14081                   GameEnds(GameIsDrawn, "Both players ran out of time",
14082                            GE_PLAYER);
14083                 else
14084                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14085             } else {
14086                 DisplayError(_("Your opponent is not out of time"), 0);
14087             }
14088             break;
14089         }
14090     }
14091 }
14092
14093 void
14094 ClockClick (int which)
14095 {       // [HGM] code moved to back-end from winboard.c
14096         if(which) { // black clock
14097           if (gameMode == EditPosition || gameMode == IcsExamining) {
14098             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14099             SetBlackToPlayEvent();
14100           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14101           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14102           } else if (shiftKey) {
14103             AdjustClock(which, -1);
14104           } else if (gameMode == IcsPlayingWhite ||
14105                      gameMode == MachinePlaysBlack) {
14106             CallFlagEvent();
14107           }
14108         } else { // white clock
14109           if (gameMode == EditPosition || gameMode == IcsExamining) {
14110             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14111             SetWhiteToPlayEvent();
14112           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14113           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14114           } else if (shiftKey) {
14115             AdjustClock(which, -1);
14116           } else if (gameMode == IcsPlayingBlack ||
14117                    gameMode == MachinePlaysWhite) {
14118             CallFlagEvent();
14119           }
14120         }
14121 }
14122
14123 void
14124 DrawEvent ()
14125 {
14126     /* Offer draw or accept pending draw offer from opponent */
14127
14128     if (appData.icsActive) {
14129         /* Note: tournament rules require draw offers to be
14130            made after you make your move but before you punch
14131            your clock.  Currently ICS doesn't let you do that;
14132            instead, you immediately punch your clock after making
14133            a move, but you can offer a draw at any time. */
14134
14135         SendToICS(ics_prefix);
14136         SendToICS("draw\n");
14137         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14138     } else if (cmailMsgLoaded) {
14139         if (currentMove == cmailOldMove &&
14140             commentList[cmailOldMove] != NULL &&
14141             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14142                    "Black offers a draw" : "White offers a draw")) {
14143             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14144             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14145         } else if (currentMove == cmailOldMove + 1) {
14146             char *offer = WhiteOnMove(cmailOldMove) ?
14147               "White offers a draw" : "Black offers a draw";
14148             AppendComment(currentMove, offer, TRUE);
14149             DisplayComment(currentMove - 1, offer);
14150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14151         } else {
14152             DisplayError(_("You must make your move before offering a draw"), 0);
14153             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14154         }
14155     } else if (first.offeredDraw) {
14156         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14157     } else {
14158         if (first.sendDrawOffers) {
14159             SendToProgram("draw\n", &first);
14160             userOfferedDraw = TRUE;
14161         }
14162     }
14163 }
14164
14165 void
14166 AdjournEvent ()
14167 {
14168     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14169
14170     if (appData.icsActive) {
14171         SendToICS(ics_prefix);
14172         SendToICS("adjourn\n");
14173     } else {
14174         /* Currently GNU Chess doesn't offer or accept Adjourns */
14175     }
14176 }
14177
14178
14179 void
14180 AbortEvent ()
14181 {
14182     /* Offer Abort or accept pending Abort offer from opponent */
14183
14184     if (appData.icsActive) {
14185         SendToICS(ics_prefix);
14186         SendToICS("abort\n");
14187     } else {
14188         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14189     }
14190 }
14191
14192 void
14193 ResignEvent ()
14194 {
14195     /* Resign.  You can do this even if it's not your turn. */
14196
14197     if (appData.icsActive) {
14198         SendToICS(ics_prefix);
14199         SendToICS("resign\n");
14200     } else {
14201         switch (gameMode) {
14202           case MachinePlaysWhite:
14203             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14204             break;
14205           case MachinePlaysBlack:
14206             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14207             break;
14208           case EditGame:
14209             if (cmailMsgLoaded) {
14210                 TruncateGame();
14211                 if (WhiteOnMove(cmailOldMove)) {
14212                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14213                 } else {
14214                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14215                 }
14216                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14217             }
14218             break;
14219           default:
14220             break;
14221         }
14222     }
14223 }
14224
14225
14226 void
14227 StopObservingEvent ()
14228 {
14229     /* Stop observing current games */
14230     SendToICS(ics_prefix);
14231     SendToICS("unobserve\n");
14232 }
14233
14234 void
14235 StopExaminingEvent ()
14236 {
14237     /* Stop observing current game */
14238     SendToICS(ics_prefix);
14239     SendToICS("unexamine\n");
14240 }
14241
14242 void
14243 ForwardInner (int target)
14244 {
14245     int limit; int oldSeekGraphUp = seekGraphUp;
14246
14247     if (appData.debugMode)
14248         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14249                 target, currentMove, forwardMostMove);
14250
14251     if (gameMode == EditPosition)
14252       return;
14253
14254     seekGraphUp = FALSE;
14255     MarkTargetSquares(1);
14256
14257     if (gameMode == PlayFromGameFile && !pausing)
14258       PauseEvent();
14259
14260     if (gameMode == IcsExamining && pausing)
14261       limit = pauseExamForwardMostMove;
14262     else
14263       limit = forwardMostMove;
14264
14265     if (target > limit) target = limit;
14266
14267     if (target > 0 && moveList[target - 1][0]) {
14268         int fromX, fromY, toX, toY;
14269         toX = moveList[target - 1][2] - AAA;
14270         toY = moveList[target - 1][3] - ONE;
14271         if (moveList[target - 1][1] == '@') {
14272             if (appData.highlightLastMove) {
14273                 SetHighlights(-1, -1, toX, toY);
14274             }
14275         } else {
14276             fromX = moveList[target - 1][0] - AAA;
14277             fromY = moveList[target - 1][1] - ONE;
14278             if (target == currentMove + 1) {
14279                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14280             }
14281             if (appData.highlightLastMove) {
14282                 SetHighlights(fromX, fromY, toX, toY);
14283             }
14284         }
14285     }
14286     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14287         gameMode == Training || gameMode == PlayFromGameFile ||
14288         gameMode == AnalyzeFile) {
14289         while (currentMove < target) {
14290             SendMoveToProgram(currentMove++, &first);
14291         }
14292     } else {
14293         currentMove = target;
14294     }
14295
14296     if (gameMode == EditGame || gameMode == EndOfGame) {
14297         whiteTimeRemaining = timeRemaining[0][currentMove];
14298         blackTimeRemaining = timeRemaining[1][currentMove];
14299     }
14300     DisplayBothClocks();
14301     DisplayMove(currentMove - 1);
14302     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14303     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14304     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14305         DisplayComment(currentMove - 1, commentList[currentMove]);
14306     }
14307 }
14308
14309
14310 void
14311 ForwardEvent ()
14312 {
14313     if (gameMode == IcsExamining && !pausing) {
14314         SendToICS(ics_prefix);
14315         SendToICS("forward\n");
14316     } else {
14317         ForwardInner(currentMove + 1);
14318     }
14319 }
14320
14321 void
14322 ToEndEvent ()
14323 {
14324     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14325         /* to optimze, we temporarily turn off analysis mode while we feed
14326          * the remaining moves to the engine. Otherwise we get analysis output
14327          * after each move.
14328          */
14329         if (first.analysisSupport) {
14330           SendToProgram("exit\nforce\n", &first);
14331           first.analyzing = FALSE;
14332         }
14333     }
14334
14335     if (gameMode == IcsExamining && !pausing) {
14336         SendToICS(ics_prefix);
14337         SendToICS("forward 999999\n");
14338     } else {
14339         ForwardInner(forwardMostMove);
14340     }
14341
14342     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14343         /* we have fed all the moves, so reactivate analysis mode */
14344         SendToProgram("analyze\n", &first);
14345         first.analyzing = TRUE;
14346         /*first.maybeThinking = TRUE;*/
14347         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14348     }
14349 }
14350
14351 void
14352 BackwardInner (int target)
14353 {
14354     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14355
14356     if (appData.debugMode)
14357         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14358                 target, currentMove, forwardMostMove);
14359
14360     if (gameMode == EditPosition) return;
14361     seekGraphUp = FALSE;
14362     MarkTargetSquares(1);
14363     if (currentMove <= backwardMostMove) {
14364         ClearHighlights();
14365         DrawPosition(full_redraw, boards[currentMove]);
14366         return;
14367     }
14368     if (gameMode == PlayFromGameFile && !pausing)
14369       PauseEvent();
14370
14371     if (moveList[target][0]) {
14372         int fromX, fromY, toX, toY;
14373         toX = moveList[target][2] - AAA;
14374         toY = moveList[target][3] - ONE;
14375         if (moveList[target][1] == '@') {
14376             if (appData.highlightLastMove) {
14377                 SetHighlights(-1, -1, toX, toY);
14378             }
14379         } else {
14380             fromX = moveList[target][0] - AAA;
14381             fromY = moveList[target][1] - ONE;
14382             if (target == currentMove - 1) {
14383                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14384             }
14385             if (appData.highlightLastMove) {
14386                 SetHighlights(fromX, fromY, toX, toY);
14387             }
14388         }
14389     }
14390     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14391         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14392         while (currentMove > target) {
14393             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14394                 // null move cannot be undone. Reload program with move history before it.
14395                 int i;
14396                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14397                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14398                 }
14399                 SendBoard(&first, i); 
14400                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14401                 break;
14402             }
14403             SendToProgram("undo\n", &first);
14404             currentMove--;
14405         }
14406     } else {
14407         currentMove = target;
14408     }
14409
14410     if (gameMode == EditGame || gameMode == EndOfGame) {
14411         whiteTimeRemaining = timeRemaining[0][currentMove];
14412         blackTimeRemaining = timeRemaining[1][currentMove];
14413     }
14414     DisplayBothClocks();
14415     DisplayMove(currentMove - 1);
14416     DrawPosition(full_redraw, boards[currentMove]);
14417     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14418     // [HGM] PV info: routine tests if comment empty
14419     DisplayComment(currentMove - 1, commentList[currentMove]);
14420 }
14421
14422 void
14423 BackwardEvent ()
14424 {
14425     if (gameMode == IcsExamining && !pausing) {
14426         SendToICS(ics_prefix);
14427         SendToICS("backward\n");
14428     } else {
14429         BackwardInner(currentMove - 1);
14430     }
14431 }
14432
14433 void
14434 ToStartEvent ()
14435 {
14436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14437         /* to optimize, we temporarily turn off analysis mode while we undo
14438          * all the moves. Otherwise we get analysis output after each undo.
14439          */
14440         if (first.analysisSupport) {
14441           SendToProgram("exit\nforce\n", &first);
14442           first.analyzing = FALSE;
14443         }
14444     }
14445
14446     if (gameMode == IcsExamining && !pausing) {
14447         SendToICS(ics_prefix);
14448         SendToICS("backward 999999\n");
14449     } else {
14450         BackwardInner(backwardMostMove);
14451     }
14452
14453     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14454         /* we have fed all the moves, so reactivate analysis mode */
14455         SendToProgram("analyze\n", &first);
14456         first.analyzing = TRUE;
14457         /*first.maybeThinking = TRUE;*/
14458         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14459     }
14460 }
14461
14462 void
14463 ToNrEvent (int to)
14464 {
14465   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14466   if (to >= forwardMostMove) to = forwardMostMove;
14467   if (to <= backwardMostMove) to = backwardMostMove;
14468   if (to < currentMove) {
14469     BackwardInner(to);
14470   } else {
14471     ForwardInner(to);
14472   }
14473 }
14474
14475 void
14476 RevertEvent (Boolean annotate)
14477 {
14478     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14479         return;
14480     }
14481     if (gameMode != IcsExamining) {
14482         DisplayError(_("You are not examining a game"), 0);
14483         return;
14484     }
14485     if (pausing) {
14486         DisplayError(_("You can't revert while pausing"), 0);
14487         return;
14488     }
14489     SendToICS(ics_prefix);
14490     SendToICS("revert\n");
14491 }
14492
14493 void
14494 RetractMoveEvent ()
14495 {
14496     switch (gameMode) {
14497       case MachinePlaysWhite:
14498       case MachinePlaysBlack:
14499         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14500             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14501             return;
14502         }
14503         if (forwardMostMove < 2) return;
14504         currentMove = forwardMostMove = forwardMostMove - 2;
14505         whiteTimeRemaining = timeRemaining[0][currentMove];
14506         blackTimeRemaining = timeRemaining[1][currentMove];
14507         DisplayBothClocks();
14508         DisplayMove(currentMove - 1);
14509         ClearHighlights();/*!! could figure this out*/
14510         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14511         SendToProgram("remove\n", &first);
14512         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14513         break;
14514
14515       case BeginningOfGame:
14516       default:
14517         break;
14518
14519       case IcsPlayingWhite:
14520       case IcsPlayingBlack:
14521         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14522             SendToICS(ics_prefix);
14523             SendToICS("takeback 2\n");
14524         } else {
14525             SendToICS(ics_prefix);
14526             SendToICS("takeback 1\n");
14527         }
14528         break;
14529     }
14530 }
14531
14532 void
14533 MoveNowEvent ()
14534 {
14535     ChessProgramState *cps;
14536
14537     switch (gameMode) {
14538       case MachinePlaysWhite:
14539         if (!WhiteOnMove(forwardMostMove)) {
14540             DisplayError(_("It is your turn"), 0);
14541             return;
14542         }
14543         cps = &first;
14544         break;
14545       case MachinePlaysBlack:
14546         if (WhiteOnMove(forwardMostMove)) {
14547             DisplayError(_("It is your turn"), 0);
14548             return;
14549         }
14550         cps = &first;
14551         break;
14552       case TwoMachinesPlay:
14553         if (WhiteOnMove(forwardMostMove) ==
14554             (first.twoMachinesColor[0] == 'w')) {
14555             cps = &first;
14556         } else {
14557             cps = &second;
14558         }
14559         break;
14560       case BeginningOfGame:
14561       default:
14562         return;
14563     }
14564     SendToProgram("?\n", cps);
14565 }
14566
14567 void
14568 TruncateGameEvent ()
14569 {
14570     EditGameEvent();
14571     if (gameMode != EditGame) return;
14572     TruncateGame();
14573 }
14574
14575 void
14576 TruncateGame ()
14577 {
14578     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14579     if (forwardMostMove > currentMove) {
14580         if (gameInfo.resultDetails != NULL) {
14581             free(gameInfo.resultDetails);
14582             gameInfo.resultDetails = NULL;
14583             gameInfo.result = GameUnfinished;
14584         }
14585         forwardMostMove = currentMove;
14586         HistorySet(parseList, backwardMostMove, forwardMostMove,
14587                    currentMove-1);
14588     }
14589 }
14590
14591 void
14592 HintEvent ()
14593 {
14594     if (appData.noChessProgram) return;
14595     switch (gameMode) {
14596       case MachinePlaysWhite:
14597         if (WhiteOnMove(forwardMostMove)) {
14598             DisplayError(_("Wait until your turn"), 0);
14599             return;
14600         }
14601         break;
14602       case BeginningOfGame:
14603       case MachinePlaysBlack:
14604         if (!WhiteOnMove(forwardMostMove)) {
14605             DisplayError(_("Wait until your turn"), 0);
14606             return;
14607         }
14608         break;
14609       default:
14610         DisplayError(_("No hint available"), 0);
14611         return;
14612     }
14613     SendToProgram("hint\n", &first);
14614     hintRequested = TRUE;
14615 }
14616
14617 void
14618 BookEvent ()
14619 {
14620     if (appData.noChessProgram) return;
14621     switch (gameMode) {
14622       case MachinePlaysWhite:
14623         if (WhiteOnMove(forwardMostMove)) {
14624             DisplayError(_("Wait until your turn"), 0);
14625             return;
14626         }
14627         break;
14628       case BeginningOfGame:
14629       case MachinePlaysBlack:
14630         if (!WhiteOnMove(forwardMostMove)) {
14631             DisplayError(_("Wait until your turn"), 0);
14632             return;
14633         }
14634         break;
14635       case EditPosition:
14636         EditPositionDone(TRUE);
14637         break;
14638       case TwoMachinesPlay:
14639         return;
14640       default:
14641         break;
14642     }
14643     SendToProgram("bk\n", &first);
14644     bookOutput[0] = NULLCHAR;
14645     bookRequested = TRUE;
14646 }
14647
14648 void
14649 AboutGameEvent ()
14650 {
14651     char *tags = PGNTags(&gameInfo);
14652     TagsPopUp(tags, CmailMsg());
14653     free(tags);
14654 }
14655
14656 /* end button procedures */
14657
14658 void
14659 PrintPosition (FILE *fp, int move)
14660 {
14661     int i, j;
14662
14663     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14664         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14665             char c = PieceToChar(boards[move][i][j]);
14666             fputc(c == 'x' ? '.' : c, fp);
14667             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14668         }
14669     }
14670     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14671       fprintf(fp, "white to play\n");
14672     else
14673       fprintf(fp, "black to play\n");
14674 }
14675
14676 void
14677 PrintOpponents (FILE *fp)
14678 {
14679     if (gameInfo.white != NULL) {
14680         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14681     } else {
14682         fprintf(fp, "\n");
14683     }
14684 }
14685
14686 /* Find last component of program's own name, using some heuristics */
14687 void
14688 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14689 {
14690     char *p, *q, c;
14691     int local = (strcmp(host, "localhost") == 0);
14692     while (!local && (p = strchr(prog, ';')) != NULL) {
14693         p++;
14694         while (*p == ' ') p++;
14695         prog = p;
14696     }
14697     if (*prog == '"' || *prog == '\'') {
14698         q = strchr(prog + 1, *prog);
14699     } else {
14700         q = strchr(prog, ' ');
14701     }
14702     if (q == NULL) q = prog + strlen(prog);
14703     p = q;
14704     while (p >= prog && *p != '/' && *p != '\\') p--;
14705     p++;
14706     if(p == prog && *p == '"') p++;
14707     c = *q; *q = 0;
14708     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14709     memcpy(buf, p, q - p);
14710     buf[q - p] = NULLCHAR;
14711     if (!local) {
14712         strcat(buf, "@");
14713         strcat(buf, host);
14714     }
14715 }
14716
14717 char *
14718 TimeControlTagValue ()
14719 {
14720     char buf[MSG_SIZ];
14721     if (!appData.clockMode) {
14722       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14723     } else if (movesPerSession > 0) {
14724       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14725     } else if (timeIncrement == 0) {
14726       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14727     } else {
14728       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14729     }
14730     return StrSave(buf);
14731 }
14732
14733 void
14734 SetGameInfo ()
14735 {
14736     /* This routine is used only for certain modes */
14737     VariantClass v = gameInfo.variant;
14738     ChessMove r = GameUnfinished;
14739     char *p = NULL;
14740
14741     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14742         r = gameInfo.result;
14743         p = gameInfo.resultDetails;
14744         gameInfo.resultDetails = NULL;
14745     }
14746     ClearGameInfo(&gameInfo);
14747     gameInfo.variant = v;
14748
14749     switch (gameMode) {
14750       case MachinePlaysWhite:
14751         gameInfo.event = StrSave( appData.pgnEventHeader );
14752         gameInfo.site = StrSave(HostName());
14753         gameInfo.date = PGNDate();
14754         gameInfo.round = StrSave("-");
14755         gameInfo.white = StrSave(first.tidy);
14756         gameInfo.black = StrSave(UserName());
14757         gameInfo.timeControl = TimeControlTagValue();
14758         break;
14759
14760       case MachinePlaysBlack:
14761         gameInfo.event = StrSave( appData.pgnEventHeader );
14762         gameInfo.site = StrSave(HostName());
14763         gameInfo.date = PGNDate();
14764         gameInfo.round = StrSave("-");
14765         gameInfo.white = StrSave(UserName());
14766         gameInfo.black = StrSave(first.tidy);
14767         gameInfo.timeControl = TimeControlTagValue();
14768         break;
14769
14770       case TwoMachinesPlay:
14771         gameInfo.event = StrSave( appData.pgnEventHeader );
14772         gameInfo.site = StrSave(HostName());
14773         gameInfo.date = PGNDate();
14774         if (roundNr > 0) {
14775             char buf[MSG_SIZ];
14776             snprintf(buf, MSG_SIZ, "%d", roundNr);
14777             gameInfo.round = StrSave(buf);
14778         } else {
14779             gameInfo.round = StrSave("-");
14780         }
14781         if (first.twoMachinesColor[0] == 'w') {
14782             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14783             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14784         } else {
14785             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14786             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14787         }
14788         gameInfo.timeControl = TimeControlTagValue();
14789         break;
14790
14791       case EditGame:
14792         gameInfo.event = StrSave("Edited game");
14793         gameInfo.site = StrSave(HostName());
14794         gameInfo.date = PGNDate();
14795         gameInfo.round = StrSave("-");
14796         gameInfo.white = StrSave("-");
14797         gameInfo.black = StrSave("-");
14798         gameInfo.result = r;
14799         gameInfo.resultDetails = p;
14800         break;
14801
14802       case EditPosition:
14803         gameInfo.event = StrSave("Edited position");
14804         gameInfo.site = StrSave(HostName());
14805         gameInfo.date = PGNDate();
14806         gameInfo.round = StrSave("-");
14807         gameInfo.white = StrSave("-");
14808         gameInfo.black = StrSave("-");
14809         break;
14810
14811       case IcsPlayingWhite:
14812       case IcsPlayingBlack:
14813       case IcsObserving:
14814       case IcsExamining:
14815         break;
14816
14817       case PlayFromGameFile:
14818         gameInfo.event = StrSave("Game from non-PGN file");
14819         gameInfo.site = StrSave(HostName());
14820         gameInfo.date = PGNDate();
14821         gameInfo.round = StrSave("-");
14822         gameInfo.white = StrSave("?");
14823         gameInfo.black = StrSave("?");
14824         break;
14825
14826       default:
14827         break;
14828     }
14829 }
14830
14831 void
14832 ReplaceComment (int index, char *text)
14833 {
14834     int len;
14835     char *p;
14836     float score;
14837
14838     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14839        pvInfoList[index-1].depth == len &&
14840        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14841        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14842     while (*text == '\n') text++;
14843     len = strlen(text);
14844     while (len > 0 && text[len - 1] == '\n') len--;
14845
14846     if (commentList[index] != NULL)
14847       free(commentList[index]);
14848
14849     if (len == 0) {
14850         commentList[index] = NULL;
14851         return;
14852     }
14853   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14854       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14855       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14856     commentList[index] = (char *) malloc(len + 2);
14857     strncpy(commentList[index], text, len);
14858     commentList[index][len] = '\n';
14859     commentList[index][len + 1] = NULLCHAR;
14860   } else {
14861     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14862     char *p;
14863     commentList[index] = (char *) malloc(len + 7);
14864     safeStrCpy(commentList[index], "{\n", 3);
14865     safeStrCpy(commentList[index]+2, text, len+1);
14866     commentList[index][len+2] = NULLCHAR;
14867     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14868     strcat(commentList[index], "\n}\n");
14869   }
14870 }
14871
14872 void
14873 CrushCRs (char *text)
14874 {
14875   char *p = text;
14876   char *q = text;
14877   char ch;
14878
14879   do {
14880     ch = *p++;
14881     if (ch == '\r') continue;
14882     *q++ = ch;
14883   } while (ch != '\0');
14884 }
14885
14886 void
14887 AppendComment (int index, char *text, Boolean addBraces)
14888 /* addBraces  tells if we should add {} */
14889 {
14890     int oldlen, len;
14891     char *old;
14892
14893 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14894     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14895
14896     CrushCRs(text);
14897     while (*text == '\n') text++;
14898     len = strlen(text);
14899     while (len > 0 && text[len - 1] == '\n') len--;
14900     text[len] = NULLCHAR;
14901
14902     if (len == 0) return;
14903
14904     if (commentList[index] != NULL) {
14905       Boolean addClosingBrace = addBraces;
14906         old = commentList[index];
14907         oldlen = strlen(old);
14908         while(commentList[index][oldlen-1] ==  '\n')
14909           commentList[index][--oldlen] = NULLCHAR;
14910         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14911         safeStrCpy(commentList[index], old, oldlen + len + 6);
14912         free(old);
14913         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14914         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14915           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14916           while (*text == '\n') { text++; len--; }
14917           commentList[index][--oldlen] = NULLCHAR;
14918       }
14919         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14920         else          strcat(commentList[index], "\n");
14921         strcat(commentList[index], text);
14922         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14923         else          strcat(commentList[index], "\n");
14924     } else {
14925         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14926         if(addBraces)
14927           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14928         else commentList[index][0] = NULLCHAR;
14929         strcat(commentList[index], text);
14930         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14931         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14932     }
14933 }
14934
14935 static char *
14936 FindStr (char * text, char * sub_text)
14937 {
14938     char * result = strstr( text, sub_text );
14939
14940     if( result != NULL ) {
14941         result += strlen( sub_text );
14942     }
14943
14944     return result;
14945 }
14946
14947 /* [AS] Try to extract PV info from PGN comment */
14948 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14949 char *
14950 GetInfoFromComment (int index, char * text)
14951 {
14952     char * sep = text, *p;
14953
14954     if( text != NULL && index > 0 ) {
14955         int score = 0;
14956         int depth = 0;
14957         int time = -1, sec = 0, deci;
14958         char * s_eval = FindStr( text, "[%eval " );
14959         char * s_emt = FindStr( text, "[%emt " );
14960
14961         if( s_eval != NULL || s_emt != NULL ) {
14962             /* New style */
14963             char delim;
14964
14965             if( s_eval != NULL ) {
14966                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14967                     return text;
14968                 }
14969
14970                 if( delim != ']' ) {
14971                     return text;
14972                 }
14973             }
14974
14975             if( s_emt != NULL ) {
14976             }
14977                 return text;
14978         }
14979         else {
14980             /* We expect something like: [+|-]nnn.nn/dd */
14981             int score_lo = 0;
14982
14983             if(*text != '{') return text; // [HGM] braces: must be normal comment
14984
14985             sep = strchr( text, '/' );
14986             if( sep == NULL || sep < (text+4) ) {
14987                 return text;
14988             }
14989
14990             p = text;
14991             if(p[1] == '(') { // comment starts with PV
14992                p = strchr(p, ')'); // locate end of PV
14993                if(p == NULL || sep < p+5) return text;
14994                // at this point we have something like "{(.*) +0.23/6 ..."
14995                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14996                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14997                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14998             }
14999             time = -1; sec = -1; deci = -1;
15000             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15001                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15002                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15003                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15004                 return text;
15005             }
15006
15007             if( score_lo < 0 || score_lo >= 100 ) {
15008                 return text;
15009             }
15010
15011             if(sec >= 0) time = 600*time + 10*sec; else
15012             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15013
15014             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15015
15016             /* [HGM] PV time: now locate end of PV info */
15017             while( *++sep >= '0' && *sep <= '9'); // strip depth
15018             if(time >= 0)
15019             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15020             if(sec >= 0)
15021             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15022             if(deci >= 0)
15023             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15024             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15025         }
15026
15027         if( depth <= 0 ) {
15028             return text;
15029         }
15030
15031         if( time < 0 ) {
15032             time = -1;
15033         }
15034
15035         pvInfoList[index-1].depth = depth;
15036         pvInfoList[index-1].score = score;
15037         pvInfoList[index-1].time  = 10*time; // centi-sec
15038         if(*sep == '}') *sep = 0; else *--sep = '{';
15039         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15040     }
15041     return sep;
15042 }
15043
15044 void
15045 SendToProgram (char *message, ChessProgramState *cps)
15046 {
15047     int count, outCount, error;
15048     char buf[MSG_SIZ];
15049
15050     if (cps->pr == NoProc) return;
15051     Attention(cps);
15052
15053     if (appData.debugMode) {
15054         TimeMark now;
15055         GetTimeMark(&now);
15056         fprintf(debugFP, "%ld >%-6s: %s",
15057                 SubtractTimeMarks(&now, &programStartTime),
15058                 cps->which, message);
15059         if(serverFP)
15060             fprintf(serverFP, "%ld >%-6s: %s",
15061                 SubtractTimeMarks(&now, &programStartTime),
15062                 cps->which, message), fflush(serverFP);
15063     }
15064
15065     count = strlen(message);
15066     outCount = OutputToProcess(cps->pr, message, count, &error);
15067     if (outCount < count && !exiting
15068                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15069       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15070       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15071         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15072             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15073                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15074                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15075                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15076             } else {
15077                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15078                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15079                 gameInfo.result = res;
15080             }
15081             gameInfo.resultDetails = StrSave(buf);
15082         }
15083         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15084         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15085     }
15086 }
15087
15088 void
15089 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15090 {
15091     char *end_str;
15092     char buf[MSG_SIZ];
15093     ChessProgramState *cps = (ChessProgramState *)closure;
15094
15095     if (isr != cps->isr) return; /* Killed intentionally */
15096     if (count <= 0) {
15097         if (count == 0) {
15098             RemoveInputSource(cps->isr);
15099             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15100             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15101                     _(cps->which), cps->program);
15102         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15103                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15104                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15105                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15106                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15107                 } else {
15108                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15109                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15110                     gameInfo.result = res;
15111                 }
15112                 gameInfo.resultDetails = StrSave(buf);
15113             }
15114             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15115             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15116         } else {
15117             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15118                     _(cps->which), cps->program);
15119             RemoveInputSource(cps->isr);
15120
15121             /* [AS] Program is misbehaving badly... kill it */
15122             if( count == -2 ) {
15123                 DestroyChildProcess( cps->pr, 9 );
15124                 cps->pr = NoProc;
15125             }
15126
15127             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15128         }
15129         return;
15130     }
15131
15132     if ((end_str = strchr(message, '\r')) != NULL)
15133       *end_str = NULLCHAR;
15134     if ((end_str = strchr(message, '\n')) != NULL)
15135       *end_str = NULLCHAR;
15136
15137     if (appData.debugMode) {
15138         TimeMark now; int print = 1;
15139         char *quote = ""; char c; int i;
15140
15141         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15142                 char start = message[0];
15143                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15144                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15145                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15146                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15147                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15148                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15149                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15150                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15151                    sscanf(message, "hint: %c", &c)!=1 && 
15152                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15153                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15154                     print = (appData.engineComments >= 2);
15155                 }
15156                 message[0] = start; // restore original message
15157         }
15158         if(print) {
15159                 GetTimeMark(&now);
15160                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15161                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15162                         quote,
15163                         message);
15164                 if(serverFP)
15165                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15166                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15167                         quote,
15168                         message), fflush(serverFP);
15169         }
15170     }
15171
15172     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15173     if (appData.icsEngineAnalyze) {
15174         if (strstr(message, "whisper") != NULL ||
15175              strstr(message, "kibitz") != NULL ||
15176             strstr(message, "tellics") != NULL) return;
15177     }
15178
15179     HandleMachineMove(message, cps);
15180 }
15181
15182
15183 void
15184 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15185 {
15186     char buf[MSG_SIZ];
15187     int seconds;
15188
15189     if( timeControl_2 > 0 ) {
15190         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15191             tc = timeControl_2;
15192         }
15193     }
15194     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15195     inc /= cps->timeOdds;
15196     st  /= cps->timeOdds;
15197
15198     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15199
15200     if (st > 0) {
15201       /* Set exact time per move, normally using st command */
15202       if (cps->stKludge) {
15203         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15204         seconds = st % 60;
15205         if (seconds == 0) {
15206           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15207         } else {
15208           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15209         }
15210       } else {
15211         snprintf(buf, MSG_SIZ, "st %d\n", st);
15212       }
15213     } else {
15214       /* Set conventional or incremental time control, using level command */
15215       if (seconds == 0) {
15216         /* Note old gnuchess bug -- minutes:seconds used to not work.
15217            Fixed in later versions, but still avoid :seconds
15218            when seconds is 0. */
15219         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15220       } else {
15221         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15222                  seconds, inc/1000.);
15223       }
15224     }
15225     SendToProgram(buf, cps);
15226
15227     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15228     /* Orthogonally, limit search to given depth */
15229     if (sd > 0) {
15230       if (cps->sdKludge) {
15231         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15232       } else {
15233         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15234       }
15235       SendToProgram(buf, cps);
15236     }
15237
15238     if(cps->nps >= 0) { /* [HGM] nps */
15239         if(cps->supportsNPS == FALSE)
15240           cps->nps = -1; // don't use if engine explicitly says not supported!
15241         else {
15242           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15243           SendToProgram(buf, cps);
15244         }
15245     }
15246 }
15247
15248 ChessProgramState *
15249 WhitePlayer ()
15250 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15251 {
15252     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15253        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15254         return &second;
15255     return &first;
15256 }
15257
15258 void
15259 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15260 {
15261     char message[MSG_SIZ];
15262     long time, otime;
15263
15264     /* Note: this routine must be called when the clocks are stopped
15265        or when they have *just* been set or switched; otherwise
15266        it will be off by the time since the current tick started.
15267     */
15268     if (machineWhite) {
15269         time = whiteTimeRemaining / 10;
15270         otime = blackTimeRemaining / 10;
15271     } else {
15272         time = blackTimeRemaining / 10;
15273         otime = whiteTimeRemaining / 10;
15274     }
15275     /* [HGM] translate opponent's time by time-odds factor */
15276     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15277
15278     if (time <= 0) time = 1;
15279     if (otime <= 0) otime = 1;
15280
15281     snprintf(message, MSG_SIZ, "time %ld\n", time);
15282     SendToProgram(message, cps);
15283
15284     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15285     SendToProgram(message, cps);
15286 }
15287
15288 int
15289 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15290 {
15291   char buf[MSG_SIZ];
15292   int len = strlen(name);
15293   int val;
15294
15295   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15296     (*p) += len + 1;
15297     sscanf(*p, "%d", &val);
15298     *loc = (val != 0);
15299     while (**p && **p != ' ')
15300       (*p)++;
15301     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15302     SendToProgram(buf, cps);
15303     return TRUE;
15304   }
15305   return FALSE;
15306 }
15307
15308 int
15309 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15310 {
15311   char buf[MSG_SIZ];
15312   int len = strlen(name);
15313   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15314     (*p) += len + 1;
15315     sscanf(*p, "%d", loc);
15316     while (**p && **p != ' ') (*p)++;
15317     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15318     SendToProgram(buf, cps);
15319     return TRUE;
15320   }
15321   return FALSE;
15322 }
15323
15324 int
15325 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15326 {
15327   char buf[MSG_SIZ];
15328   int len = strlen(name);
15329   if (strncmp((*p), name, len) == 0
15330       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15331     (*p) += len + 2;
15332     sscanf(*p, "%[^\"]", loc);
15333     while (**p && **p != '\"') (*p)++;
15334     if (**p == '\"') (*p)++;
15335     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15336     SendToProgram(buf, cps);
15337     return TRUE;
15338   }
15339   return FALSE;
15340 }
15341
15342 int
15343 ParseOption (Option *opt, ChessProgramState *cps)
15344 // [HGM] options: process the string that defines an engine option, and determine
15345 // name, type, default value, and allowed value range
15346 {
15347         char *p, *q, buf[MSG_SIZ];
15348         int n, min = (-1)<<31, max = 1<<31, def;
15349
15350         if(p = strstr(opt->name, " -spin ")) {
15351             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15352             if(max < min) max = min; // enforce consistency
15353             if(def < min) def = min;
15354             if(def > max) def = max;
15355             opt->value = def;
15356             opt->min = min;
15357             opt->max = max;
15358             opt->type = Spin;
15359         } else if((p = strstr(opt->name, " -slider "))) {
15360             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15361             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15362             if(max < min) max = min; // enforce consistency
15363             if(def < min) def = min;
15364             if(def > max) def = max;
15365             opt->value = def;
15366             opt->min = min;
15367             opt->max = max;
15368             opt->type = Spin; // Slider;
15369         } else if((p = strstr(opt->name, " -string "))) {
15370             opt->textValue = p+9;
15371             opt->type = TextBox;
15372         } else if((p = strstr(opt->name, " -file "))) {
15373             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15374             opt->textValue = p+7;
15375             opt->type = FileName; // FileName;
15376         } else if((p = strstr(opt->name, " -path "))) {
15377             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15378             opt->textValue = p+7;
15379             opt->type = PathName; // PathName;
15380         } else if(p = strstr(opt->name, " -check ")) {
15381             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15382             opt->value = (def != 0);
15383             opt->type = CheckBox;
15384         } else if(p = strstr(opt->name, " -combo ")) {
15385             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15386             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15387             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15388             opt->value = n = 0;
15389             while(q = StrStr(q, " /// ")) {
15390                 n++; *q = 0;    // count choices, and null-terminate each of them
15391                 q += 5;
15392                 if(*q == '*') { // remember default, which is marked with * prefix
15393                     q++;
15394                     opt->value = n;
15395                 }
15396                 cps->comboList[cps->comboCnt++] = q;
15397             }
15398             cps->comboList[cps->comboCnt++] = NULL;
15399             opt->max = n + 1;
15400             opt->type = ComboBox;
15401         } else if(p = strstr(opt->name, " -button")) {
15402             opt->type = Button;
15403         } else if(p = strstr(opt->name, " -save")) {
15404             opt->type = SaveButton;
15405         } else return FALSE;
15406         *p = 0; // terminate option name
15407         // now look if the command-line options define a setting for this engine option.
15408         if(cps->optionSettings && cps->optionSettings[0])
15409             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15410         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15411           snprintf(buf, MSG_SIZ, "option %s", p);
15412                 if(p = strstr(buf, ",")) *p = 0;
15413                 if(q = strchr(buf, '=')) switch(opt->type) {
15414                     case ComboBox:
15415                         for(n=0; n<opt->max; n++)
15416                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15417                         break;
15418                     case TextBox:
15419                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15420                         break;
15421                     case Spin:
15422                     case CheckBox:
15423                         opt->value = atoi(q+1);
15424                     default:
15425                         break;
15426                 }
15427                 strcat(buf, "\n");
15428                 SendToProgram(buf, cps);
15429         }
15430         return TRUE;
15431 }
15432
15433 void
15434 FeatureDone (ChessProgramState *cps, int val)
15435 {
15436   DelayedEventCallback cb = GetDelayedEvent();
15437   if ((cb == InitBackEnd3 && cps == &first) ||
15438       (cb == SettingsMenuIfReady && cps == &second) ||
15439       (cb == LoadEngine) ||
15440       (cb == TwoMachinesEventIfReady)) {
15441     CancelDelayedEvent();
15442     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15443   }
15444   cps->initDone = val;
15445 }
15446
15447 /* Parse feature command from engine */
15448 void
15449 ParseFeatures (char *args, ChessProgramState *cps)
15450 {
15451   char *p = args;
15452   char *q;
15453   int val;
15454   char buf[MSG_SIZ];
15455
15456   for (;;) {
15457     while (*p == ' ') p++;
15458     if (*p == NULLCHAR) return;
15459
15460     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15461     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15462     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15463     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15464     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15465     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15466     if (BoolFeature(&p, "reuse", &val, cps)) {
15467       /* Engine can disable reuse, but can't enable it if user said no */
15468       if (!val) cps->reuse = FALSE;
15469       continue;
15470     }
15471     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15472     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15473       if (gameMode == TwoMachinesPlay) {
15474         DisplayTwoMachinesTitle();
15475       } else {
15476         DisplayTitle("");
15477       }
15478       continue;
15479     }
15480     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15481     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15482     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15483     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15484     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15485     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15486     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15487     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15488     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15489     if (IntFeature(&p, "done", &val, cps)) {
15490       FeatureDone(cps, val);
15491       continue;
15492     }
15493     /* Added by Tord: */
15494     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15495     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15496     /* End of additions by Tord */
15497
15498     /* [HGM] added features: */
15499     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15500     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15501     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15502     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15503     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15504     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15505     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15506         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15507           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15508             SendToProgram(buf, cps);
15509             continue;
15510         }
15511         if(cps->nrOptions >= MAX_OPTIONS) {
15512             cps->nrOptions--;
15513             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15514             DisplayError(buf, 0);
15515         }
15516         continue;
15517     }
15518     /* End of additions by HGM */
15519
15520     /* unknown feature: complain and skip */
15521     q = p;
15522     while (*q && *q != '=') q++;
15523     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15524     SendToProgram(buf, cps);
15525     p = q;
15526     if (*p == '=') {
15527       p++;
15528       if (*p == '\"') {
15529         p++;
15530         while (*p && *p != '\"') p++;
15531         if (*p == '\"') p++;
15532       } else {
15533         while (*p && *p != ' ') p++;
15534       }
15535     }
15536   }
15537
15538 }
15539
15540 void
15541 PeriodicUpdatesEvent (int newState)
15542 {
15543     if (newState == appData.periodicUpdates)
15544       return;
15545
15546     appData.periodicUpdates=newState;
15547
15548     /* Display type changes, so update it now */
15549 //    DisplayAnalysis();
15550
15551     /* Get the ball rolling again... */
15552     if (newState) {
15553         AnalysisPeriodicEvent(1);
15554         StartAnalysisClock();
15555     }
15556 }
15557
15558 void
15559 PonderNextMoveEvent (int newState)
15560 {
15561     if (newState == appData.ponderNextMove) return;
15562     if (gameMode == EditPosition) EditPositionDone(TRUE);
15563     if (newState) {
15564         SendToProgram("hard\n", &first);
15565         if (gameMode == TwoMachinesPlay) {
15566             SendToProgram("hard\n", &second);
15567         }
15568     } else {
15569         SendToProgram("easy\n", &first);
15570         thinkOutput[0] = NULLCHAR;
15571         if (gameMode == TwoMachinesPlay) {
15572             SendToProgram("easy\n", &second);
15573         }
15574     }
15575     appData.ponderNextMove = newState;
15576 }
15577
15578 void
15579 NewSettingEvent (int option, int *feature, char *command, int value)
15580 {
15581     char buf[MSG_SIZ];
15582
15583     if (gameMode == EditPosition) EditPositionDone(TRUE);
15584     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15585     if(feature == NULL || *feature) SendToProgram(buf, &first);
15586     if (gameMode == TwoMachinesPlay) {
15587         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15588     }
15589 }
15590
15591 void
15592 ShowThinkingEvent ()
15593 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15594 {
15595     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15596     int newState = appData.showThinking
15597         // [HGM] thinking: other features now need thinking output as well
15598         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15599
15600     if (oldState == newState) return;
15601     oldState = newState;
15602     if (gameMode == EditPosition) EditPositionDone(TRUE);
15603     if (oldState) {
15604         SendToProgram("post\n", &first);
15605         if (gameMode == TwoMachinesPlay) {
15606             SendToProgram("post\n", &second);
15607         }
15608     } else {
15609         SendToProgram("nopost\n", &first);
15610         thinkOutput[0] = NULLCHAR;
15611         if (gameMode == TwoMachinesPlay) {
15612             SendToProgram("nopost\n", &second);
15613         }
15614     }
15615 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15616 }
15617
15618 void
15619 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15620 {
15621   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15622   if (pr == NoProc) return;
15623   AskQuestion(title, question, replyPrefix, pr);
15624 }
15625
15626 void
15627 TypeInEvent (char firstChar)
15628 {
15629     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15630         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15631         gameMode == AnalyzeMode || gameMode == EditGame || 
15632         gameMode == EditPosition || gameMode == IcsExamining ||
15633         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15634         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15635                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15636                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15637         gameMode == Training) PopUpMoveDialog(firstChar);
15638 }
15639
15640 void
15641 TypeInDoneEvent (char *move)
15642 {
15643         Board board;
15644         int n, fromX, fromY, toX, toY;
15645         char promoChar;
15646         ChessMove moveType;
15647
15648         // [HGM] FENedit
15649         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15650                 EditPositionPasteFEN(move);
15651                 return;
15652         }
15653         // [HGM] movenum: allow move number to be typed in any mode
15654         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15655           ToNrEvent(2*n-1);
15656           return;
15657         }
15658         // undocumented kludge: allow command-line option to be typed in!
15659         // (potentially fatal, and does not implement the effect of the option.)
15660         // should only be used for options that are values on which future decisions will be made,
15661         // and definitely not on options that would be used during initialization.
15662         if(strstr(move, "!!! -") == move) {
15663             ParseArgsFromString(move+4);
15664             return;
15665         }
15666
15667       if (gameMode != EditGame && currentMove != forwardMostMove && 
15668         gameMode != Training) {
15669         DisplayMoveError(_("Displayed move is not current"));
15670       } else {
15671         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15672           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15673         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15674         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15675           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15676           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15677         } else {
15678           DisplayMoveError(_("Could not parse move"));
15679         }
15680       }
15681 }
15682
15683 void
15684 DisplayMove (int moveNumber)
15685 {
15686     char message[MSG_SIZ];
15687     char res[MSG_SIZ];
15688     char cpThinkOutput[MSG_SIZ];
15689
15690     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15691
15692     if (moveNumber == forwardMostMove - 1 ||
15693         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15694
15695         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15696
15697         if (strchr(cpThinkOutput, '\n')) {
15698             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15699         }
15700     } else {
15701         *cpThinkOutput = NULLCHAR;
15702     }
15703
15704     /* [AS] Hide thinking from human user */
15705     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15706         *cpThinkOutput = NULLCHAR;
15707         if( thinkOutput[0] != NULLCHAR ) {
15708             int i;
15709
15710             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15711                 cpThinkOutput[i] = '.';
15712             }
15713             cpThinkOutput[i] = NULLCHAR;
15714             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15715         }
15716     }
15717
15718     if (moveNumber == forwardMostMove - 1 &&
15719         gameInfo.resultDetails != NULL) {
15720         if (gameInfo.resultDetails[0] == NULLCHAR) {
15721           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15722         } else {
15723           snprintf(res, MSG_SIZ, " {%s} %s",
15724                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15725         }
15726     } else {
15727         res[0] = NULLCHAR;
15728     }
15729
15730     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15731         DisplayMessage(res, cpThinkOutput);
15732     } else {
15733       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15734                 WhiteOnMove(moveNumber) ? " " : ".. ",
15735                 parseList[moveNumber], res);
15736         DisplayMessage(message, cpThinkOutput);
15737     }
15738 }
15739
15740 void
15741 DisplayComment (int moveNumber, char *text)
15742 {
15743     char title[MSG_SIZ];
15744
15745     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15746       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15747     } else {
15748       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15749               WhiteOnMove(moveNumber) ? " " : ".. ",
15750               parseList[moveNumber]);
15751     }
15752     if (text != NULL && (appData.autoDisplayComment || commentUp))
15753         CommentPopUp(title, text);
15754 }
15755
15756 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15757  * might be busy thinking or pondering.  It can be omitted if your
15758  * gnuchess is configured to stop thinking immediately on any user
15759  * input.  However, that gnuchess feature depends on the FIONREAD
15760  * ioctl, which does not work properly on some flavors of Unix.
15761  */
15762 void
15763 Attention (ChessProgramState *cps)
15764 {
15765 #if ATTENTION
15766     if (!cps->useSigint) return;
15767     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15768     switch (gameMode) {
15769       case MachinePlaysWhite:
15770       case MachinePlaysBlack:
15771       case TwoMachinesPlay:
15772       case IcsPlayingWhite:
15773       case IcsPlayingBlack:
15774       case AnalyzeMode:
15775       case AnalyzeFile:
15776         /* Skip if we know it isn't thinking */
15777         if (!cps->maybeThinking) return;
15778         if (appData.debugMode)
15779           fprintf(debugFP, "Interrupting %s\n", cps->which);
15780         InterruptChildProcess(cps->pr);
15781         cps->maybeThinking = FALSE;
15782         break;
15783       default:
15784         break;
15785     }
15786 #endif /*ATTENTION*/
15787 }
15788
15789 int
15790 CheckFlags ()
15791 {
15792     if (whiteTimeRemaining <= 0) {
15793         if (!whiteFlag) {
15794             whiteFlag = TRUE;
15795             if (appData.icsActive) {
15796                 if (appData.autoCallFlag &&
15797                     gameMode == IcsPlayingBlack && !blackFlag) {
15798                   SendToICS(ics_prefix);
15799                   SendToICS("flag\n");
15800                 }
15801             } else {
15802                 if (blackFlag) {
15803                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15804                 } else {
15805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15806                     if (appData.autoCallFlag) {
15807                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15808                         return TRUE;
15809                     }
15810                 }
15811             }
15812         }
15813     }
15814     if (blackTimeRemaining <= 0) {
15815         if (!blackFlag) {
15816             blackFlag = TRUE;
15817             if (appData.icsActive) {
15818                 if (appData.autoCallFlag &&
15819                     gameMode == IcsPlayingWhite && !whiteFlag) {
15820                   SendToICS(ics_prefix);
15821                   SendToICS("flag\n");
15822                 }
15823             } else {
15824                 if (whiteFlag) {
15825                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15826                 } else {
15827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15828                     if (appData.autoCallFlag) {
15829                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15830                         return TRUE;
15831                     }
15832                 }
15833             }
15834         }
15835     }
15836     return FALSE;
15837 }
15838
15839 void
15840 CheckTimeControl ()
15841 {
15842     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15843         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15844
15845     /*
15846      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15847      */
15848     if ( !WhiteOnMove(forwardMostMove) ) {
15849         /* White made time control */
15850         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15851         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15852         /* [HGM] time odds: correct new time quota for time odds! */
15853                                             / WhitePlayer()->timeOdds;
15854         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15855     } else {
15856         lastBlack -= blackTimeRemaining;
15857         /* Black made time control */
15858         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15859                                             / WhitePlayer()->other->timeOdds;
15860         lastWhite = whiteTimeRemaining;
15861     }
15862 }
15863
15864 void
15865 DisplayBothClocks ()
15866 {
15867     int wom = gameMode == EditPosition ?
15868       !blackPlaysFirst : WhiteOnMove(currentMove);
15869     DisplayWhiteClock(whiteTimeRemaining, wom);
15870     DisplayBlackClock(blackTimeRemaining, !wom);
15871 }
15872
15873
15874 /* Timekeeping seems to be a portability nightmare.  I think everyone
15875    has ftime(), but I'm really not sure, so I'm including some ifdefs
15876    to use other calls if you don't.  Clocks will be less accurate if
15877    you have neither ftime nor gettimeofday.
15878 */
15879
15880 /* VS 2008 requires the #include outside of the function */
15881 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15882 #include <sys/timeb.h>
15883 #endif
15884
15885 /* Get the current time as a TimeMark */
15886 void
15887 GetTimeMark (TimeMark *tm)
15888 {
15889 #if HAVE_GETTIMEOFDAY
15890
15891     struct timeval timeVal;
15892     struct timezone timeZone;
15893
15894     gettimeofday(&timeVal, &timeZone);
15895     tm->sec = (long) timeVal.tv_sec;
15896     tm->ms = (int) (timeVal.tv_usec / 1000L);
15897
15898 #else /*!HAVE_GETTIMEOFDAY*/
15899 #if HAVE_FTIME
15900
15901 // include <sys/timeb.h> / moved to just above start of function
15902     struct timeb timeB;
15903
15904     ftime(&timeB);
15905     tm->sec = (long) timeB.time;
15906     tm->ms = (int) timeB.millitm;
15907
15908 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15909     tm->sec = (long) time(NULL);
15910     tm->ms = 0;
15911 #endif
15912 #endif
15913 }
15914
15915 /* Return the difference in milliseconds between two
15916    time marks.  We assume the difference will fit in a long!
15917 */
15918 long
15919 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15920 {
15921     return 1000L*(tm2->sec - tm1->sec) +
15922            (long) (tm2->ms - tm1->ms);
15923 }
15924
15925
15926 /*
15927  * Code to manage the game clocks.
15928  *
15929  * In tournament play, black starts the clock and then white makes a move.
15930  * We give the human user a slight advantage if he is playing white---the
15931  * clocks don't run until he makes his first move, so it takes zero time.
15932  * Also, we don't account for network lag, so we could get out of sync
15933  * with GNU Chess's clock -- but then, referees are always right.
15934  */
15935
15936 static TimeMark tickStartTM;
15937 static long intendedTickLength;
15938
15939 long
15940 NextTickLength (long timeRemaining)
15941 {
15942     long nominalTickLength, nextTickLength;
15943
15944     if (timeRemaining > 0L && timeRemaining <= 10000L)
15945       nominalTickLength = 100L;
15946     else
15947       nominalTickLength = 1000L;
15948     nextTickLength = timeRemaining % nominalTickLength;
15949     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15950
15951     return nextTickLength;
15952 }
15953
15954 /* Adjust clock one minute up or down */
15955 void
15956 AdjustClock (Boolean which, int dir)
15957 {
15958     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15959     if(which) blackTimeRemaining += 60000*dir;
15960     else      whiteTimeRemaining += 60000*dir;
15961     DisplayBothClocks();
15962     adjustedClock = TRUE;
15963 }
15964
15965 /* Stop clocks and reset to a fresh time control */
15966 void
15967 ResetClocks ()
15968 {
15969     (void) StopClockTimer();
15970     if (appData.icsActive) {
15971         whiteTimeRemaining = blackTimeRemaining = 0;
15972     } else if (searchTime) {
15973         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15974         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15975     } else { /* [HGM] correct new time quote for time odds */
15976         whiteTC = blackTC = fullTimeControlString;
15977         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15978         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15979     }
15980     if (whiteFlag || blackFlag) {
15981         DisplayTitle("");
15982         whiteFlag = blackFlag = FALSE;
15983     }
15984     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15985     DisplayBothClocks();
15986     adjustedClock = FALSE;
15987 }
15988
15989 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15990
15991 /* Decrement running clock by amount of time that has passed */
15992 void
15993 DecrementClocks ()
15994 {
15995     long timeRemaining;
15996     long lastTickLength, fudge;
15997     TimeMark now;
15998
15999     if (!appData.clockMode) return;
16000     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16001
16002     GetTimeMark(&now);
16003
16004     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16005
16006     /* Fudge if we woke up a little too soon */
16007     fudge = intendedTickLength - lastTickLength;
16008     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16009
16010     if (WhiteOnMove(forwardMostMove)) {
16011         if(whiteNPS >= 0) lastTickLength = 0;
16012         timeRemaining = whiteTimeRemaining -= lastTickLength;
16013         if(timeRemaining < 0 && !appData.icsActive) {
16014             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16015             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16016                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16017                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16018             }
16019         }
16020         DisplayWhiteClock(whiteTimeRemaining - fudge,
16021                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16022     } else {
16023         if(blackNPS >= 0) lastTickLength = 0;
16024         timeRemaining = blackTimeRemaining -= lastTickLength;
16025         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16026             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16027             if(suddenDeath) {
16028                 blackStartMove = forwardMostMove;
16029                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16030             }
16031         }
16032         DisplayBlackClock(blackTimeRemaining - fudge,
16033                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16034     }
16035     if (CheckFlags()) return;
16036
16037     tickStartTM = now;
16038     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16039     StartClockTimer(intendedTickLength);
16040
16041     /* if the time remaining has fallen below the alarm threshold, sound the
16042      * alarm. if the alarm has sounded and (due to a takeback or time control
16043      * with increment) the time remaining has increased to a level above the
16044      * threshold, reset the alarm so it can sound again.
16045      */
16046
16047     if (appData.icsActive && appData.icsAlarm) {
16048
16049         /* make sure we are dealing with the user's clock */
16050         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16051                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16052            )) return;
16053
16054         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16055             alarmSounded = FALSE;
16056         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16057             PlayAlarmSound();
16058             alarmSounded = TRUE;
16059         }
16060     }
16061 }
16062
16063
16064 /* A player has just moved, so stop the previously running
16065    clock and (if in clock mode) start the other one.
16066    We redisplay both clocks in case we're in ICS mode, because
16067    ICS gives us an update to both clocks after every move.
16068    Note that this routine is called *after* forwardMostMove
16069    is updated, so the last fractional tick must be subtracted
16070    from the color that is *not* on move now.
16071 */
16072 void
16073 SwitchClocks (int newMoveNr)
16074 {
16075     long lastTickLength;
16076     TimeMark now;
16077     int flagged = FALSE;
16078
16079     GetTimeMark(&now);
16080
16081     if (StopClockTimer() && appData.clockMode) {
16082         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16083         if (!WhiteOnMove(forwardMostMove)) {
16084             if(blackNPS >= 0) lastTickLength = 0;
16085             blackTimeRemaining -= lastTickLength;
16086            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16087 //         if(pvInfoList[forwardMostMove].time == -1)
16088                  pvInfoList[forwardMostMove].time =               // use GUI time
16089                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16090         } else {
16091            if(whiteNPS >= 0) lastTickLength = 0;
16092            whiteTimeRemaining -= lastTickLength;
16093            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16094 //         if(pvInfoList[forwardMostMove].time == -1)
16095                  pvInfoList[forwardMostMove].time =
16096                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16097         }
16098         flagged = CheckFlags();
16099     }
16100     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16101     CheckTimeControl();
16102
16103     if (flagged || !appData.clockMode) return;
16104
16105     switch (gameMode) {
16106       case MachinePlaysBlack:
16107       case MachinePlaysWhite:
16108       case BeginningOfGame:
16109         if (pausing) return;
16110         break;
16111
16112       case EditGame:
16113       case PlayFromGameFile:
16114       case IcsExamining:
16115         return;
16116
16117       default:
16118         break;
16119     }
16120
16121     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16122         if(WhiteOnMove(forwardMostMove))
16123              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16124         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16125     }
16126
16127     tickStartTM = now;
16128     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16129       whiteTimeRemaining : blackTimeRemaining);
16130     StartClockTimer(intendedTickLength);
16131 }
16132
16133
16134 /* Stop both clocks */
16135 void
16136 StopClocks ()
16137 {
16138     long lastTickLength;
16139     TimeMark now;
16140
16141     if (!StopClockTimer()) return;
16142     if (!appData.clockMode) return;
16143
16144     GetTimeMark(&now);
16145
16146     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16147     if (WhiteOnMove(forwardMostMove)) {
16148         if(whiteNPS >= 0) lastTickLength = 0;
16149         whiteTimeRemaining -= lastTickLength;
16150         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16151     } else {
16152         if(blackNPS >= 0) lastTickLength = 0;
16153         blackTimeRemaining -= lastTickLength;
16154         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16155     }
16156     CheckFlags();
16157 }
16158
16159 /* Start clock of player on move.  Time may have been reset, so
16160    if clock is already running, stop and restart it. */
16161 void
16162 StartClocks ()
16163 {
16164     (void) StopClockTimer(); /* in case it was running already */
16165     DisplayBothClocks();
16166     if (CheckFlags()) return;
16167
16168     if (!appData.clockMode) return;
16169     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16170
16171     GetTimeMark(&tickStartTM);
16172     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16173       whiteTimeRemaining : blackTimeRemaining);
16174
16175    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16176     whiteNPS = blackNPS = -1;
16177     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16178        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16179         whiteNPS = first.nps;
16180     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16181        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16182         blackNPS = first.nps;
16183     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16184         whiteNPS = second.nps;
16185     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16186         blackNPS = second.nps;
16187     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16188
16189     StartClockTimer(intendedTickLength);
16190 }
16191
16192 char *
16193 TimeString (long ms)
16194 {
16195     long second, minute, hour, day;
16196     char *sign = "";
16197     static char buf[32];
16198
16199     if (ms > 0 && ms <= 9900) {
16200       /* convert milliseconds to tenths, rounding up */
16201       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16202
16203       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16204       return buf;
16205     }
16206
16207     /* convert milliseconds to seconds, rounding up */
16208     /* use floating point to avoid strangeness of integer division
16209        with negative dividends on many machines */
16210     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16211
16212     if (second < 0) {
16213         sign = "-";
16214         second = -second;
16215     }
16216
16217     day = second / (60 * 60 * 24);
16218     second = second % (60 * 60 * 24);
16219     hour = second / (60 * 60);
16220     second = second % (60 * 60);
16221     minute = second / 60;
16222     second = second % 60;
16223
16224     if (day > 0)
16225       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16226               sign, day, hour, minute, second);
16227     else if (hour > 0)
16228       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16229     else
16230       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16231
16232     return buf;
16233 }
16234
16235
16236 /*
16237  * This is necessary because some C libraries aren't ANSI C compliant yet.
16238  */
16239 char *
16240 StrStr (char *string, char *match)
16241 {
16242     int i, length;
16243
16244     length = strlen(match);
16245
16246     for (i = strlen(string) - length; i >= 0; i--, string++)
16247       if (!strncmp(match, string, length))
16248         return string;
16249
16250     return NULL;
16251 }
16252
16253 char *
16254 StrCaseStr (char *string, char *match)
16255 {
16256     int i, j, length;
16257
16258     length = strlen(match);
16259
16260     for (i = strlen(string) - length; i >= 0; i--, string++) {
16261         for (j = 0; j < length; j++) {
16262             if (ToLower(match[j]) != ToLower(string[j]))
16263               break;
16264         }
16265         if (j == length) return string;
16266     }
16267
16268     return NULL;
16269 }
16270
16271 #ifndef _amigados
16272 int
16273 StrCaseCmp (char *s1, char *s2)
16274 {
16275     char c1, c2;
16276
16277     for (;;) {
16278         c1 = ToLower(*s1++);
16279         c2 = ToLower(*s2++);
16280         if (c1 > c2) return 1;
16281         if (c1 < c2) return -1;
16282         if (c1 == NULLCHAR) return 0;
16283     }
16284 }
16285
16286
16287 int
16288 ToLower (int c)
16289 {
16290     return isupper(c) ? tolower(c) : c;
16291 }
16292
16293
16294 int
16295 ToUpper (int c)
16296 {
16297     return islower(c) ? toupper(c) : c;
16298 }
16299 #endif /* !_amigados    */
16300
16301 char *
16302 StrSave (char *s)
16303 {
16304   char *ret;
16305
16306   if ((ret = (char *) malloc(strlen(s) + 1)))
16307     {
16308       safeStrCpy(ret, s, strlen(s)+1);
16309     }
16310   return ret;
16311 }
16312
16313 char *
16314 StrSavePtr (char *s, char **savePtr)
16315 {
16316     if (*savePtr) {
16317         free(*savePtr);
16318     }
16319     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16320       safeStrCpy(*savePtr, s, strlen(s)+1);
16321     }
16322     return(*savePtr);
16323 }
16324
16325 char *
16326 PGNDate ()
16327 {
16328     time_t clock;
16329     struct tm *tm;
16330     char buf[MSG_SIZ];
16331
16332     clock = time((time_t *)NULL);
16333     tm = localtime(&clock);
16334     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16335             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16336     return StrSave(buf);
16337 }
16338
16339
16340 char *
16341 PositionToFEN (int move, char *overrideCastling)
16342 {
16343     int i, j, fromX, fromY, toX, toY;
16344     int whiteToPlay;
16345     char buf[MSG_SIZ];
16346     char *p, *q;
16347     int emptycount;
16348     ChessSquare piece;
16349
16350     whiteToPlay = (gameMode == EditPosition) ?
16351       !blackPlaysFirst : (move % 2 == 0);
16352     p = buf;
16353
16354     /* Piece placement data */
16355     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16356         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16357         emptycount = 0;
16358         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16359             if (boards[move][i][j] == EmptySquare) {
16360                 emptycount++;
16361             } else { ChessSquare piece = boards[move][i][j];
16362                 if (emptycount > 0) {
16363                     if(emptycount<10) /* [HGM] can be >= 10 */
16364                         *p++ = '0' + emptycount;
16365                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16366                     emptycount = 0;
16367                 }
16368                 if(PieceToChar(piece) == '+') {
16369                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16370                     *p++ = '+';
16371                     piece = (ChessSquare)(DEMOTED piece);
16372                 }
16373                 *p++ = PieceToChar(piece);
16374                 if(p[-1] == '~') {
16375                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16376                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16377                     *p++ = '~';
16378                 }
16379             }
16380         }
16381         if (emptycount > 0) {
16382             if(emptycount<10) /* [HGM] can be >= 10 */
16383                 *p++ = '0' + emptycount;
16384             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16385             emptycount = 0;
16386         }
16387         *p++ = '/';
16388     }
16389     *(p - 1) = ' ';
16390
16391     /* [HGM] print Crazyhouse or Shogi holdings */
16392     if( gameInfo.holdingsWidth ) {
16393         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16394         q = p;
16395         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16396             piece = boards[move][i][BOARD_WIDTH-1];
16397             if( piece != EmptySquare )
16398               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16399                   *p++ = PieceToChar(piece);
16400         }
16401         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16402             piece = boards[move][BOARD_HEIGHT-i-1][0];
16403             if( piece != EmptySquare )
16404               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16405                   *p++ = PieceToChar(piece);
16406         }
16407
16408         if( q == p ) *p++ = '-';
16409         *p++ = ']';
16410         *p++ = ' ';
16411     }
16412
16413     /* Active color */
16414     *p++ = whiteToPlay ? 'w' : 'b';
16415     *p++ = ' ';
16416
16417   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16418     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16419   } else {
16420   if(nrCastlingRights) {
16421      q = p;
16422      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16423        /* [HGM] write directly from rights */
16424            if(boards[move][CASTLING][2] != NoRights &&
16425               boards[move][CASTLING][0] != NoRights   )
16426                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16427            if(boards[move][CASTLING][2] != NoRights &&
16428               boards[move][CASTLING][1] != NoRights   )
16429                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16430            if(boards[move][CASTLING][5] != NoRights &&
16431               boards[move][CASTLING][3] != NoRights   )
16432                 *p++ = boards[move][CASTLING][3] + AAA;
16433            if(boards[move][CASTLING][5] != NoRights &&
16434               boards[move][CASTLING][4] != NoRights   )
16435                 *p++ = boards[move][CASTLING][4] + AAA;
16436      } else {
16437
16438         /* [HGM] write true castling rights */
16439         if( nrCastlingRights == 6 ) {
16440             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16441                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16442             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16443                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16444             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16445                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16446             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16447                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16448         }
16449      }
16450      if (q == p) *p++ = '-'; /* No castling rights */
16451      *p++ = ' ';
16452   }
16453
16454   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16455      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16456     /* En passant target square */
16457     if (move > backwardMostMove) {
16458         fromX = moveList[move - 1][0] - AAA;
16459         fromY = moveList[move - 1][1] - ONE;
16460         toX = moveList[move - 1][2] - AAA;
16461         toY = moveList[move - 1][3] - ONE;
16462         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16463             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16464             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16465             fromX == toX) {
16466             /* 2-square pawn move just happened */
16467             *p++ = toX + AAA;
16468             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16469         } else {
16470             *p++ = '-';
16471         }
16472     } else if(move == backwardMostMove) {
16473         // [HGM] perhaps we should always do it like this, and forget the above?
16474         if((signed char)boards[move][EP_STATUS] >= 0) {
16475             *p++ = boards[move][EP_STATUS] + AAA;
16476             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16477         } else {
16478             *p++ = '-';
16479         }
16480     } else {
16481         *p++ = '-';
16482     }
16483     *p++ = ' ';
16484   }
16485   }
16486
16487     /* [HGM] find reversible plies */
16488     {   int i = 0, j=move;
16489
16490         if (appData.debugMode) { int k;
16491             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16492             for(k=backwardMostMove; k<=forwardMostMove; k++)
16493                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16494
16495         }
16496
16497         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16498         if( j == backwardMostMove ) i += initialRulePlies;
16499         sprintf(p, "%d ", i);
16500         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16501     }
16502     /* Fullmove number */
16503     sprintf(p, "%d", (move / 2) + 1);
16504
16505     return StrSave(buf);
16506 }
16507
16508 Boolean
16509 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16510 {
16511     int i, j;
16512     char *p, c;
16513     int emptycount;
16514     ChessSquare piece;
16515
16516     p = fen;
16517
16518     /* [HGM] by default clear Crazyhouse holdings, if present */
16519     if(gameInfo.holdingsWidth) {
16520        for(i=0; i<BOARD_HEIGHT; i++) {
16521            board[i][0]             = EmptySquare; /* black holdings */
16522            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16523            board[i][1]             = (ChessSquare) 0; /* black counts */
16524            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16525        }
16526     }
16527
16528     /* Piece placement data */
16529     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16530         j = 0;
16531         for (;;) {
16532             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16533                 if (*p == '/') p++;
16534                 emptycount = gameInfo.boardWidth - j;
16535                 while (emptycount--)
16536                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16537                 break;
16538 #if(BOARD_FILES >= 10)
16539             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16540                 p++; emptycount=10;
16541                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16542                 while (emptycount--)
16543                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16544 #endif
16545             } else if (isdigit(*p)) {
16546                 emptycount = *p++ - '0';
16547                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16548                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16549                 while (emptycount--)
16550                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16551             } else if (*p == '+' || isalpha(*p)) {
16552                 if (j >= gameInfo.boardWidth) return FALSE;
16553                 if(*p=='+') {
16554                     piece = CharToPiece(*++p);
16555                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16556                     piece = (ChessSquare) (PROMOTED piece ); p++;
16557                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16558                 } else piece = CharToPiece(*p++);
16559
16560                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16561                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16562                     piece = (ChessSquare) (PROMOTED piece);
16563                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16564                     p++;
16565                 }
16566                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16567             } else {
16568                 return FALSE;
16569             }
16570         }
16571     }
16572     while (*p == '/' || *p == ' ') p++;
16573
16574     /* [HGM] look for Crazyhouse holdings here */
16575     while(*p==' ') p++;
16576     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16577         if(*p == '[') p++;
16578         if(*p == '-' ) p++; /* empty holdings */ else {
16579             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16580             /* if we would allow FEN reading to set board size, we would   */
16581             /* have to add holdings and shift the board read so far here   */
16582             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16583                 p++;
16584                 if((int) piece >= (int) BlackPawn ) {
16585                     i = (int)piece - (int)BlackPawn;
16586                     i = PieceToNumber((ChessSquare)i);
16587                     if( i >= gameInfo.holdingsSize ) return FALSE;
16588                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16589                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16590                 } else {
16591                     i = (int)piece - (int)WhitePawn;
16592                     i = PieceToNumber((ChessSquare)i);
16593                     if( i >= gameInfo.holdingsSize ) return FALSE;
16594                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16595                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16596                 }
16597             }
16598         }
16599         if(*p == ']') p++;
16600     }
16601
16602     while(*p == ' ') p++;
16603
16604     /* Active color */
16605     c = *p++;
16606     if(appData.colorNickNames) {
16607       if( c == appData.colorNickNames[0] ) c = 'w'; else
16608       if( c == appData.colorNickNames[1] ) c = 'b';
16609     }
16610     switch (c) {
16611       case 'w':
16612         *blackPlaysFirst = FALSE;
16613         break;
16614       case 'b':
16615         *blackPlaysFirst = TRUE;
16616         break;
16617       default:
16618         return FALSE;
16619     }
16620
16621     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16622     /* return the extra info in global variiables             */
16623
16624     /* set defaults in case FEN is incomplete */
16625     board[EP_STATUS] = EP_UNKNOWN;
16626     for(i=0; i<nrCastlingRights; i++ ) {
16627         board[CASTLING][i] =
16628             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16629     }   /* assume possible unless obviously impossible */
16630     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16631     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16632     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16633                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16634     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16635     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16636     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16637                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16638     FENrulePlies = 0;
16639
16640     while(*p==' ') p++;
16641     if(nrCastlingRights) {
16642       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16643           /* castling indicator present, so default becomes no castlings */
16644           for(i=0; i<nrCastlingRights; i++ ) {
16645                  board[CASTLING][i] = NoRights;
16646           }
16647       }
16648       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16649              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16650              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16651              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16652         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16653
16654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16655             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16656             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16657         }
16658         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16659             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16660         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16661                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16662         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16663                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16664         switch(c) {
16665           case'K':
16666               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16667               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16668               board[CASTLING][2] = whiteKingFile;
16669               break;
16670           case'Q':
16671               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16672               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16673               board[CASTLING][2] = whiteKingFile;
16674               break;
16675           case'k':
16676               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16677               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16678               board[CASTLING][5] = blackKingFile;
16679               break;
16680           case'q':
16681               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16682               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16683               board[CASTLING][5] = blackKingFile;
16684           case '-':
16685               break;
16686           default: /* FRC castlings */
16687               if(c >= 'a') { /* black rights */
16688                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16689                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16690                   if(i == BOARD_RGHT) break;
16691                   board[CASTLING][5] = i;
16692                   c -= AAA;
16693                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16694                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16695                   if(c > i)
16696                       board[CASTLING][3] = c;
16697                   else
16698                       board[CASTLING][4] = c;
16699               } else { /* white rights */
16700                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16701                     if(board[0][i] == WhiteKing) break;
16702                   if(i == BOARD_RGHT) break;
16703                   board[CASTLING][2] = i;
16704                   c -= AAA - 'a' + 'A';
16705                   if(board[0][c] >= WhiteKing) break;
16706                   if(c > i)
16707                       board[CASTLING][0] = c;
16708                   else
16709                       board[CASTLING][1] = c;
16710               }
16711         }
16712       }
16713       for(i=0; i<nrCastlingRights; i++)
16714         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16715     if (appData.debugMode) {
16716         fprintf(debugFP, "FEN castling rights:");
16717         for(i=0; i<nrCastlingRights; i++)
16718         fprintf(debugFP, " %d", board[CASTLING][i]);
16719         fprintf(debugFP, "\n");
16720     }
16721
16722       while(*p==' ') p++;
16723     }
16724
16725     /* read e.p. field in games that know e.p. capture */
16726     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16727        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16728       if(*p=='-') {
16729         p++; board[EP_STATUS] = EP_NONE;
16730       } else {
16731          char c = *p++ - AAA;
16732
16733          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16734          if(*p >= '0' && *p <='9') p++;
16735          board[EP_STATUS] = c;
16736       }
16737     }
16738
16739
16740     if(sscanf(p, "%d", &i) == 1) {
16741         FENrulePlies = i; /* 50-move ply counter */
16742         /* (The move number is still ignored)    */
16743     }
16744
16745     return TRUE;
16746 }
16747
16748 void
16749 EditPositionPasteFEN (char *fen)
16750 {
16751   if (fen != NULL) {
16752     Board initial_position;
16753
16754     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16755       DisplayError(_("Bad FEN position in clipboard"), 0);
16756       return ;
16757     } else {
16758       int savedBlackPlaysFirst = blackPlaysFirst;
16759       EditPositionEvent();
16760       blackPlaysFirst = savedBlackPlaysFirst;
16761       CopyBoard(boards[0], initial_position);
16762       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16763       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16764       DisplayBothClocks();
16765       DrawPosition(FALSE, boards[currentMove]);
16766     }
16767   }
16768 }
16769
16770 static char cseq[12] = "\\   ";
16771
16772 Boolean
16773 set_cont_sequence (char *new_seq)
16774 {
16775     int len;
16776     Boolean ret;
16777
16778     // handle bad attempts to set the sequence
16779         if (!new_seq)
16780                 return 0; // acceptable error - no debug
16781
16782     len = strlen(new_seq);
16783     ret = (len > 0) && (len < sizeof(cseq));
16784     if (ret)
16785       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16786     else if (appData.debugMode)
16787       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16788     return ret;
16789 }
16790
16791 /*
16792     reformat a source message so words don't cross the width boundary.  internal
16793     newlines are not removed.  returns the wrapped size (no null character unless
16794     included in source message).  If dest is NULL, only calculate the size required
16795     for the dest buffer.  lp argument indicats line position upon entry, and it's
16796     passed back upon exit.
16797 */
16798 int
16799 wrap (char *dest, char *src, int count, int width, int *lp)
16800 {
16801     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16802
16803     cseq_len = strlen(cseq);
16804     old_line = line = *lp;
16805     ansi = len = clen = 0;
16806
16807     for (i=0; i < count; i++)
16808     {
16809         if (src[i] == '\033')
16810             ansi = 1;
16811
16812         // if we hit the width, back up
16813         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16814         {
16815             // store i & len in case the word is too long
16816             old_i = i, old_len = len;
16817
16818             // find the end of the last word
16819             while (i && src[i] != ' ' && src[i] != '\n')
16820             {
16821                 i--;
16822                 len--;
16823             }
16824
16825             // word too long?  restore i & len before splitting it
16826             if ((old_i-i+clen) >= width)
16827             {
16828                 i = old_i;
16829                 len = old_len;
16830             }
16831
16832             // extra space?
16833             if (i && src[i-1] == ' ')
16834                 len--;
16835
16836             if (src[i] != ' ' && src[i] != '\n')
16837             {
16838                 i--;
16839                 if (len)
16840                     len--;
16841             }
16842
16843             // now append the newline and continuation sequence
16844             if (dest)
16845                 dest[len] = '\n';
16846             len++;
16847             if (dest)
16848                 strncpy(dest+len, cseq, cseq_len);
16849             len += cseq_len;
16850             line = cseq_len;
16851             clen = cseq_len;
16852             continue;
16853         }
16854
16855         if (dest)
16856             dest[len] = src[i];
16857         len++;
16858         if (!ansi)
16859             line++;
16860         if (src[i] == '\n')
16861             line = 0;
16862         if (src[i] == 'm')
16863             ansi = 0;
16864     }
16865     if (dest && appData.debugMode)
16866     {
16867         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16868             count, width, line, len, *lp);
16869         show_bytes(debugFP, src, count);
16870         fprintf(debugFP, "\ndest: ");
16871         show_bytes(debugFP, dest, len);
16872         fprintf(debugFP, "\n");
16873     }
16874     *lp = dest ? line : old_line;
16875
16876     return len;
16877 }
16878
16879 // [HGM] vari: routines for shelving variations
16880 Boolean modeRestore = FALSE;
16881
16882 void
16883 PushInner (int firstMove, int lastMove)
16884 {
16885         int i, j, nrMoves = lastMove - firstMove;
16886
16887         // push current tail of game on stack
16888         savedResult[storedGames] = gameInfo.result;
16889         savedDetails[storedGames] = gameInfo.resultDetails;
16890         gameInfo.resultDetails = NULL;
16891         savedFirst[storedGames] = firstMove;
16892         savedLast [storedGames] = lastMove;
16893         savedFramePtr[storedGames] = framePtr;
16894         framePtr -= nrMoves; // reserve space for the boards
16895         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16896             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16897             for(j=0; j<MOVE_LEN; j++)
16898                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16899             for(j=0; j<2*MOVE_LEN; j++)
16900                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16901             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16902             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16903             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16904             pvInfoList[firstMove+i-1].depth = 0;
16905             commentList[framePtr+i] = commentList[firstMove+i];
16906             commentList[firstMove+i] = NULL;
16907         }
16908
16909         storedGames++;
16910         forwardMostMove = firstMove; // truncate game so we can start variation
16911 }
16912
16913 void
16914 PushTail (int firstMove, int lastMove)
16915 {
16916         if(appData.icsActive) { // only in local mode
16917                 forwardMostMove = currentMove; // mimic old ICS behavior
16918                 return;
16919         }
16920         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16921
16922         PushInner(firstMove, lastMove);
16923         if(storedGames == 1) GreyRevert(FALSE);
16924         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16925 }
16926
16927 void
16928 PopInner (Boolean annotate)
16929 {
16930         int i, j, nrMoves;
16931         char buf[8000], moveBuf[20];
16932
16933         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16934         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16935         nrMoves = savedLast[storedGames] - currentMove;
16936         if(annotate) {
16937                 int cnt = 10;
16938                 if(!WhiteOnMove(currentMove))
16939                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16940                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16941                 for(i=currentMove; i<forwardMostMove; i++) {
16942                         if(WhiteOnMove(i))
16943                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16944                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16945                         strcat(buf, moveBuf);
16946                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16947                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16948                 }
16949                 strcat(buf, ")");
16950         }
16951         for(i=1; i<=nrMoves; i++) { // copy last variation back
16952             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16953             for(j=0; j<MOVE_LEN; j++)
16954                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16955             for(j=0; j<2*MOVE_LEN; j++)
16956                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16957             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16958             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16959             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16960             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16961             commentList[currentMove+i] = commentList[framePtr+i];
16962             commentList[framePtr+i] = NULL;
16963         }
16964         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16965         framePtr = savedFramePtr[storedGames];
16966         gameInfo.result = savedResult[storedGames];
16967         if(gameInfo.resultDetails != NULL) {
16968             free(gameInfo.resultDetails);
16969       }
16970         gameInfo.resultDetails = savedDetails[storedGames];
16971         forwardMostMove = currentMove + nrMoves;
16972 }
16973
16974 Boolean
16975 PopTail (Boolean annotate)
16976 {
16977         if(appData.icsActive) return FALSE; // only in local mode
16978         if(!storedGames) return FALSE; // sanity
16979         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16980
16981         PopInner(annotate);
16982         if(currentMove < forwardMostMove) ForwardEvent(); else
16983         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16984
16985         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16986         return TRUE;
16987 }
16988
16989 void
16990 CleanupTail ()
16991 {       // remove all shelved variations
16992         int i;
16993         for(i=0; i<storedGames; i++) {
16994             if(savedDetails[i])
16995                 free(savedDetails[i]);
16996             savedDetails[i] = NULL;
16997         }
16998         for(i=framePtr; i<MAX_MOVES; i++) {
16999                 if(commentList[i]) free(commentList[i]);
17000                 commentList[i] = NULL;
17001         }
17002         framePtr = MAX_MOVES-1;
17003         storedGames = 0;
17004 }
17005
17006 void
17007 LoadVariation (int index, char *text)
17008 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17009         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17010         int level = 0, move;
17011
17012         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17013         // first find outermost bracketing variation
17014         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17015             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17016                 if(*p == '{') wait = '}'; else
17017                 if(*p == '[') wait = ']'; else
17018                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17019                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17020             }
17021             if(*p == wait) wait = NULLCHAR; // closing ]} found
17022             p++;
17023         }
17024         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17025         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17026         end[1] = NULLCHAR; // clip off comment beyond variation
17027         ToNrEvent(currentMove-1);
17028         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17029         // kludge: use ParsePV() to append variation to game
17030         move = currentMove;
17031         ParsePV(start, TRUE, TRUE);
17032         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17033         ClearPremoveHighlights();
17034         CommentPopDown();
17035         ToNrEvent(currentMove+1);
17036 }
17037