b1d35a9cb92eda05831ba54bdbdef4a4dd9a4c28
[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             if(cps == &first) {
8382                 appData.noChessProgram = TRUE;
8383                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8384                 gameMode = BeginningOfGame; ModeHighlight();
8385                 SetNCPMode();
8386             }
8387             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8388             DisplayMessage("", ""); // erase waiting message
8389             DisplayError(buf1, 0);
8390         }
8391         return;
8392     }
8393
8394     /*
8395      * Look for hint output
8396      */
8397     if (sscanf(message, "Hint: %s", buf1) == 1) {
8398         if (cps == &first && hintRequested) {
8399             hintRequested = FALSE;
8400             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8401                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8402                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8403                                     PosFlags(forwardMostMove),
8404                                     fromY, fromX, toY, toX, promoChar, buf1);
8405                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8406                 DisplayInformation(buf2);
8407             } else {
8408                 /* Hint move could not be parsed!? */
8409               snprintf(buf2, sizeof(buf2),
8410                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8411                         buf1, _(cps->which));
8412                 DisplayError(buf2, 0);
8413             }
8414         } else {
8415           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8416         }
8417         return;
8418     }
8419
8420     /*
8421      * Ignore other messages if game is not in progress
8422      */
8423     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8424         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8425
8426     /*
8427      * look for win, lose, draw, or draw offer
8428      */
8429     if (strncmp(message, "1-0", 3) == 0) {
8430         char *p, *q, *r = "";
8431         p = strchr(message, '{');
8432         if (p) {
8433             q = strchr(p, '}');
8434             if (q) {
8435                 *q = NULLCHAR;
8436                 r = p + 1;
8437             }
8438         }
8439         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8440         return;
8441     } else if (strncmp(message, "0-1", 3) == 0) {
8442         char *p, *q, *r = "";
8443         p = strchr(message, '{');
8444         if (p) {
8445             q = strchr(p, '}');
8446             if (q) {
8447                 *q = NULLCHAR;
8448                 r = p + 1;
8449             }
8450         }
8451         /* Kludge for Arasan 4.1 bug */
8452         if (strcmp(r, "Black resigns") == 0) {
8453             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8454             return;
8455         }
8456         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8457         return;
8458     } else if (strncmp(message, "1/2", 3) == 0) {
8459         char *p, *q, *r = "";
8460         p = strchr(message, '{');
8461         if (p) {
8462             q = strchr(p, '}');
8463             if (q) {
8464                 *q = NULLCHAR;
8465                 r = p + 1;
8466             }
8467         }
8468
8469         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8470         return;
8471
8472     } else if (strncmp(message, "White resign", 12) == 0) {
8473         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8474         return;
8475     } else if (strncmp(message, "Black resign", 12) == 0) {
8476         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strncmp(message, "White matches", 13) == 0 ||
8479                strncmp(message, "Black matches", 13) == 0   ) {
8480         /* [HGM] ignore GNUShogi noises */
8481         return;
8482     } else if (strncmp(message, "White", 5) == 0 &&
8483                message[5] != '(' &&
8484                StrStr(message, "Black") == NULL) {
8485         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8486         return;
8487     } else if (strncmp(message, "Black", 5) == 0 &&
8488                message[5] != '(') {
8489         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8490         return;
8491     } else if (strcmp(message, "resign") == 0 ||
8492                strcmp(message, "computer resigns") == 0) {
8493         switch (gameMode) {
8494           case MachinePlaysBlack:
8495           case IcsPlayingBlack:
8496             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8497             break;
8498           case MachinePlaysWhite:
8499           case IcsPlayingWhite:
8500             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8501             break;
8502           case TwoMachinesPlay:
8503             if (cps->twoMachinesColor[0] == 'w')
8504               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8505             else
8506               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8507             break;
8508           default:
8509             /* can't happen */
8510             break;
8511         }
8512         return;
8513     } else if (strncmp(message, "opponent mates", 14) == 0) {
8514         switch (gameMode) {
8515           case MachinePlaysBlack:
8516           case IcsPlayingBlack:
8517             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8518             break;
8519           case MachinePlaysWhite:
8520           case IcsPlayingWhite:
8521             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8522             break;
8523           case TwoMachinesPlay:
8524             if (cps->twoMachinesColor[0] == 'w')
8525               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526             else
8527               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8528             break;
8529           default:
8530             /* can't happen */
8531             break;
8532         }
8533         return;
8534     } else if (strncmp(message, "computer mates", 14) == 0) {
8535         switch (gameMode) {
8536           case MachinePlaysBlack:
8537           case IcsPlayingBlack:
8538             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8539             break;
8540           case MachinePlaysWhite:
8541           case IcsPlayingWhite:
8542             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8543             break;
8544           case TwoMachinesPlay:
8545             if (cps->twoMachinesColor[0] == 'w')
8546               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8547             else
8548               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8549             break;
8550           default:
8551             /* can't happen */
8552             break;
8553         }
8554         return;
8555     } else if (strncmp(message, "checkmate", 9) == 0) {
8556         if (WhiteOnMove(forwardMostMove)) {
8557             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8558         } else {
8559             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8560         }
8561         return;
8562     } else if (strstr(message, "Draw") != NULL ||
8563                strstr(message, "game is a draw") != NULL) {
8564         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8565         return;
8566     } else if (strstr(message, "offer") != NULL &&
8567                strstr(message, "draw") != NULL) {
8568 #if ZIPPY
8569         if (appData.zippyPlay && first.initDone) {
8570             /* Relay offer to ICS */
8571             SendToICS(ics_prefix);
8572             SendToICS("draw\n");
8573         }
8574 #endif
8575         cps->offeredDraw = 2; /* valid until this engine moves twice */
8576         if (gameMode == TwoMachinesPlay) {
8577             if (cps->other->offeredDraw) {
8578                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579             /* [HGM] in two-machine mode we delay relaying draw offer      */
8580             /* until after we also have move, to see if it is really claim */
8581             }
8582         } else if (gameMode == MachinePlaysWhite ||
8583                    gameMode == MachinePlaysBlack) {
8584           if (userOfferedDraw) {
8585             DisplayInformation(_("Machine accepts your draw offer"));
8586             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8587           } else {
8588             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8589           }
8590         }
8591     }
8592
8593
8594     /*
8595      * Look for thinking output
8596      */
8597     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8598           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8599                                 ) {
8600         int plylev, mvleft, mvtot, curscore, time;
8601         char mvname[MOVE_LEN];
8602         u64 nodes; // [DM]
8603         char plyext;
8604         int ignore = FALSE;
8605         int prefixHint = FALSE;
8606         mvname[0] = NULLCHAR;
8607
8608         switch (gameMode) {
8609           case MachinePlaysBlack:
8610           case IcsPlayingBlack:
8611             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8612             break;
8613           case MachinePlaysWhite:
8614           case IcsPlayingWhite:
8615             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8616             break;
8617           case AnalyzeMode:
8618           case AnalyzeFile:
8619             break;
8620           case IcsObserving: /* [DM] icsEngineAnalyze */
8621             if (!appData.icsEngineAnalyze) ignore = TRUE;
8622             break;
8623           case TwoMachinesPlay:
8624             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8625                 ignore = TRUE;
8626             }
8627             break;
8628           default:
8629             ignore = TRUE;
8630             break;
8631         }
8632
8633         if (!ignore) {
8634             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8635             buf1[0] = NULLCHAR;
8636             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8637                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8638
8639                 if (plyext != ' ' && plyext != '\t') {
8640                     time *= 100;
8641                 }
8642
8643                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8644                 if( cps->scoreIsAbsolute &&
8645                     ( gameMode == MachinePlaysBlack ||
8646                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8647                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8648                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8649                      !WhiteOnMove(currentMove)
8650                     ) )
8651                 {
8652                     curscore = -curscore;
8653                 }
8654
8655                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8656
8657                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8658                         char buf[MSG_SIZ];
8659                         FILE *f;
8660                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8661                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8662                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8663                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8664                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8665                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8666                                 fclose(f);
8667                         } else DisplayError(_("failed writing PV"), 0);
8668                 }
8669
8670                 tempStats.depth = plylev;
8671                 tempStats.nodes = nodes;
8672                 tempStats.time = time;
8673                 tempStats.score = curscore;
8674                 tempStats.got_only_move = 0;
8675
8676                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8677                         int ticklen;
8678
8679                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8680                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8681                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8682                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8683                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8684                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8685                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8686                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8687                 }
8688
8689                 /* Buffer overflow protection */
8690                 if (pv[0] != NULLCHAR) {
8691                     if (strlen(pv) >= sizeof(tempStats.movelist)
8692                         && appData.debugMode) {
8693                         fprintf(debugFP,
8694                                 "PV is too long; using the first %u bytes.\n",
8695                                 (unsigned) sizeof(tempStats.movelist) - 1);
8696                     }
8697
8698                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8699                 } else {
8700                     sprintf(tempStats.movelist, " no PV\n");
8701                 }
8702
8703                 if (tempStats.seen_stat) {
8704                     tempStats.ok_to_send = 1;
8705                 }
8706
8707                 if (strchr(tempStats.movelist, '(') != NULL) {
8708                     tempStats.line_is_book = 1;
8709                     tempStats.nr_moves = 0;
8710                     tempStats.moves_left = 0;
8711                 } else {
8712                     tempStats.line_is_book = 0;
8713                 }
8714
8715                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8716                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8717
8718                 SendProgramStatsToFrontend( cps, &tempStats );
8719
8720                 /*
8721                     [AS] Protect the thinkOutput buffer from overflow... this
8722                     is only useful if buf1 hasn't overflowed first!
8723                 */
8724                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8725                          plylev,
8726                          (gameMode == TwoMachinesPlay ?
8727                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8728                          ((double) curscore) / 100.0,
8729                          prefixHint ? lastHint : "",
8730                          prefixHint ? " " : "" );
8731
8732                 if( buf1[0] != NULLCHAR ) {
8733                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8734
8735                     if( strlen(pv) > max_len ) {
8736                         if( appData.debugMode) {
8737                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8738                         }
8739                         pv[max_len+1] = '\0';
8740                     }
8741
8742                     strcat( thinkOutput, pv);
8743                 }
8744
8745                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8746                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8747                     DisplayMove(currentMove - 1);
8748                 }
8749                 return;
8750
8751             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8752                 /* crafty (9.25+) says "(only move) <move>"
8753                  * if there is only 1 legal move
8754                  */
8755                 sscanf(p, "(only move) %s", buf1);
8756                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8757                 sprintf(programStats.movelist, "%s (only move)", buf1);
8758                 programStats.depth = 1;
8759                 programStats.nr_moves = 1;
8760                 programStats.moves_left = 1;
8761                 programStats.nodes = 1;
8762                 programStats.time = 1;
8763                 programStats.got_only_move = 1;
8764
8765                 /* Not really, but we also use this member to
8766                    mean "line isn't going to change" (Crafty
8767                    isn't searching, so stats won't change) */
8768                 programStats.line_is_book = 1;
8769
8770                 SendProgramStatsToFrontend( cps, &programStats );
8771
8772                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8773                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8774                     DisplayMove(currentMove - 1);
8775                 }
8776                 return;
8777             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8778                               &time, &nodes, &plylev, &mvleft,
8779                               &mvtot, mvname) >= 5) {
8780                 /* The stat01: line is from Crafty (9.29+) in response
8781                    to the "." command */
8782                 programStats.seen_stat = 1;
8783                 cps->maybeThinking = TRUE;
8784
8785                 if (programStats.got_only_move || !appData.periodicUpdates)
8786                   return;
8787
8788                 programStats.depth = plylev;
8789                 programStats.time = time;
8790                 programStats.nodes = nodes;
8791                 programStats.moves_left = mvleft;
8792                 programStats.nr_moves = mvtot;
8793                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8794                 programStats.ok_to_send = 1;
8795                 programStats.movelist[0] = '\0';
8796
8797                 SendProgramStatsToFrontend( cps, &programStats );
8798
8799                 return;
8800
8801             } else if (strncmp(message,"++",2) == 0) {
8802                 /* Crafty 9.29+ outputs this */
8803                 programStats.got_fail = 2;
8804                 return;
8805
8806             } else if (strncmp(message,"--",2) == 0) {
8807                 /* Crafty 9.29+ outputs this */
8808                 programStats.got_fail = 1;
8809                 return;
8810
8811             } else if (thinkOutput[0] != NULLCHAR &&
8812                        strncmp(message, "    ", 4) == 0) {
8813                 unsigned message_len;
8814
8815                 p = message;
8816                 while (*p && *p == ' ') p++;
8817
8818                 message_len = strlen( p );
8819
8820                 /* [AS] Avoid buffer overflow */
8821                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8822                     strcat(thinkOutput, " ");
8823                     strcat(thinkOutput, p);
8824                 }
8825
8826                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8827                     strcat(programStats.movelist, " ");
8828                     strcat(programStats.movelist, p);
8829                 }
8830
8831                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8832                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8833                     DisplayMove(currentMove - 1);
8834                 }
8835                 return;
8836             }
8837         }
8838         else {
8839             buf1[0] = NULLCHAR;
8840
8841             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8842                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8843             {
8844                 ChessProgramStats cpstats;
8845
8846                 if (plyext != ' ' && plyext != '\t') {
8847                     time *= 100;
8848                 }
8849
8850                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8851                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8852                     curscore = -curscore;
8853                 }
8854
8855                 cpstats.depth = plylev;
8856                 cpstats.nodes = nodes;
8857                 cpstats.time = time;
8858                 cpstats.score = curscore;
8859                 cpstats.got_only_move = 0;
8860                 cpstats.movelist[0] = '\0';
8861
8862                 if (buf1[0] != NULLCHAR) {
8863                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8864                 }
8865
8866                 cpstats.ok_to_send = 0;
8867                 cpstats.line_is_book = 0;
8868                 cpstats.nr_moves = 0;
8869                 cpstats.moves_left = 0;
8870
8871                 SendProgramStatsToFrontend( cps, &cpstats );
8872             }
8873         }
8874     }
8875 }
8876
8877
8878 /* Parse a game score from the character string "game", and
8879    record it as the history of the current game.  The game
8880    score is NOT assumed to start from the standard position.
8881    The display is not updated in any way.
8882    */
8883 void
8884 ParseGameHistory (char *game)
8885 {
8886     ChessMove moveType;
8887     int fromX, fromY, toX, toY, boardIndex;
8888     char promoChar;
8889     char *p, *q;
8890     char buf[MSG_SIZ];
8891
8892     if (appData.debugMode)
8893       fprintf(debugFP, "Parsing game history: %s\n", game);
8894
8895     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8896     gameInfo.site = StrSave(appData.icsHost);
8897     gameInfo.date = PGNDate();
8898     gameInfo.round = StrSave("-");
8899
8900     /* Parse out names of players */
8901     while (*game == ' ') game++;
8902     p = buf;
8903     while (*game != ' ') *p++ = *game++;
8904     *p = NULLCHAR;
8905     gameInfo.white = StrSave(buf);
8906     while (*game == ' ') game++;
8907     p = buf;
8908     while (*game != ' ' && *game != '\n') *p++ = *game++;
8909     *p = NULLCHAR;
8910     gameInfo.black = StrSave(buf);
8911
8912     /* Parse moves */
8913     boardIndex = blackPlaysFirst ? 1 : 0;
8914     yynewstr(game);
8915     for (;;) {
8916         yyboardindex = boardIndex;
8917         moveType = (ChessMove) Myylex();
8918         switch (moveType) {
8919           case IllegalMove:             /* maybe suicide chess, etc. */
8920   if (appData.debugMode) {
8921     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8922     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8923     setbuf(debugFP, NULL);
8924   }
8925           case WhitePromotion:
8926           case BlackPromotion:
8927           case WhiteNonPromotion:
8928           case BlackNonPromotion:
8929           case NormalMove:
8930           case WhiteCapturesEnPassant:
8931           case BlackCapturesEnPassant:
8932           case WhiteKingSideCastle:
8933           case WhiteQueenSideCastle:
8934           case BlackKingSideCastle:
8935           case BlackQueenSideCastle:
8936           case WhiteKingSideCastleWild:
8937           case WhiteQueenSideCastleWild:
8938           case BlackKingSideCastleWild:
8939           case BlackQueenSideCastleWild:
8940           /* PUSH Fabien */
8941           case WhiteHSideCastleFR:
8942           case WhiteASideCastleFR:
8943           case BlackHSideCastleFR:
8944           case BlackASideCastleFR:
8945           /* POP Fabien */
8946             fromX = currentMoveString[0] - AAA;
8947             fromY = currentMoveString[1] - ONE;
8948             toX = currentMoveString[2] - AAA;
8949             toY = currentMoveString[3] - ONE;
8950             promoChar = currentMoveString[4];
8951             break;
8952           case WhiteDrop:
8953           case BlackDrop:
8954             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8955             fromX = moveType == WhiteDrop ?
8956               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8957             (int) CharToPiece(ToLower(currentMoveString[0]));
8958             fromY = DROP_RANK;
8959             toX = currentMoveString[2] - AAA;
8960             toY = currentMoveString[3] - ONE;
8961             promoChar = NULLCHAR;
8962             break;
8963           case AmbiguousMove:
8964             /* bug? */
8965             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8966   if (appData.debugMode) {
8967     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8968     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8969     setbuf(debugFP, NULL);
8970   }
8971             DisplayError(buf, 0);
8972             return;
8973           case ImpossibleMove:
8974             /* bug? */
8975             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8976   if (appData.debugMode) {
8977     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8978     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8979     setbuf(debugFP, NULL);
8980   }
8981             DisplayError(buf, 0);
8982             return;
8983           case EndOfFile:
8984             if (boardIndex < backwardMostMove) {
8985                 /* Oops, gap.  How did that happen? */
8986                 DisplayError(_("Gap in move list"), 0);
8987                 return;
8988             }
8989             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8990             if (boardIndex > forwardMostMove) {
8991                 forwardMostMove = boardIndex;
8992             }
8993             return;
8994           case ElapsedTime:
8995             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8996                 strcat(parseList[boardIndex-1], " ");
8997                 strcat(parseList[boardIndex-1], yy_text);
8998             }
8999             continue;
9000           case Comment:
9001           case PGNTag:
9002           case NAG:
9003           default:
9004             /* ignore */
9005             continue;
9006           case WhiteWins:
9007           case BlackWins:
9008           case GameIsDrawn:
9009           case GameUnfinished:
9010             if (gameMode == IcsExamining) {
9011                 if (boardIndex < backwardMostMove) {
9012                     /* Oops, gap.  How did that happen? */
9013                     return;
9014                 }
9015                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9016                 return;
9017             }
9018             gameInfo.result = moveType;
9019             p = strchr(yy_text, '{');
9020             if (p == NULL) p = strchr(yy_text, '(');
9021             if (p == NULL) {
9022                 p = yy_text;
9023                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9024             } else {
9025                 q = strchr(p, *p == '{' ? '}' : ')');
9026                 if (q != NULL) *q = NULLCHAR;
9027                 p++;
9028             }
9029             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9030             gameInfo.resultDetails = StrSave(p);
9031             continue;
9032         }
9033         if (boardIndex >= forwardMostMove &&
9034             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9035             backwardMostMove = blackPlaysFirst ? 1 : 0;
9036             return;
9037         }
9038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9039                                  fromY, fromX, toY, toX, promoChar,
9040                                  parseList[boardIndex]);
9041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9042         /* currentMoveString is set as a side-effect of yylex */
9043         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9044         strcat(moveList[boardIndex], "\n");
9045         boardIndex++;
9046         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9047         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9048           case MT_NONE:
9049           case MT_STALEMATE:
9050           default:
9051             break;
9052           case MT_CHECK:
9053             if(gameInfo.variant != VariantShogi)
9054                 strcat(parseList[boardIndex - 1], "+");
9055             break;
9056           case MT_CHECKMATE:
9057           case MT_STAINMATE:
9058             strcat(parseList[boardIndex - 1], "#");
9059             break;
9060         }
9061     }
9062 }
9063
9064
9065 /* Apply a move to the given board  */
9066 void
9067 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9068 {
9069   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9070   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9071
9072     /* [HGM] compute & store e.p. status and castling rights for new position */
9073     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9074
9075       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9076       oldEP = (signed char)board[EP_STATUS];
9077       board[EP_STATUS] = EP_NONE;
9078
9079   if (fromY == DROP_RANK) {
9080         /* must be first */
9081         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9082             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9083             return;
9084         }
9085         piece = board[toY][toX] = (ChessSquare) fromX;
9086   } else {
9087       int i;
9088
9089       if( board[toY][toX] != EmptySquare )
9090            board[EP_STATUS] = EP_CAPTURE;
9091
9092       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9093            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9094                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9095       } else
9096       if( board[fromY][fromX] == WhitePawn ) {
9097            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9098                board[EP_STATUS] = EP_PAWN_MOVE;
9099            if( toY-fromY==2) {
9100                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9101                         gameInfo.variant != VariantBerolina || toX < fromX)
9102                       board[EP_STATUS] = toX | berolina;
9103                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9104                         gameInfo.variant != VariantBerolina || toX > fromX)
9105                       board[EP_STATUS] = toX;
9106            }
9107       } else
9108       if( board[fromY][fromX] == BlackPawn ) {
9109            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9110                board[EP_STATUS] = EP_PAWN_MOVE;
9111            if( toY-fromY== -2) {
9112                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9113                         gameInfo.variant != VariantBerolina || toX < fromX)
9114                       board[EP_STATUS] = toX | berolina;
9115                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9116                         gameInfo.variant != VariantBerolina || toX > fromX)
9117                       board[EP_STATUS] = toX;
9118            }
9119        }
9120
9121        for(i=0; i<nrCastlingRights; i++) {
9122            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9123               board[CASTLING][i] == toX   && castlingRank[i] == toY
9124              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9125        }
9126
9127      if (fromX == toX && fromY == toY) return;
9128
9129      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9130      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9131      if(gameInfo.variant == VariantKnightmate)
9132          king += (int) WhiteUnicorn - (int) WhiteKing;
9133
9134     /* Code added by Tord: */
9135     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9136     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9137         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9138       board[fromY][fromX] = EmptySquare;
9139       board[toY][toX] = EmptySquare;
9140       if((toX > fromX) != (piece == WhiteRook)) {
9141         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9142       } else {
9143         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9144       }
9145     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9146                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9147       board[fromY][fromX] = EmptySquare;
9148       board[toY][toX] = EmptySquare;
9149       if((toX > fromX) != (piece == BlackRook)) {
9150         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9151       } else {
9152         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9153       }
9154     /* End of code added by Tord */
9155
9156     } else if (board[fromY][fromX] == king
9157         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9158         && toY == fromY && toX > fromX+1) {
9159         board[fromY][fromX] = EmptySquare;
9160         board[toY][toX] = king;
9161         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9162         board[fromY][BOARD_RGHT-1] = EmptySquare;
9163     } else if (board[fromY][fromX] == king
9164         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9165                && toY == fromY && toX < fromX-1) {
9166         board[fromY][fromX] = EmptySquare;
9167         board[toY][toX] = king;
9168         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9169         board[fromY][BOARD_LEFT] = EmptySquare;
9170     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9171                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9172                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9173                ) {
9174         /* white pawn promotion */
9175         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9176         if(gameInfo.variant==VariantBughouse ||
9177            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9178             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9179         board[fromY][fromX] = EmptySquare;
9180     } else if ((fromY >= BOARD_HEIGHT>>1)
9181                && (toX != fromX)
9182                && gameInfo.variant != VariantXiangqi
9183                && gameInfo.variant != VariantBerolina
9184                && (board[fromY][fromX] == WhitePawn)
9185                && (board[toY][toX] == EmptySquare)) {
9186         board[fromY][fromX] = EmptySquare;
9187         board[toY][toX] = WhitePawn;
9188         captured = board[toY - 1][toX];
9189         board[toY - 1][toX] = EmptySquare;
9190     } else if ((fromY == BOARD_HEIGHT-4)
9191                && (toX == fromX)
9192                && gameInfo.variant == VariantBerolina
9193                && (board[fromY][fromX] == WhitePawn)
9194                && (board[toY][toX] == EmptySquare)) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = WhitePawn;
9197         if(oldEP & EP_BEROLIN_A) {
9198                 captured = board[fromY][fromX-1];
9199                 board[fromY][fromX-1] = EmptySquare;
9200         }else{  captured = board[fromY][fromX+1];
9201                 board[fromY][fromX+1] = EmptySquare;
9202         }
9203     } else if (board[fromY][fromX] == king
9204         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9205                && toY == fromY && toX > fromX+1) {
9206         board[fromY][fromX] = EmptySquare;
9207         board[toY][toX] = king;
9208         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9209         board[fromY][BOARD_RGHT-1] = EmptySquare;
9210     } else if (board[fromY][fromX] == king
9211         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9212                && toY == fromY && toX < fromX-1) {
9213         board[fromY][fromX] = EmptySquare;
9214         board[toY][toX] = king;
9215         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9216         board[fromY][BOARD_LEFT] = EmptySquare;
9217     } else if (fromY == 7 && fromX == 3
9218                && board[fromY][fromX] == BlackKing
9219                && toY == 7 && toX == 5) {
9220         board[fromY][fromX] = EmptySquare;
9221         board[toY][toX] = BlackKing;
9222         board[fromY][7] = EmptySquare;
9223         board[toY][4] = BlackRook;
9224     } else if (fromY == 7 && fromX == 3
9225                && board[fromY][fromX] == BlackKing
9226                && toY == 7 && toX == 1) {
9227         board[fromY][fromX] = EmptySquare;
9228         board[toY][toX] = BlackKing;
9229         board[fromY][0] = EmptySquare;
9230         board[toY][2] = BlackRook;
9231     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9232                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9233                && toY < promoRank && promoChar
9234                ) {
9235         /* black pawn promotion */
9236         board[toY][toX] = CharToPiece(ToLower(promoChar));
9237         if(gameInfo.variant==VariantBughouse ||
9238            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9239             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9240         board[fromY][fromX] = EmptySquare;
9241     } else if ((fromY < BOARD_HEIGHT>>1)
9242                && (toX != fromX)
9243                && gameInfo.variant != VariantXiangqi
9244                && gameInfo.variant != VariantBerolina
9245                && (board[fromY][fromX] == BlackPawn)
9246                && (board[toY][toX] == EmptySquare)) {
9247         board[fromY][fromX] = EmptySquare;
9248         board[toY][toX] = BlackPawn;
9249         captured = board[toY + 1][toX];
9250         board[toY + 1][toX] = EmptySquare;
9251     } else if ((fromY == 3)
9252                && (toX == fromX)
9253                && gameInfo.variant == VariantBerolina
9254                && (board[fromY][fromX] == BlackPawn)
9255                && (board[toY][toX] == EmptySquare)) {
9256         board[fromY][fromX] = EmptySquare;
9257         board[toY][toX] = BlackPawn;
9258         if(oldEP & EP_BEROLIN_A) {
9259                 captured = board[fromY][fromX-1];
9260                 board[fromY][fromX-1] = EmptySquare;
9261         }else{  captured = board[fromY][fromX+1];
9262                 board[fromY][fromX+1] = EmptySquare;
9263         }
9264     } else {
9265         board[toY][toX] = board[fromY][fromX];
9266         board[fromY][fromX] = EmptySquare;
9267     }
9268   }
9269
9270     if (gameInfo.holdingsWidth != 0) {
9271
9272       /* !!A lot more code needs to be written to support holdings  */
9273       /* [HGM] OK, so I have written it. Holdings are stored in the */
9274       /* penultimate board files, so they are automaticlly stored   */
9275       /* in the game history.                                       */
9276       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9277                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9278         /* Delete from holdings, by decreasing count */
9279         /* and erasing image if necessary            */
9280         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9281         if(p < (int) BlackPawn) { /* white drop */
9282              p -= (int)WhitePawn;
9283                  p = PieceToNumber((ChessSquare)p);
9284              if(p >= gameInfo.holdingsSize) p = 0;
9285              if(--board[p][BOARD_WIDTH-2] <= 0)
9286                   board[p][BOARD_WIDTH-1] = EmptySquare;
9287              if((int)board[p][BOARD_WIDTH-2] < 0)
9288                         board[p][BOARD_WIDTH-2] = 0;
9289         } else {                  /* black drop */
9290              p -= (int)BlackPawn;
9291                  p = PieceToNumber((ChessSquare)p);
9292              if(p >= gameInfo.holdingsSize) p = 0;
9293              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9294                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9295              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9296                         board[BOARD_HEIGHT-1-p][1] = 0;
9297         }
9298       }
9299       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9300           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9301         /* [HGM] holdings: Add to holdings, if holdings exist */
9302         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9303                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9304                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9305         }
9306         p = (int) captured;
9307         if (p >= (int) BlackPawn) {
9308           p -= (int)BlackPawn;
9309           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9310                   /* in Shogi restore piece to its original  first */
9311                   captured = (ChessSquare) (DEMOTED captured);
9312                   p = DEMOTED p;
9313           }
9314           p = PieceToNumber((ChessSquare)p);
9315           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9316           board[p][BOARD_WIDTH-2]++;
9317           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9318         } else {
9319           p -= (int)WhitePawn;
9320           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9321                   captured = (ChessSquare) (DEMOTED captured);
9322                   p = DEMOTED p;
9323           }
9324           p = PieceToNumber((ChessSquare)p);
9325           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9326           board[BOARD_HEIGHT-1-p][1]++;
9327           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9328         }
9329       }
9330     } else if (gameInfo.variant == VariantAtomic) {
9331       if (captured != EmptySquare) {
9332         int y, x;
9333         for (y = toY-1; y <= toY+1; y++) {
9334           for (x = toX-1; x <= toX+1; x++) {
9335             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9336                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9337               board[y][x] = EmptySquare;
9338             }
9339           }
9340         }
9341         board[toY][toX] = EmptySquare;
9342       }
9343     }
9344     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9345         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9346     } else
9347     if(promoChar == '+') {
9348         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9349         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9350     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9351         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9352     }
9353     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9354                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9355         // [HGM] superchess: take promotion piece out of holdings
9356         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9357         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9358             if(!--board[k][BOARD_WIDTH-2])
9359                 board[k][BOARD_WIDTH-1] = EmptySquare;
9360         } else {
9361             if(!--board[BOARD_HEIGHT-1-k][1])
9362                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9363         }
9364     }
9365
9366 }
9367
9368 /* Updates forwardMostMove */
9369 void
9370 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9371 {
9372 //    forwardMostMove++; // [HGM] bare: moved downstream
9373
9374     (void) CoordsToAlgebraic(boards[forwardMostMove],
9375                              PosFlags(forwardMostMove),
9376                              fromY, fromX, toY, toX, promoChar,
9377                              parseList[forwardMostMove]);
9378
9379     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9380         int timeLeft; static int lastLoadFlag=0; int king, piece;
9381         piece = boards[forwardMostMove][fromY][fromX];
9382         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9383         if(gameInfo.variant == VariantKnightmate)
9384             king += (int) WhiteUnicorn - (int) WhiteKing;
9385         if(forwardMostMove == 0) {
9386             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9387                 fprintf(serverMoves, "%s;", UserName());
9388             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9389                 fprintf(serverMoves, "%s;", second.tidy);
9390             fprintf(serverMoves, "%s;", first.tidy);
9391             if(gameMode == MachinePlaysWhite)
9392                 fprintf(serverMoves, "%s;", UserName());
9393             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9394                 fprintf(serverMoves, "%s;", second.tidy);
9395         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9396         lastLoadFlag = loadFlag;
9397         // print base move
9398         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9399         // print castling suffix
9400         if( toY == fromY && piece == king ) {
9401             if(toX-fromX > 1)
9402                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9403             if(fromX-toX >1)
9404                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9405         }
9406         // e.p. suffix
9407         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9408              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9409              boards[forwardMostMove][toY][toX] == EmptySquare
9410              && fromX != toX && fromY != toY)
9411                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9412         // promotion suffix
9413         if(promoChar != NULLCHAR)
9414                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9415         if(!loadFlag) {
9416                 char buf[MOVE_LEN*2], *p; int len;
9417             fprintf(serverMoves, "/%d/%d",
9418                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9419             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9420             else                      timeLeft = blackTimeRemaining/1000;
9421             fprintf(serverMoves, "/%d", timeLeft);
9422                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9423                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9424                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9425             fprintf(serverMoves, "/%s", buf);
9426         }
9427         fflush(serverMoves);
9428     }
9429
9430     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9431         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9432       return;
9433     }
9434     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9435     if (commentList[forwardMostMove+1] != NULL) {
9436         free(commentList[forwardMostMove+1]);
9437         commentList[forwardMostMove+1] = NULL;
9438     }
9439     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9441     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9442     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9443     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9444     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9445     adjustedClock = FALSE;
9446     gameInfo.result = GameUnfinished;
9447     if (gameInfo.resultDetails != NULL) {
9448         free(gameInfo.resultDetails);
9449         gameInfo.resultDetails = NULL;
9450     }
9451     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9452                               moveList[forwardMostMove - 1]);
9453     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9454       case MT_NONE:
9455       case MT_STALEMATE:
9456       default:
9457         break;
9458       case MT_CHECK:
9459         if(gameInfo.variant != VariantShogi)
9460             strcat(parseList[forwardMostMove - 1], "+");
9461         break;
9462       case MT_CHECKMATE:
9463       case MT_STAINMATE:
9464         strcat(parseList[forwardMostMove - 1], "#");
9465         break;
9466     }
9467
9468 }
9469
9470 /* Updates currentMove if not pausing */
9471 void
9472 ShowMove (int fromX, int fromY, int toX, int toY)
9473 {
9474     int instant = (gameMode == PlayFromGameFile) ?
9475         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9476     if(appData.noGUI) return;
9477     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9478         if (!instant) {
9479             if (forwardMostMove == currentMove + 1) {
9480                 AnimateMove(boards[forwardMostMove - 1],
9481                             fromX, fromY, toX, toY);
9482             }
9483             if (appData.highlightLastMove) {
9484                 SetHighlights(fromX, fromY, toX, toY);
9485             }
9486         }
9487         currentMove = forwardMostMove;
9488     }
9489
9490     if (instant) return;
9491
9492     DisplayMove(currentMove - 1);
9493     DrawPosition(FALSE, boards[currentMove]);
9494     DisplayBothClocks();
9495     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9496 }
9497
9498 void
9499 SendEgtPath (ChessProgramState *cps)
9500 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9501         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9502
9503         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9504
9505         while(*p) {
9506             char c, *q = name+1, *r, *s;
9507
9508             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9509             while(*p && *p != ',') *q++ = *p++;
9510             *q++ = ':'; *q = 0;
9511             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9512                 strcmp(name, ",nalimov:") == 0 ) {
9513                 // take nalimov path from the menu-changeable option first, if it is defined
9514               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9515                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9516             } else
9517             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9518                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9519                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9520                 s = r = StrStr(s, ":") + 1; // beginning of path info
9521                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9522                 c = *r; *r = 0;             // temporarily null-terminate path info
9523                     *--q = 0;               // strip of trailig ':' from name
9524                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9525                 *r = c;
9526                 SendToProgram(buf,cps);     // send egtbpath command for this format
9527             }
9528             if(*p == ',') p++; // read away comma to position for next format name
9529         }
9530 }
9531
9532 void
9533 InitChessProgram (ChessProgramState *cps, int setup)
9534 /* setup needed to setup FRC opening position */
9535 {
9536     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9537     if (appData.noChessProgram) return;
9538     hintRequested = FALSE;
9539     bookRequested = FALSE;
9540
9541     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9542     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9543     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9544     if(cps->memSize) { /* [HGM] memory */
9545       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9546         SendToProgram(buf, cps);
9547     }
9548     SendEgtPath(cps); /* [HGM] EGT */
9549     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9550       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9551         SendToProgram(buf, cps);
9552     }
9553
9554     SendToProgram(cps->initString, cps);
9555     if (gameInfo.variant != VariantNormal &&
9556         gameInfo.variant != VariantLoadable
9557         /* [HGM] also send variant if board size non-standard */
9558         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9559                                             ) {
9560       char *v = VariantName(gameInfo.variant);
9561       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9562         /* [HGM] in protocol 1 we have to assume all variants valid */
9563         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9564         DisplayFatalError(buf, 0, 1);
9565         return;
9566       }
9567
9568       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9569       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9570       if( gameInfo.variant == VariantXiangqi )
9571            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9572       if( gameInfo.variant == VariantShogi )
9573            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9574       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9575            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9576       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9577           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9578            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9579       if( gameInfo.variant == VariantCourier )
9580            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9581       if( gameInfo.variant == VariantSuper )
9582            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9583       if( gameInfo.variant == VariantGreat )
9584            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9585       if( gameInfo.variant == VariantSChess )
9586            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9587       if( gameInfo.variant == VariantGrand )
9588            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9589
9590       if(overruled) {
9591         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9592                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9593            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9594            if(StrStr(cps->variants, b) == NULL) {
9595                // specific sized variant not known, check if general sizing allowed
9596                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9597                    if(StrStr(cps->variants, "boardsize") == NULL) {
9598                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9599                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9600                        DisplayFatalError(buf, 0, 1);
9601                        return;
9602                    }
9603                    /* [HGM] here we really should compare with the maximum supported board size */
9604                }
9605            }
9606       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9607       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9608       SendToProgram(buf, cps);
9609     }
9610     currentlyInitializedVariant = gameInfo.variant;
9611
9612     /* [HGM] send opening position in FRC to first engine */
9613     if(setup) {
9614           SendToProgram("force\n", cps);
9615           SendBoard(cps, 0);
9616           /* engine is now in force mode! Set flag to wake it up after first move. */
9617           setboardSpoiledMachineBlack = 1;
9618     }
9619
9620     if (cps->sendICS) {
9621       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9622       SendToProgram(buf, cps);
9623     }
9624     cps->maybeThinking = FALSE;
9625     cps->offeredDraw = 0;
9626     if (!appData.icsActive) {
9627         SendTimeControl(cps, movesPerSession, timeControl,
9628                         timeIncrement, appData.searchDepth,
9629                         searchTime);
9630     }
9631     if (appData.showThinking
9632         // [HGM] thinking: four options require thinking output to be sent
9633         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9634                                 ) {
9635         SendToProgram("post\n", cps);
9636     }
9637     SendToProgram("hard\n", cps);
9638     if (!appData.ponderNextMove) {
9639         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9640            it without being sure what state we are in first.  "hard"
9641            is not a toggle, so that one is OK.
9642          */
9643         SendToProgram("easy\n", cps);
9644     }
9645     if (cps->usePing) {
9646       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9647       SendToProgram(buf, cps);
9648     }
9649     cps->initDone = TRUE;
9650     ClearEngineOutputPane(cps == &second);
9651 }
9652
9653
9654 void
9655 StartChessProgram (ChessProgramState *cps)
9656 {
9657     char buf[MSG_SIZ];
9658     int err;
9659
9660     if (appData.noChessProgram) return;
9661     cps->initDone = FALSE;
9662
9663     if (strcmp(cps->host, "localhost") == 0) {
9664         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9665     } else if (*appData.remoteShell == NULLCHAR) {
9666         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9667     } else {
9668         if (*appData.remoteUser == NULLCHAR) {
9669           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9670                     cps->program);
9671         } else {
9672           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9673                     cps->host, appData.remoteUser, cps->program);
9674         }
9675         err = StartChildProcess(buf, "", &cps->pr);
9676     }
9677
9678     if (err != 0) {
9679       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9680         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9681         if(cps != &first) return;
9682         appData.noChessProgram = TRUE;
9683         ThawUI();
9684         SetNCPMode();
9685 //      DisplayFatalError(buf, err, 1);
9686 //      cps->pr = NoProc;
9687 //      cps->isr = NULL;
9688         return;
9689     }
9690
9691     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9692     if (cps->protocolVersion > 1) {
9693       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9694       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9695       cps->comboCnt = 0;  //                and values of combo boxes
9696       SendToProgram(buf, cps);
9697     } else {
9698       SendToProgram("xboard\n", cps);
9699     }
9700 }
9701
9702 void
9703 TwoMachinesEventIfReady P((void))
9704 {
9705   static int curMess = 0;
9706   if (first.lastPing != first.lastPong) {
9707     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9708     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9709     return;
9710   }
9711   if (second.lastPing != second.lastPong) {
9712     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9713     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9714     return;
9715   }
9716   DisplayMessage("", ""); curMess = 0;
9717   ThawUI();
9718   TwoMachinesEvent();
9719 }
9720
9721 char *
9722 MakeName (char *template)
9723 {
9724     time_t clock;
9725     struct tm *tm;
9726     static char buf[MSG_SIZ];
9727     char *p = buf;
9728     int i;
9729
9730     clock = time((time_t *)NULL);
9731     tm = localtime(&clock);
9732
9733     while(*p++ = *template++) if(p[-1] == '%') {
9734         switch(*template++) {
9735           case 0:   *p = 0; return buf;
9736           case 'Y': i = tm->tm_year+1900; break;
9737           case 'y': i = tm->tm_year-100; break;
9738           case 'M': i = tm->tm_mon+1; break;
9739           case 'd': i = tm->tm_mday; break;
9740           case 'h': i = tm->tm_hour; break;
9741           case 'm': i = tm->tm_min; break;
9742           case 's': i = tm->tm_sec; break;
9743           default:  i = 0;
9744         }
9745         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9746     }
9747     return buf;
9748 }
9749
9750 int
9751 CountPlayers (char *p)
9752 {
9753     int n = 0;
9754     while(p = strchr(p, '\n')) p++, n++; // count participants
9755     return n;
9756 }
9757
9758 FILE *
9759 WriteTourneyFile (char *results, FILE *f)
9760 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9761     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9762     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9763         // create a file with tournament description
9764         fprintf(f, "-participants {%s}\n", appData.participants);
9765         fprintf(f, "-seedBase %d\n", appData.seedBase);
9766         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9767         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9768         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9769         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9770         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9771         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9772         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9773         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9774         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9775         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9776         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9777         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9778         if(searchTime > 0)
9779                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9780         else {
9781                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9782                 fprintf(f, "-tc %s\n", appData.timeControl);
9783                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9784         }
9785         fprintf(f, "-results \"%s\"\n", results);
9786     }
9787     return f;
9788 }
9789
9790 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9791
9792 void
9793 Substitute (char *participants, int expunge)
9794 {
9795     int i, changed, changes=0, nPlayers=0;
9796     char *p, *q, *r, buf[MSG_SIZ];
9797     if(participants == NULL) return;
9798     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9799     r = p = participants; q = appData.participants;
9800     while(*p && *p == *q) {
9801         if(*p == '\n') r = p+1, nPlayers++;
9802         p++; q++;
9803     }
9804     if(*p) { // difference
9805         while(*p && *p++ != '\n');
9806         while(*q && *q++ != '\n');
9807       changed = nPlayers;
9808         changes = 1 + (strcmp(p, q) != 0);
9809     }
9810     if(changes == 1) { // a single engine mnemonic was changed
9811         q = r; while(*q) nPlayers += (*q++ == '\n');
9812         p = buf; while(*r && (*p = *r++) != '\n') p++;
9813         *p = NULLCHAR;
9814         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9815         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9816         if(mnemonic[i]) { // The substitute is valid
9817             FILE *f;
9818             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9819                 flock(fileno(f), LOCK_EX);
9820                 ParseArgsFromFile(f);
9821                 fseek(f, 0, SEEK_SET);
9822                 FREE(appData.participants); appData.participants = participants;
9823                 if(expunge) { // erase results of replaced engine
9824                     int len = strlen(appData.results), w, b, dummy;
9825                     for(i=0; i<len; i++) {
9826                         Pairing(i, nPlayers, &w, &b, &dummy);
9827                         if((w == changed || b == changed) && appData.results[i] == '*') {
9828                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9829                             fclose(f);
9830                             return;
9831                         }
9832                     }
9833                     for(i=0; i<len; i++) {
9834                         Pairing(i, nPlayers, &w, &b, &dummy);
9835                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9836                     }
9837                 }
9838                 WriteTourneyFile(appData.results, f);
9839                 fclose(f); // release lock
9840                 return;
9841             }
9842         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9843     }
9844     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9845     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9846     free(participants);
9847     return;
9848 }
9849
9850 int
9851 CreateTourney (char *name)
9852 {
9853         FILE *f;
9854         if(matchMode && strcmp(name, appData.tourneyFile)) {
9855              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9856         }
9857         if(name[0] == NULLCHAR) {
9858             if(appData.participants[0])
9859                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9860             return 0;
9861         }
9862         f = fopen(name, "r");
9863         if(f) { // file exists
9864             ASSIGN(appData.tourneyFile, name);
9865             ParseArgsFromFile(f); // parse it
9866         } else {
9867             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9868             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9869                 DisplayError(_("Not enough participants"), 0);
9870                 return 0;
9871             }
9872             ASSIGN(appData.tourneyFile, name);
9873             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9874             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9875         }
9876         fclose(f);
9877         appData.noChessProgram = FALSE;
9878         appData.clockMode = TRUE;
9879         SetGNUMode();
9880         return 1;
9881 }
9882
9883 int
9884 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9885 {
9886     char buf[MSG_SIZ], *p, *q;
9887     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9888     skip = !all && group[0]; // if group requested, we start in skip mode
9889     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9890         p = names; q = buf; header = 0;
9891         while(*p && *p != '\n') *q++ = *p++;
9892         *q = 0;
9893         if(*p == '\n') p++;
9894         if(buf[0] == '#') {
9895             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9896             depth++; // we must be entering a new group
9897             if(all) continue; // suppress printing group headers when complete list requested
9898             header = 1;
9899             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9900         }
9901         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9902         if(engineList[i]) free(engineList[i]);
9903         engineList[i] = strdup(buf);
9904         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9905         if(engineMnemonic[i]) free(engineMnemonic[i]);
9906         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9907             strcat(buf, " (");
9908             sscanf(q + 8, "%s", buf + strlen(buf));
9909             strcat(buf, ")");
9910         }
9911         engineMnemonic[i] = strdup(buf);
9912         i++;
9913     }
9914     engineList[i] = engineMnemonic[i] = NULL;
9915     return i;
9916 }
9917
9918 // following implemented as macro to avoid type limitations
9919 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9920
9921 void
9922 SwapEngines (int n)
9923 {   // swap settings for first engine and other engine (so far only some selected options)
9924     int h;
9925     char *p;
9926     if(n == 0) return;
9927     SWAP(directory, p)
9928     SWAP(chessProgram, p)
9929     SWAP(isUCI, h)
9930     SWAP(hasOwnBookUCI, h)
9931     SWAP(protocolVersion, h)
9932     SWAP(reuse, h)
9933     SWAP(scoreIsAbsolute, h)
9934     SWAP(timeOdds, h)
9935     SWAP(logo, p)
9936     SWAP(pgnName, p)
9937     SWAP(pvSAN, h)
9938     SWAP(engOptions, p)
9939 }
9940
9941 int
9942 SetPlayer (int player, char *p)
9943 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9944     int i;
9945     char buf[MSG_SIZ], *engineName;
9946     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9947     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9948     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9949     if(mnemonic[i]) {
9950         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9951         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9952         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9953         ParseArgsFromString(buf);
9954     }
9955     free(engineName);
9956     return i;
9957 }
9958
9959 char *recentEngines;
9960
9961 void
9962 RecentEngineEvent (int nr)
9963 {
9964     int n;
9965 //    SwapEngines(1); // bump first to second
9966 //    ReplaceEngine(&second, 1); // and load it there
9967     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9968     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9969     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9970         ReplaceEngine(&first, 0);
9971         FloatToFront(&appData.recentEngineList, command[n]);
9972     }
9973 }
9974
9975 int
9976 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9977 {   // determine players from game number
9978     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9979
9980     if(appData.tourneyType == 0) {
9981         roundsPerCycle = (nPlayers - 1) | 1;
9982         pairingsPerRound = nPlayers / 2;
9983     } else if(appData.tourneyType > 0) {
9984         roundsPerCycle = nPlayers - appData.tourneyType;
9985         pairingsPerRound = appData.tourneyType;
9986     }
9987     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9988     gamesPerCycle = gamesPerRound * roundsPerCycle;
9989     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9990     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9991     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9992     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9993     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9994     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9995
9996     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9997     if(appData.roundSync) *syncInterval = gamesPerRound;
9998
9999     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10000
10001     if(appData.tourneyType == 0) {
10002         if(curPairing == (nPlayers-1)/2 ) {
10003             *whitePlayer = curRound;
10004             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10005         } else {
10006             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10007             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10008             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10009             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10010         }
10011     } else if(appData.tourneyType > 1) {
10012         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10013         *whitePlayer = curRound + appData.tourneyType;
10014     } else if(appData.tourneyType > 0) {
10015         *whitePlayer = curPairing;
10016         *blackPlayer = curRound + appData.tourneyType;
10017     }
10018
10019     // take care of white/black alternation per round. 
10020     // For cycles and games this is already taken care of by default, derived from matchGame!
10021     return curRound & 1;
10022 }
10023
10024 int
10025 NextTourneyGame (int nr, int *swapColors)
10026 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10027     char *p, *q;
10028     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10029     FILE *tf;
10030     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10031     tf = fopen(appData.tourneyFile, "r");
10032     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10033     ParseArgsFromFile(tf); fclose(tf);
10034     InitTimeControls(); // TC might be altered from tourney file
10035
10036     nPlayers = CountPlayers(appData.participants); // count participants
10037     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10038     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10039
10040     if(syncInterval) {
10041         p = q = appData.results;
10042         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10043         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10044             DisplayMessage(_("Waiting for other game(s)"),"");
10045             waitingForGame = TRUE;
10046             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10047             return 0;
10048         }
10049         waitingForGame = FALSE;
10050     }
10051
10052     if(appData.tourneyType < 0) {
10053         if(nr>=0 && !pairingReceived) {
10054             char buf[1<<16];
10055             if(pairing.pr == NoProc) {
10056                 if(!appData.pairingEngine[0]) {
10057                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10058                     return 0;
10059                 }
10060                 StartChessProgram(&pairing); // starts the pairing engine
10061             }
10062             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10063             SendToProgram(buf, &pairing);
10064             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10065             SendToProgram(buf, &pairing);
10066             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10067         }
10068         pairingReceived = 0;                              // ... so we continue here 
10069         *swapColors = 0;
10070         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10071         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10072         matchGame = 1; roundNr = nr / syncInterval + 1;
10073     }
10074
10075     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10076
10077     // redefine engines, engine dir, etc.
10078     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10079     if(first.pr == NoProc) {
10080       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10081       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10082     }
10083     if(second.pr == NoProc) {
10084       SwapEngines(1);
10085       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10086       SwapEngines(1);         // and make that valid for second engine by swapping
10087       InitEngine(&second, 1);
10088     }
10089     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10090     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10091     return 1;
10092 }
10093
10094 void
10095 NextMatchGame ()
10096 {   // performs game initialization that does not invoke engines, and then tries to start the game
10097     int res, firstWhite, swapColors = 0;
10098     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10099     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
10100         char buf[MSG_SIZ];
10101         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10102         if(strcmp(buf, currentDebugFile)) { // name has changed
10103             FILE *f = fopen(buf, "w");
10104             if(f) { // if opening the new file failed, just keep using the old one
10105                 ASSIGN(currentDebugFile, buf);
10106                 fclose(debugFP);
10107                 debugFP = f;
10108             }
10109             if(appData.serverFileName) {
10110                 if(serverFP) fclose(serverFP);
10111                 serverFP = fopen(appData.serverFileName, "w");
10112                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10113                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10114             }
10115         }
10116     }
10117     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10118     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10119     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10120     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10121     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10122     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10123     Reset(FALSE, first.pr != NoProc);
10124     res = LoadGameOrPosition(matchGame); // setup game
10125     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10126     if(!res) return; // abort when bad game/pos file
10127     TwoMachinesEvent();
10128 }
10129
10130 void
10131 UserAdjudicationEvent (int result)
10132 {
10133     ChessMove gameResult = GameIsDrawn;
10134
10135     if( result > 0 ) {
10136         gameResult = WhiteWins;
10137     }
10138     else if( result < 0 ) {
10139         gameResult = BlackWins;
10140     }
10141
10142     if( gameMode == TwoMachinesPlay ) {
10143         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10144     }
10145 }
10146
10147
10148 // [HGM] save: calculate checksum of game to make games easily identifiable
10149 int
10150 StringCheckSum (char *s)
10151 {
10152         int i = 0;
10153         if(s==NULL) return 0;
10154         while(*s) i = i*259 + *s++;
10155         return i;
10156 }
10157
10158 int
10159 GameCheckSum ()
10160 {
10161         int i, sum=0;
10162         for(i=backwardMostMove; i<forwardMostMove; i++) {
10163                 sum += pvInfoList[i].depth;
10164                 sum += StringCheckSum(parseList[i]);
10165                 sum += StringCheckSum(commentList[i]);
10166                 sum *= 261;
10167         }
10168         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10169         return sum + StringCheckSum(commentList[i]);
10170 } // end of save patch
10171
10172 void
10173 GameEnds (ChessMove result, char *resultDetails, int whosays)
10174 {
10175     GameMode nextGameMode;
10176     int isIcsGame;
10177     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10178
10179     if(endingGame) return; /* [HGM] crash: forbid recursion */
10180     endingGame = 1;
10181     if(twoBoards) { // [HGM] dual: switch back to one board
10182         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10183         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10184     }
10185     if (appData.debugMode) {
10186       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10187               result, resultDetails ? resultDetails : "(null)", whosays);
10188     }
10189
10190     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10191
10192     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10193         /* If we are playing on ICS, the server decides when the
10194            game is over, but the engine can offer to draw, claim
10195            a draw, or resign.
10196          */
10197 #if ZIPPY
10198         if (appData.zippyPlay && first.initDone) {
10199             if (result == GameIsDrawn) {
10200                 /* In case draw still needs to be claimed */
10201                 SendToICS(ics_prefix);
10202                 SendToICS("draw\n");
10203             } else if (StrCaseStr(resultDetails, "resign")) {
10204                 SendToICS(ics_prefix);
10205                 SendToICS("resign\n");
10206             }
10207         }
10208 #endif
10209         endingGame = 0; /* [HGM] crash */
10210         return;
10211     }
10212
10213     /* If we're loading the game from a file, stop */
10214     if (whosays == GE_FILE) {
10215       (void) StopLoadGameTimer();
10216       gameFileFP = NULL;
10217     }
10218
10219     /* Cancel draw offers */
10220     first.offeredDraw = second.offeredDraw = 0;
10221
10222     /* If this is an ICS game, only ICS can really say it's done;
10223        if not, anyone can. */
10224     isIcsGame = (gameMode == IcsPlayingWhite ||
10225                  gameMode == IcsPlayingBlack ||
10226                  gameMode == IcsObserving    ||
10227                  gameMode == IcsExamining);
10228
10229     if (!isIcsGame || whosays == GE_ICS) {
10230         /* OK -- not an ICS game, or ICS said it was done */
10231         StopClocks();
10232         if (!isIcsGame && !appData.noChessProgram)
10233           SetUserThinkingEnables();
10234
10235         /* [HGM] if a machine claims the game end we verify this claim */
10236         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10237             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10238                 char claimer;
10239                 ChessMove trueResult = (ChessMove) -1;
10240
10241                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10242                                             first.twoMachinesColor[0] :
10243                                             second.twoMachinesColor[0] ;
10244
10245                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10246                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10247                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10248                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10249                 } else
10250                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10251                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10252                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10253                 } else
10254                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10255                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10256                 }
10257
10258                 // now verify win claims, but not in drop games, as we don't understand those yet
10259                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10260                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10261                     (result == WhiteWins && claimer == 'w' ||
10262                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10263                       if (appData.debugMode) {
10264                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10265                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10266                       }
10267                       if(result != trueResult) {
10268                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10269                               result = claimer == 'w' ? BlackWins : WhiteWins;
10270                               resultDetails = buf;
10271                       }
10272                 } else
10273                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10274                     && (forwardMostMove <= backwardMostMove ||
10275                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10276                         (claimer=='b')==(forwardMostMove&1))
10277                                                                                   ) {
10278                       /* [HGM] verify: draws that were not flagged are false claims */
10279                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10280                       result = claimer == 'w' ? BlackWins : WhiteWins;
10281                       resultDetails = buf;
10282                 }
10283                 /* (Claiming a loss is accepted no questions asked!) */
10284             }
10285             /* [HGM] bare: don't allow bare King to win */
10286             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10287                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10288                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10289                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10290                && result != GameIsDrawn)
10291             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10292                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10293                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10294                         if(p >= 0 && p <= (int)WhiteKing) k++;
10295                 }
10296                 if (appData.debugMode) {
10297                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10298                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10299                 }
10300                 if(k <= 1) {
10301                         result = GameIsDrawn;
10302                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10303                         resultDetails = buf;
10304                 }
10305             }
10306         }
10307
10308
10309         if(serverMoves != NULL && !loadFlag) { char c = '=';
10310             if(result==WhiteWins) c = '+';
10311             if(result==BlackWins) c = '-';
10312             if(resultDetails != NULL)
10313                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10314         }
10315         if (resultDetails != NULL) {
10316             gameInfo.result = result;
10317             gameInfo.resultDetails = StrSave(resultDetails);
10318
10319             /* display last move only if game was not loaded from file */
10320             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10321                 DisplayMove(currentMove - 1);
10322
10323             if (forwardMostMove != 0) {
10324                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10325                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10326                                                                 ) {
10327                     if (*appData.saveGameFile != NULLCHAR) {
10328                         SaveGameToFile(appData.saveGameFile, TRUE);
10329                     } else if (appData.autoSaveGames) {
10330                         AutoSaveGame();
10331                     }
10332                     if (*appData.savePositionFile != NULLCHAR) {
10333                         SavePositionToFile(appData.savePositionFile);
10334                     }
10335                 }
10336             }
10337
10338             /* Tell program how game ended in case it is learning */
10339             /* [HGM] Moved this to after saving the PGN, just in case */
10340             /* engine died and we got here through time loss. In that */
10341             /* case we will get a fatal error writing the pipe, which */
10342             /* would otherwise lose us the PGN.                       */
10343             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10344             /* output during GameEnds should never be fatal anymore   */
10345             if (gameMode == MachinePlaysWhite ||
10346                 gameMode == MachinePlaysBlack ||
10347                 gameMode == TwoMachinesPlay ||
10348                 gameMode == IcsPlayingWhite ||
10349                 gameMode == IcsPlayingBlack ||
10350                 gameMode == BeginningOfGame) {
10351                 char buf[MSG_SIZ];
10352                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10353                         resultDetails);
10354                 if (first.pr != NoProc) {
10355                     SendToProgram(buf, &first);
10356                 }
10357                 if (second.pr != NoProc &&
10358                     gameMode == TwoMachinesPlay) {
10359                     SendToProgram(buf, &second);
10360                 }
10361             }
10362         }
10363
10364         if (appData.icsActive) {
10365             if (appData.quietPlay &&
10366                 (gameMode == IcsPlayingWhite ||
10367                  gameMode == IcsPlayingBlack)) {
10368                 SendToICS(ics_prefix);
10369                 SendToICS("set shout 1\n");
10370             }
10371             nextGameMode = IcsIdle;
10372             ics_user_moved = FALSE;
10373             /* clean up premove.  It's ugly when the game has ended and the
10374              * premove highlights are still on the board.
10375              */
10376             if (gotPremove) {
10377               gotPremove = FALSE;
10378               ClearPremoveHighlights();
10379               DrawPosition(FALSE, boards[currentMove]);
10380             }
10381             if (whosays == GE_ICS) {
10382                 switch (result) {
10383                 case WhiteWins:
10384                     if (gameMode == IcsPlayingWhite)
10385                         PlayIcsWinSound();
10386                     else if(gameMode == IcsPlayingBlack)
10387                         PlayIcsLossSound();
10388                     break;
10389                 case BlackWins:
10390                     if (gameMode == IcsPlayingBlack)
10391                         PlayIcsWinSound();
10392                     else if(gameMode == IcsPlayingWhite)
10393                         PlayIcsLossSound();
10394                     break;
10395                 case GameIsDrawn:
10396                     PlayIcsDrawSound();
10397                     break;
10398                 default:
10399                     PlayIcsUnfinishedSound();
10400                 }
10401             }
10402         } else if (gameMode == EditGame ||
10403                    gameMode == PlayFromGameFile ||
10404                    gameMode == AnalyzeMode ||
10405                    gameMode == AnalyzeFile) {
10406             nextGameMode = gameMode;
10407         } else {
10408             nextGameMode = EndOfGame;
10409         }
10410         pausing = FALSE;
10411         ModeHighlight();
10412     } else {
10413         nextGameMode = gameMode;
10414     }
10415
10416     if (appData.noChessProgram) {
10417         gameMode = nextGameMode;
10418         ModeHighlight();
10419         endingGame = 0; /* [HGM] crash */
10420         return;
10421     }
10422
10423     if (first.reuse) {
10424         /* Put first chess program into idle state */
10425         if (first.pr != NoProc &&
10426             (gameMode == MachinePlaysWhite ||
10427              gameMode == MachinePlaysBlack ||
10428              gameMode == TwoMachinesPlay ||
10429              gameMode == IcsPlayingWhite ||
10430              gameMode == IcsPlayingBlack ||
10431              gameMode == BeginningOfGame)) {
10432             SendToProgram("force\n", &first);
10433             if (first.usePing) {
10434               char buf[MSG_SIZ];
10435               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10436               SendToProgram(buf, &first);
10437             }
10438         }
10439     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10440         /* Kill off first chess program */
10441         if (first.isr != NULL)
10442           RemoveInputSource(first.isr);
10443         first.isr = NULL;
10444
10445         if (first.pr != NoProc) {
10446             ExitAnalyzeMode();
10447             DoSleep( appData.delayBeforeQuit );
10448             SendToProgram("quit\n", &first);
10449             DoSleep( appData.delayAfterQuit );
10450             DestroyChildProcess(first.pr, first.useSigterm);
10451         }
10452         first.pr = NoProc;
10453     }
10454     if (second.reuse) {
10455         /* Put second chess program into idle state */
10456         if (second.pr != NoProc &&
10457             gameMode == TwoMachinesPlay) {
10458             SendToProgram("force\n", &second);
10459             if (second.usePing) {
10460               char buf[MSG_SIZ];
10461               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10462               SendToProgram(buf, &second);
10463             }
10464         }
10465     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10466         /* Kill off second chess program */
10467         if (second.isr != NULL)
10468           RemoveInputSource(second.isr);
10469         second.isr = NULL;
10470
10471         if (second.pr != NoProc) {
10472             DoSleep( appData.delayBeforeQuit );
10473             SendToProgram("quit\n", &second);
10474             DoSleep( appData.delayAfterQuit );
10475             DestroyChildProcess(second.pr, second.useSigterm);
10476         }
10477         second.pr = NoProc;
10478     }
10479
10480     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10481         char resChar = '=';
10482         switch (result) {
10483         case WhiteWins:
10484           resChar = '+';
10485           if (first.twoMachinesColor[0] == 'w') {
10486             first.matchWins++;
10487           } else {
10488             second.matchWins++;
10489           }
10490           break;
10491         case BlackWins:
10492           resChar = '-';
10493           if (first.twoMachinesColor[0] == 'b') {
10494             first.matchWins++;
10495           } else {
10496             second.matchWins++;
10497           }
10498           break;
10499         case GameUnfinished:
10500           resChar = ' ';
10501         default:
10502           break;
10503         }
10504
10505         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10506         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10507             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10508             ReserveGame(nextGame, resChar); // sets nextGame
10509             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10510             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10511         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10512
10513         if (nextGame <= appData.matchGames && !abortMatch) {
10514             gameMode = nextGameMode;
10515             matchGame = nextGame; // this will be overruled in tourney mode!
10516             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10517             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10518             endingGame = 0; /* [HGM] crash */
10519             return;
10520         } else {
10521             gameMode = nextGameMode;
10522             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10523                      first.tidy, second.tidy,
10524                      first.matchWins, second.matchWins,
10525                      appData.matchGames - (first.matchWins + second.matchWins));
10526             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10527             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10528             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10529             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10530                 first.twoMachinesColor = "black\n";
10531                 second.twoMachinesColor = "white\n";
10532             } else {
10533                 first.twoMachinesColor = "white\n";
10534                 second.twoMachinesColor = "black\n";
10535             }
10536         }
10537     }
10538     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10539         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10540       ExitAnalyzeMode();
10541     gameMode = nextGameMode;
10542     ModeHighlight();
10543     endingGame = 0;  /* [HGM] crash */
10544     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10545         if(matchMode == TRUE) { // match through command line: exit with or without popup
10546             if(ranking) {
10547                 ToNrEvent(forwardMostMove);
10548                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10549                 else ExitEvent(0);
10550             } else DisplayFatalError(buf, 0, 0);
10551         } else { // match through menu; just stop, with or without popup
10552             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10553             ModeHighlight();
10554             if(ranking){
10555                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10556             } else DisplayNote(buf);
10557       }
10558       if(ranking) free(ranking);
10559     }
10560 }
10561
10562 /* Assumes program was just initialized (initString sent).
10563    Leaves program in force mode. */
10564 void
10565 FeedMovesToProgram (ChessProgramState *cps, int upto)
10566 {
10567     int i;
10568
10569     if (appData.debugMode)
10570       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10571               startedFromSetupPosition ? "position and " : "",
10572               backwardMostMove, upto, cps->which);
10573     if(currentlyInitializedVariant != gameInfo.variant) {
10574       char buf[MSG_SIZ];
10575         // [HGM] variantswitch: make engine aware of new variant
10576         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10577                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10578         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10579         SendToProgram(buf, cps);
10580         currentlyInitializedVariant = gameInfo.variant;
10581     }
10582     SendToProgram("force\n", cps);
10583     if (startedFromSetupPosition) {
10584         SendBoard(cps, backwardMostMove);
10585     if (appData.debugMode) {
10586         fprintf(debugFP, "feedMoves\n");
10587     }
10588     }
10589     for (i = backwardMostMove; i < upto; i++) {
10590         SendMoveToProgram(i, cps);
10591     }
10592 }
10593
10594
10595 int
10596 ResurrectChessProgram ()
10597 {
10598      /* The chess program may have exited.
10599         If so, restart it and feed it all the moves made so far. */
10600     static int doInit = 0;
10601
10602     if (appData.noChessProgram) return 1;
10603
10604     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10605         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10606         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10607         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10608     } else {
10609         if (first.pr != NoProc) return 1;
10610         StartChessProgram(&first);
10611     }
10612     InitChessProgram(&first, FALSE);
10613     FeedMovesToProgram(&first, currentMove);
10614
10615     if (!first.sendTime) {
10616         /* can't tell gnuchess what its clock should read,
10617            so we bow to its notion. */
10618         ResetClocks();
10619         timeRemaining[0][currentMove] = whiteTimeRemaining;
10620         timeRemaining[1][currentMove] = blackTimeRemaining;
10621     }
10622
10623     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10624                 appData.icsEngineAnalyze) && first.analysisSupport) {
10625       SendToProgram("analyze\n", &first);
10626       first.analyzing = TRUE;
10627     }
10628     return 1;
10629 }
10630
10631 /*
10632  * Button procedures
10633  */
10634 void
10635 Reset (int redraw, int init)
10636 {
10637     int i;
10638
10639     if (appData.debugMode) {
10640         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10641                 redraw, init, gameMode);
10642     }
10643     CleanupTail(); // [HGM] vari: delete any stored variations
10644     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10645     pausing = pauseExamInvalid = FALSE;
10646     startedFromSetupPosition = blackPlaysFirst = FALSE;
10647     firstMove = TRUE;
10648     whiteFlag = blackFlag = FALSE;
10649     userOfferedDraw = FALSE;
10650     hintRequested = bookRequested = FALSE;
10651     first.maybeThinking = FALSE;
10652     second.maybeThinking = FALSE;
10653     first.bookSuspend = FALSE; // [HGM] book
10654     second.bookSuspend = FALSE;
10655     thinkOutput[0] = NULLCHAR;
10656     lastHint[0] = NULLCHAR;
10657     ClearGameInfo(&gameInfo);
10658     gameInfo.variant = StringToVariant(appData.variant);
10659     ics_user_moved = ics_clock_paused = FALSE;
10660     ics_getting_history = H_FALSE;
10661     ics_gamenum = -1;
10662     white_holding[0] = black_holding[0] = NULLCHAR;
10663     ClearProgramStats();
10664     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10665
10666     ResetFrontEnd();
10667     ClearHighlights();
10668     flipView = appData.flipView;
10669     ClearPremoveHighlights();
10670     gotPremove = FALSE;
10671     alarmSounded = FALSE;
10672
10673     GameEnds(EndOfFile, NULL, GE_PLAYER);
10674     if(appData.serverMovesName != NULL) {
10675         /* [HGM] prepare to make moves file for broadcasting */
10676         clock_t t = clock();
10677         if(serverMoves != NULL) fclose(serverMoves);
10678         serverMoves = fopen(appData.serverMovesName, "r");
10679         if(serverMoves != NULL) {
10680             fclose(serverMoves);
10681             /* delay 15 sec before overwriting, so all clients can see end */
10682             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10683         }
10684         serverMoves = fopen(appData.serverMovesName, "w");
10685     }
10686
10687     ExitAnalyzeMode();
10688     gameMode = BeginningOfGame;
10689     ModeHighlight();
10690     if(appData.icsActive) gameInfo.variant = VariantNormal;
10691     currentMove = forwardMostMove = backwardMostMove = 0;
10692     MarkTargetSquares(1);
10693     InitPosition(redraw);
10694     for (i = 0; i < MAX_MOVES; i++) {
10695         if (commentList[i] != NULL) {
10696             free(commentList[i]);
10697             commentList[i] = NULL;
10698         }
10699     }
10700     ResetClocks();
10701     timeRemaining[0][0] = whiteTimeRemaining;
10702     timeRemaining[1][0] = blackTimeRemaining;
10703
10704     if (first.pr == NoProc) {
10705         StartChessProgram(&first);
10706     }
10707     if (init) {
10708             InitChessProgram(&first, startedFromSetupPosition);
10709     }
10710     DisplayTitle("");
10711     DisplayMessage("", "");
10712     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10713     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10714 }
10715
10716 void
10717 AutoPlayGameLoop ()
10718 {
10719     for (;;) {
10720         if (!AutoPlayOneMove())
10721           return;
10722         if (matchMode || appData.timeDelay == 0)
10723           continue;
10724         if (appData.timeDelay < 0)
10725           return;
10726         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10727         break;
10728     }
10729 }
10730
10731
10732 int
10733 AutoPlayOneMove ()
10734 {
10735     int fromX, fromY, toX, toY;
10736
10737     if (appData.debugMode) {
10738       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10739     }
10740
10741     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10742       return FALSE;
10743
10744     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10745       pvInfoList[currentMove].depth = programStats.depth;
10746       pvInfoList[currentMove].score = programStats.score;
10747       pvInfoList[currentMove].time  = 0;
10748       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10749     }
10750
10751     if (currentMove >= forwardMostMove) {
10752       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10753 //      gameMode = EndOfGame;
10754 //      ModeHighlight();
10755
10756       /* [AS] Clear current move marker at the end of a game */
10757       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10758
10759       return FALSE;
10760     }
10761
10762     toX = moveList[currentMove][2] - AAA;
10763     toY = moveList[currentMove][3] - ONE;
10764
10765     if (moveList[currentMove][1] == '@') {
10766         if (appData.highlightLastMove) {
10767             SetHighlights(-1, -1, toX, toY);
10768         }
10769     } else {
10770         fromX = moveList[currentMove][0] - AAA;
10771         fromY = moveList[currentMove][1] - ONE;
10772
10773         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10774
10775         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10776
10777         if (appData.highlightLastMove) {
10778             SetHighlights(fromX, fromY, toX, toY);
10779         }
10780     }
10781     DisplayMove(currentMove);
10782     SendMoveToProgram(currentMove++, &first);
10783     DisplayBothClocks();
10784     DrawPosition(FALSE, boards[currentMove]);
10785     // [HGM] PV info: always display, routine tests if empty
10786     DisplayComment(currentMove - 1, commentList[currentMove]);
10787     return TRUE;
10788 }
10789
10790
10791 int
10792 LoadGameOneMove (ChessMove readAhead)
10793 {
10794     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10795     char promoChar = NULLCHAR;
10796     ChessMove moveType;
10797     char move[MSG_SIZ];
10798     char *p, *q;
10799
10800     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10801         gameMode != AnalyzeMode && gameMode != Training) {
10802         gameFileFP = NULL;
10803         return FALSE;
10804     }
10805
10806     yyboardindex = forwardMostMove;
10807     if (readAhead != EndOfFile) {
10808       moveType = readAhead;
10809     } else {
10810       if (gameFileFP == NULL)
10811           return FALSE;
10812       moveType = (ChessMove) Myylex();
10813     }
10814
10815     done = FALSE;
10816     switch (moveType) {
10817       case Comment:
10818         if (appData.debugMode)
10819           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10820         p = yy_text;
10821
10822         /* append the comment but don't display it */
10823         AppendComment(currentMove, p, FALSE);
10824         return TRUE;
10825
10826       case WhiteCapturesEnPassant:
10827       case BlackCapturesEnPassant:
10828       case WhitePromotion:
10829       case BlackPromotion:
10830       case WhiteNonPromotion:
10831       case BlackNonPromotion:
10832       case NormalMove:
10833       case WhiteKingSideCastle:
10834       case WhiteQueenSideCastle:
10835       case BlackKingSideCastle:
10836       case BlackQueenSideCastle:
10837       case WhiteKingSideCastleWild:
10838       case WhiteQueenSideCastleWild:
10839       case BlackKingSideCastleWild:
10840       case BlackQueenSideCastleWild:
10841       /* PUSH Fabien */
10842       case WhiteHSideCastleFR:
10843       case WhiteASideCastleFR:
10844       case BlackHSideCastleFR:
10845       case BlackASideCastleFR:
10846       /* POP Fabien */
10847         if (appData.debugMode)
10848           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10849         fromX = currentMoveString[0] - AAA;
10850         fromY = currentMoveString[1] - ONE;
10851         toX = currentMoveString[2] - AAA;
10852         toY = currentMoveString[3] - ONE;
10853         promoChar = currentMoveString[4];
10854         break;
10855
10856       case WhiteDrop:
10857       case BlackDrop:
10858         if (appData.debugMode)
10859           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10860         fromX = moveType == WhiteDrop ?
10861           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10862         (int) CharToPiece(ToLower(currentMoveString[0]));
10863         fromY = DROP_RANK;
10864         toX = currentMoveString[2] - AAA;
10865         toY = currentMoveString[3] - ONE;
10866         break;
10867
10868       case WhiteWins:
10869       case BlackWins:
10870       case GameIsDrawn:
10871       case GameUnfinished:
10872         if (appData.debugMode)
10873           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10874         p = strchr(yy_text, '{');
10875         if (p == NULL) p = strchr(yy_text, '(');
10876         if (p == NULL) {
10877             p = yy_text;
10878             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10879         } else {
10880             q = strchr(p, *p == '{' ? '}' : ')');
10881             if (q != NULL) *q = NULLCHAR;
10882             p++;
10883         }
10884         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10885         GameEnds(moveType, p, GE_FILE);
10886         done = TRUE;
10887         if (cmailMsgLoaded) {
10888             ClearHighlights();
10889             flipView = WhiteOnMove(currentMove);
10890             if (moveType == GameUnfinished) flipView = !flipView;
10891             if (appData.debugMode)
10892               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10893         }
10894         break;
10895
10896       case EndOfFile:
10897         if (appData.debugMode)
10898           fprintf(debugFP, "Parser hit end of file\n");
10899         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10900           case MT_NONE:
10901           case MT_CHECK:
10902             break;
10903           case MT_CHECKMATE:
10904           case MT_STAINMATE:
10905             if (WhiteOnMove(currentMove)) {
10906                 GameEnds(BlackWins, "Black mates", GE_FILE);
10907             } else {
10908                 GameEnds(WhiteWins, "White mates", GE_FILE);
10909             }
10910             break;
10911           case MT_STALEMATE:
10912             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10913             break;
10914         }
10915         done = TRUE;
10916         break;
10917
10918       case MoveNumberOne:
10919         if (lastLoadGameStart == GNUChessGame) {
10920             /* GNUChessGames have numbers, but they aren't move numbers */
10921             if (appData.debugMode)
10922               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10923                       yy_text, (int) moveType);
10924             return LoadGameOneMove(EndOfFile); /* tail recursion */
10925         }
10926         /* else fall thru */
10927
10928       case XBoardGame:
10929       case GNUChessGame:
10930       case PGNTag:
10931         /* Reached start of next game in file */
10932         if (appData.debugMode)
10933           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10934         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10935           case MT_NONE:
10936           case MT_CHECK:
10937             break;
10938           case MT_CHECKMATE:
10939           case MT_STAINMATE:
10940             if (WhiteOnMove(currentMove)) {
10941                 GameEnds(BlackWins, "Black mates", GE_FILE);
10942             } else {
10943                 GameEnds(WhiteWins, "White mates", GE_FILE);
10944             }
10945             break;
10946           case MT_STALEMATE:
10947             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10948             break;
10949         }
10950         done = TRUE;
10951         break;
10952
10953       case PositionDiagram:     /* should not happen; ignore */
10954       case ElapsedTime:         /* ignore */
10955       case NAG:                 /* ignore */
10956         if (appData.debugMode)
10957           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10958                   yy_text, (int) moveType);
10959         return LoadGameOneMove(EndOfFile); /* tail recursion */
10960
10961       case IllegalMove:
10962         if (appData.testLegality) {
10963             if (appData.debugMode)
10964               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10965             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10966                     (forwardMostMove / 2) + 1,
10967                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10968             DisplayError(move, 0);
10969             done = TRUE;
10970         } else {
10971             if (appData.debugMode)
10972               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10973                       yy_text, currentMoveString);
10974             fromX = currentMoveString[0] - AAA;
10975             fromY = currentMoveString[1] - ONE;
10976             toX = currentMoveString[2] - AAA;
10977             toY = currentMoveString[3] - ONE;
10978             promoChar = currentMoveString[4];
10979         }
10980         break;
10981
10982       case AmbiguousMove:
10983         if (appData.debugMode)
10984           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10985         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10986                 (forwardMostMove / 2) + 1,
10987                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10988         DisplayError(move, 0);
10989         done = TRUE;
10990         break;
10991
10992       default:
10993       case ImpossibleMove:
10994         if (appData.debugMode)
10995           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10996         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10997                 (forwardMostMove / 2) + 1,
10998                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10999         DisplayError(move, 0);
11000         done = TRUE;
11001         break;
11002     }
11003
11004     if (done) {
11005         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11006             DrawPosition(FALSE, boards[currentMove]);
11007             DisplayBothClocks();
11008             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11009               DisplayComment(currentMove - 1, commentList[currentMove]);
11010         }
11011         (void) StopLoadGameTimer();
11012         gameFileFP = NULL;
11013         cmailOldMove = forwardMostMove;
11014         return FALSE;
11015     } else {
11016         /* currentMoveString is set as a side-effect of yylex */
11017
11018         thinkOutput[0] = NULLCHAR;
11019         MakeMove(fromX, fromY, toX, toY, promoChar);
11020         currentMove = forwardMostMove;
11021         return TRUE;
11022     }
11023 }
11024
11025 /* Load the nth game from the given file */
11026 int
11027 LoadGameFromFile (char *filename, int n, char *title, int useList)
11028 {
11029     FILE *f;
11030     char buf[MSG_SIZ];
11031
11032     if (strcmp(filename, "-") == 0) {
11033         f = stdin;
11034         title = "stdin";
11035     } else {
11036         f = fopen(filename, "rb");
11037         if (f == NULL) {
11038           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11039             DisplayError(buf, errno);
11040             return FALSE;
11041         }
11042     }
11043     if (fseek(f, 0, 0) == -1) {
11044         /* f is not seekable; probably a pipe */
11045         useList = FALSE;
11046     }
11047     if (useList && n == 0) {
11048         int error = GameListBuild(f);
11049         if (error) {
11050             DisplayError(_("Cannot build game list"), error);
11051         } else if (!ListEmpty(&gameList) &&
11052                    ((ListGame *) gameList.tailPred)->number > 1) {
11053             GameListPopUp(f, title);
11054             return TRUE;
11055         }
11056         GameListDestroy();
11057         n = 1;
11058     }
11059     if (n == 0) n = 1;
11060     return LoadGame(f, n, title, FALSE);
11061 }
11062
11063
11064 void
11065 MakeRegisteredMove ()
11066 {
11067     int fromX, fromY, toX, toY;
11068     char promoChar;
11069     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11070         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11071           case CMAIL_MOVE:
11072           case CMAIL_DRAW:
11073             if (appData.debugMode)
11074               fprintf(debugFP, "Restoring %s for game %d\n",
11075                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11076
11077             thinkOutput[0] = NULLCHAR;
11078             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11079             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11080             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11081             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11082             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11083             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11084             MakeMove(fromX, fromY, toX, toY, promoChar);
11085             ShowMove(fromX, fromY, toX, toY);
11086
11087             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11088               case MT_NONE:
11089               case MT_CHECK:
11090                 break;
11091
11092               case MT_CHECKMATE:
11093               case MT_STAINMATE:
11094                 if (WhiteOnMove(currentMove)) {
11095                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11096                 } else {
11097                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11098                 }
11099                 break;
11100
11101               case MT_STALEMATE:
11102                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11103                 break;
11104             }
11105
11106             break;
11107
11108           case CMAIL_RESIGN:
11109             if (WhiteOnMove(currentMove)) {
11110                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11111             } else {
11112                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11113             }
11114             break;
11115
11116           case CMAIL_ACCEPT:
11117             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11118             break;
11119
11120           default:
11121             break;
11122         }
11123     }
11124
11125     return;
11126 }
11127
11128 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11129 int
11130 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11131 {
11132     int retVal;
11133
11134     if (gameNumber > nCmailGames) {
11135         DisplayError(_("No more games in this message"), 0);
11136         return FALSE;
11137     }
11138     if (f == lastLoadGameFP) {
11139         int offset = gameNumber - lastLoadGameNumber;
11140         if (offset == 0) {
11141             cmailMsg[0] = NULLCHAR;
11142             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11143                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11144                 nCmailMovesRegistered--;
11145             }
11146             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11147             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11148                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11149             }
11150         } else {
11151             if (! RegisterMove()) return FALSE;
11152         }
11153     }
11154
11155     retVal = LoadGame(f, gameNumber, title, useList);
11156
11157     /* Make move registered during previous look at this game, if any */
11158     MakeRegisteredMove();
11159
11160     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11161         commentList[currentMove]
11162           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11163         DisplayComment(currentMove - 1, commentList[currentMove]);
11164     }
11165
11166     return retVal;
11167 }
11168
11169 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11170 int
11171 ReloadGame (int offset)
11172 {
11173     int gameNumber = lastLoadGameNumber + offset;
11174     if (lastLoadGameFP == NULL) {
11175         DisplayError(_("No game has been loaded yet"), 0);
11176         return FALSE;
11177     }
11178     if (gameNumber <= 0) {
11179         DisplayError(_("Can't back up any further"), 0);
11180         return FALSE;
11181     }
11182     if (cmailMsgLoaded) {
11183         return CmailLoadGame(lastLoadGameFP, gameNumber,
11184                              lastLoadGameTitle, lastLoadGameUseList);
11185     } else {
11186         return LoadGame(lastLoadGameFP, gameNumber,
11187                         lastLoadGameTitle, lastLoadGameUseList);
11188     }
11189 }
11190
11191 int keys[EmptySquare+1];
11192
11193 int
11194 PositionMatches (Board b1, Board b2)
11195 {
11196     int r, f, sum=0;
11197     switch(appData.searchMode) {
11198         case 1: return CompareWithRights(b1, b2);
11199         case 2:
11200             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11201                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11202             }
11203             return TRUE;
11204         case 3:
11205             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11206               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11207                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11208             }
11209             return sum==0;
11210         case 4:
11211             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11212                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11213             }
11214             return sum==0;
11215     }
11216     return TRUE;
11217 }
11218
11219 #define Q_PROMO  4
11220 #define Q_EP     3
11221 #define Q_BCASTL 2
11222 #define Q_WCASTL 1
11223
11224 int pieceList[256], quickBoard[256];
11225 ChessSquare pieceType[256] = { EmptySquare };
11226 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11227 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11228 int soughtTotal, turn;
11229 Boolean epOK, flipSearch;
11230
11231 typedef struct {
11232     unsigned char piece, to;
11233 } Move;
11234
11235 #define DSIZE (250000)
11236
11237 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11238 Move *moveDatabase = initialSpace;
11239 unsigned int movePtr, dataSize = DSIZE;
11240
11241 int
11242 MakePieceList (Board board, int *counts)
11243 {
11244     int r, f, n=Q_PROMO, total=0;
11245     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11246     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11247         int sq = f + (r<<4);
11248         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11249             quickBoard[sq] = ++n;
11250             pieceList[n] = sq;
11251             pieceType[n] = board[r][f];
11252             counts[board[r][f]]++;
11253             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11254             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11255             total++;
11256         }
11257     }
11258     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11259     return total;
11260 }
11261
11262 void
11263 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11264 {
11265     int sq = fromX + (fromY<<4);
11266     int piece = quickBoard[sq];
11267     quickBoard[sq] = 0;
11268     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11269     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11270         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11271         moveDatabase[movePtr++].piece = Q_WCASTL;
11272         quickBoard[sq] = piece;
11273         piece = quickBoard[from]; quickBoard[from] = 0;
11274         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11275     } else
11276     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11277         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11278         moveDatabase[movePtr++].piece = Q_BCASTL;
11279         quickBoard[sq] = piece;
11280         piece = quickBoard[from]; quickBoard[from] = 0;
11281         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11282     } else
11283     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11284         quickBoard[(fromY<<4)+toX] = 0;
11285         moveDatabase[movePtr].piece = Q_EP;
11286         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11287         moveDatabase[movePtr].to = sq;
11288     } else
11289     if(promoPiece != pieceType[piece]) {
11290         moveDatabase[movePtr++].piece = Q_PROMO;
11291         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11292     }
11293     moveDatabase[movePtr].piece = piece;
11294     quickBoard[sq] = piece;
11295     movePtr++;
11296 }
11297
11298 int
11299 PackGame (Board board)
11300 {
11301     Move *newSpace = NULL;
11302     moveDatabase[movePtr].piece = 0; // terminate previous game
11303     if(movePtr > dataSize) {
11304         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11305         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11306         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11307         if(newSpace) {
11308             int i;
11309             Move *p = moveDatabase, *q = newSpace;
11310             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11311             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11312             moveDatabase = newSpace;
11313         } else { // calloc failed, we must be out of memory. Too bad...
11314             dataSize = 0; // prevent calloc events for all subsequent games
11315             return 0;     // and signal this one isn't cached
11316         }
11317     }
11318     movePtr++;
11319     MakePieceList(board, counts);
11320     return movePtr;
11321 }
11322
11323 int
11324 QuickCompare (Board board, int *minCounts, int *maxCounts)
11325 {   // compare according to search mode
11326     int r, f;
11327     switch(appData.searchMode)
11328     {
11329       case 1: // exact position match
11330         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11331         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11332             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11333         }
11334         break;
11335       case 2: // can have extra material on empty squares
11336         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11337             if(board[r][f] == EmptySquare) continue;
11338             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11339         }
11340         break;
11341       case 3: // material with exact Pawn structure
11342         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11343             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11344             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11345         } // fall through to material comparison
11346       case 4: // exact material
11347         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11348         break;
11349       case 6: // material range with given imbalance
11350         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11351         // fall through to range comparison
11352       case 5: // material range
11353         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11354     }
11355     return TRUE;
11356 }
11357
11358 int
11359 QuickScan (Board board, Move *move)
11360 {   // reconstruct game,and compare all positions in it
11361     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11362     do {
11363         int piece = move->piece;
11364         int to = move->to, from = pieceList[piece];
11365         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11366           if(!piece) return -1;
11367           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11368             piece = (++move)->piece;
11369             from = pieceList[piece];
11370             counts[pieceType[piece]]--;
11371             pieceType[piece] = (ChessSquare) move->to;
11372             counts[move->to]++;
11373           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11374             counts[pieceType[quickBoard[to]]]--;
11375             quickBoard[to] = 0; total--;
11376             move++;
11377             continue;
11378           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11379             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11380             from  = pieceList[piece]; // so this must be King
11381             quickBoard[from] = 0;
11382             quickBoard[to] = piece;
11383             pieceList[piece] = to;
11384             move++;
11385             continue;
11386           }
11387         }
11388         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11389         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11390         quickBoard[from] = 0;
11391         quickBoard[to] = piece;
11392         pieceList[piece] = to;
11393         cnt++; turn ^= 3;
11394         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11395            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11396            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11397                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11398           ) {
11399             static int lastCounts[EmptySquare+1];
11400             int i;
11401             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11402             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11403         } else stretch = 0;
11404         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11405         move++;
11406     } while(1);
11407 }
11408
11409 void
11410 InitSearch ()
11411 {
11412     int r, f;
11413     flipSearch = FALSE;
11414     CopyBoard(soughtBoard, boards[currentMove]);
11415     soughtTotal = MakePieceList(soughtBoard, maxSought);
11416     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11417     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11418     CopyBoard(reverseBoard, boards[currentMove]);
11419     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11420         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11421         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11422         reverseBoard[r][f] = piece;
11423     }
11424     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11425     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11426     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11427                  || (boards[currentMove][CASTLING][2] == NoRights || 
11428                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11429                  && (boards[currentMove][CASTLING][5] == NoRights || 
11430                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11431       ) {
11432         flipSearch = TRUE;
11433         CopyBoard(flipBoard, soughtBoard);
11434         CopyBoard(rotateBoard, reverseBoard);
11435         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11436             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11437             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11438         }
11439     }
11440     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11441     if(appData.searchMode >= 5) {
11442         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11443         MakePieceList(soughtBoard, minSought);
11444         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11445     }
11446     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11447         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11448 }
11449
11450 GameInfo dummyInfo;
11451
11452 int
11453 GameContainsPosition (FILE *f, ListGame *lg)
11454 {
11455     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11456     int fromX, fromY, toX, toY;
11457     char promoChar;
11458     static int initDone=FALSE;
11459
11460     // weed out games based on numerical tag comparison
11461     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11462     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11463     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11464     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11465     if(!initDone) {
11466         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11467         initDone = TRUE;
11468     }
11469     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11470     else CopyBoard(boards[scratch], initialPosition); // default start position
11471     if(lg->moves) {
11472         turn = btm + 1;
11473         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11474         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11475     }
11476     if(btm) plyNr++;
11477     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11478     fseek(f, lg->offset, 0);
11479     yynewfile(f);
11480     while(1) {
11481         yyboardindex = scratch;
11482         quickFlag = plyNr+1;
11483         next = Myylex();
11484         quickFlag = 0;
11485         switch(next) {
11486             case PGNTag:
11487                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11488             default:
11489                 continue;
11490
11491             case XBoardGame:
11492             case GNUChessGame:
11493                 if(plyNr) return -1; // after we have seen moves, this is for new game
11494               continue;
11495
11496             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11497             case ImpossibleMove:
11498             case WhiteWins: // game ends here with these four
11499             case BlackWins:
11500             case GameIsDrawn:
11501             case GameUnfinished:
11502                 return -1;
11503
11504             case IllegalMove:
11505                 if(appData.testLegality) return -1;
11506             case WhiteCapturesEnPassant:
11507             case BlackCapturesEnPassant:
11508             case WhitePromotion:
11509             case BlackPromotion:
11510             case WhiteNonPromotion:
11511             case BlackNonPromotion:
11512             case NormalMove:
11513             case WhiteKingSideCastle:
11514             case WhiteQueenSideCastle:
11515             case BlackKingSideCastle:
11516             case BlackQueenSideCastle:
11517             case WhiteKingSideCastleWild:
11518             case WhiteQueenSideCastleWild:
11519             case BlackKingSideCastleWild:
11520             case BlackQueenSideCastleWild:
11521             case WhiteHSideCastleFR:
11522             case WhiteASideCastleFR:
11523             case BlackHSideCastleFR:
11524             case BlackASideCastleFR:
11525                 fromX = currentMoveString[0] - AAA;
11526                 fromY = currentMoveString[1] - ONE;
11527                 toX = currentMoveString[2] - AAA;
11528                 toY = currentMoveString[3] - ONE;
11529                 promoChar = currentMoveString[4];
11530                 break;
11531             case WhiteDrop:
11532             case BlackDrop:
11533                 fromX = next == WhiteDrop ?
11534                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11535                   (int) CharToPiece(ToLower(currentMoveString[0]));
11536                 fromY = DROP_RANK;
11537                 toX = currentMoveString[2] - AAA;
11538                 toY = currentMoveString[3] - ONE;
11539                 promoChar = 0;
11540                 break;
11541         }
11542         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11543         plyNr++;
11544         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11545         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11546         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11547         if(appData.findMirror) {
11548             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11549             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11550         }
11551     }
11552 }
11553
11554 /* Load the nth game from open file f */
11555 int
11556 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11557 {
11558     ChessMove cm;
11559     char buf[MSG_SIZ];
11560     int gn = gameNumber;
11561     ListGame *lg = NULL;
11562     int numPGNTags = 0;
11563     int err, pos = -1;
11564     GameMode oldGameMode;
11565     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11566
11567     if (appData.debugMode)
11568         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11569
11570     if (gameMode == Training )
11571         SetTrainingModeOff();
11572
11573     oldGameMode = gameMode;
11574     if (gameMode != BeginningOfGame) {
11575       Reset(FALSE, TRUE);
11576     }
11577
11578     gameFileFP = f;
11579     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11580         fclose(lastLoadGameFP);
11581     }
11582
11583     if (useList) {
11584         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11585
11586         if (lg) {
11587             fseek(f, lg->offset, 0);
11588             GameListHighlight(gameNumber);
11589             pos = lg->position;
11590             gn = 1;
11591         }
11592         else {
11593             DisplayError(_("Game number out of range"), 0);
11594             return FALSE;
11595         }
11596     } else {
11597         GameListDestroy();
11598         if (fseek(f, 0, 0) == -1) {
11599             if (f == lastLoadGameFP ?
11600                 gameNumber == lastLoadGameNumber + 1 :
11601                 gameNumber == 1) {
11602                 gn = 1;
11603             } else {
11604                 DisplayError(_("Can't seek on game file"), 0);
11605                 return FALSE;
11606             }
11607         }
11608     }
11609     lastLoadGameFP = f;
11610     lastLoadGameNumber = gameNumber;
11611     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11612     lastLoadGameUseList = useList;
11613
11614     yynewfile(f);
11615
11616     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11617       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11618                 lg->gameInfo.black);
11619             DisplayTitle(buf);
11620     } else if (*title != NULLCHAR) {
11621         if (gameNumber > 1) {
11622           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11623             DisplayTitle(buf);
11624         } else {
11625             DisplayTitle(title);
11626         }
11627     }
11628
11629     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11630         gameMode = PlayFromGameFile;
11631         ModeHighlight();
11632     }
11633
11634     currentMove = forwardMostMove = backwardMostMove = 0;
11635     CopyBoard(boards[0], initialPosition);
11636     StopClocks();
11637
11638     /*
11639      * Skip the first gn-1 games in the file.
11640      * Also skip over anything that precedes an identifiable
11641      * start of game marker, to avoid being confused by
11642      * garbage at the start of the file.  Currently
11643      * recognized start of game markers are the move number "1",
11644      * the pattern "gnuchess .* game", the pattern
11645      * "^[#;%] [^ ]* game file", and a PGN tag block.
11646      * A game that starts with one of the latter two patterns
11647      * will also have a move number 1, possibly
11648      * following a position diagram.
11649      * 5-4-02: Let's try being more lenient and allowing a game to
11650      * start with an unnumbered move.  Does that break anything?
11651      */
11652     cm = lastLoadGameStart = EndOfFile;
11653     while (gn > 0) {
11654         yyboardindex = forwardMostMove;
11655         cm = (ChessMove) Myylex();
11656         switch (cm) {
11657           case EndOfFile:
11658             if (cmailMsgLoaded) {
11659                 nCmailGames = CMAIL_MAX_GAMES - gn;
11660             } else {
11661                 Reset(TRUE, TRUE);
11662                 DisplayError(_("Game not found in file"), 0);
11663             }
11664             return FALSE;
11665
11666           case GNUChessGame:
11667           case XBoardGame:
11668             gn--;
11669             lastLoadGameStart = cm;
11670             break;
11671
11672           case MoveNumberOne:
11673             switch (lastLoadGameStart) {
11674               case GNUChessGame:
11675               case XBoardGame:
11676               case PGNTag:
11677                 break;
11678               case MoveNumberOne:
11679               case EndOfFile:
11680                 gn--;           /* count this game */
11681                 lastLoadGameStart = cm;
11682                 break;
11683               default:
11684                 /* impossible */
11685                 break;
11686             }
11687             break;
11688
11689           case PGNTag:
11690             switch (lastLoadGameStart) {
11691               case GNUChessGame:
11692               case PGNTag:
11693               case MoveNumberOne:
11694               case EndOfFile:
11695                 gn--;           /* count this game */
11696                 lastLoadGameStart = cm;
11697                 break;
11698               case XBoardGame:
11699                 lastLoadGameStart = cm; /* game counted already */
11700                 break;
11701               default:
11702                 /* impossible */
11703                 break;
11704             }
11705             if (gn > 0) {
11706                 do {
11707                     yyboardindex = forwardMostMove;
11708                     cm = (ChessMove) Myylex();
11709                 } while (cm == PGNTag || cm == Comment);
11710             }
11711             break;
11712
11713           case WhiteWins:
11714           case BlackWins:
11715           case GameIsDrawn:
11716             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11717                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11718                     != CMAIL_OLD_RESULT) {
11719                     nCmailResults ++ ;
11720                     cmailResult[  CMAIL_MAX_GAMES
11721                                 - gn - 1] = CMAIL_OLD_RESULT;
11722                 }
11723             }
11724             break;
11725
11726           case NormalMove:
11727             /* Only a NormalMove can be at the start of a game
11728              * without a position diagram. */
11729             if (lastLoadGameStart == EndOfFile ) {
11730               gn--;
11731               lastLoadGameStart = MoveNumberOne;
11732             }
11733             break;
11734
11735           default:
11736             break;
11737         }
11738     }
11739
11740     if (appData.debugMode)
11741       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11742
11743     if (cm == XBoardGame) {
11744         /* Skip any header junk before position diagram and/or move 1 */
11745         for (;;) {
11746             yyboardindex = forwardMostMove;
11747             cm = (ChessMove) Myylex();
11748
11749             if (cm == EndOfFile ||
11750                 cm == GNUChessGame || cm == XBoardGame) {
11751                 /* Empty game; pretend end-of-file and handle later */
11752                 cm = EndOfFile;
11753                 break;
11754             }
11755
11756             if (cm == MoveNumberOne || cm == PositionDiagram ||
11757                 cm == PGNTag || cm == Comment)
11758               break;
11759         }
11760     } else if (cm == GNUChessGame) {
11761         if (gameInfo.event != NULL) {
11762             free(gameInfo.event);
11763         }
11764         gameInfo.event = StrSave(yy_text);
11765     }
11766
11767     startedFromSetupPosition = FALSE;
11768     while (cm == PGNTag) {
11769         if (appData.debugMode)
11770           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11771         err = ParsePGNTag(yy_text, &gameInfo);
11772         if (!err) numPGNTags++;
11773
11774         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11775         if(gameInfo.variant != oldVariant) {
11776             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11777             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11778             InitPosition(TRUE);
11779             oldVariant = gameInfo.variant;
11780             if (appData.debugMode)
11781               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11782         }
11783
11784
11785         if (gameInfo.fen != NULL) {
11786           Board initial_position;
11787           startedFromSetupPosition = TRUE;
11788           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11789             Reset(TRUE, TRUE);
11790             DisplayError(_("Bad FEN position in file"), 0);
11791             return FALSE;
11792           }
11793           CopyBoard(boards[0], initial_position);
11794           if (blackPlaysFirst) {
11795             currentMove = forwardMostMove = backwardMostMove = 1;
11796             CopyBoard(boards[1], initial_position);
11797             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11798             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11799             timeRemaining[0][1] = whiteTimeRemaining;
11800             timeRemaining[1][1] = blackTimeRemaining;
11801             if (commentList[0] != NULL) {
11802               commentList[1] = commentList[0];
11803               commentList[0] = NULL;
11804             }
11805           } else {
11806             currentMove = forwardMostMove = backwardMostMove = 0;
11807           }
11808           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11809           {   int i;
11810               initialRulePlies = FENrulePlies;
11811               for( i=0; i< nrCastlingRights; i++ )
11812                   initialRights[i] = initial_position[CASTLING][i];
11813           }
11814           yyboardindex = forwardMostMove;
11815           free(gameInfo.fen);
11816           gameInfo.fen = NULL;
11817         }
11818
11819         yyboardindex = forwardMostMove;
11820         cm = (ChessMove) Myylex();
11821
11822         /* Handle comments interspersed among the tags */
11823         while (cm == Comment) {
11824             char *p;
11825             if (appData.debugMode)
11826               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11827             p = yy_text;
11828             AppendComment(currentMove, p, FALSE);
11829             yyboardindex = forwardMostMove;
11830             cm = (ChessMove) Myylex();
11831         }
11832     }
11833
11834     /* don't rely on existence of Event tag since if game was
11835      * pasted from clipboard the Event tag may not exist
11836      */
11837     if (numPGNTags > 0){
11838         char *tags;
11839         if (gameInfo.variant == VariantNormal) {
11840           VariantClass v = StringToVariant(gameInfo.event);
11841           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11842           if(v < VariantShogi) gameInfo.variant = v;
11843         }
11844         if (!matchMode) {
11845           if( appData.autoDisplayTags ) {
11846             tags = PGNTags(&gameInfo);
11847             TagsPopUp(tags, CmailMsg());
11848             free(tags);
11849           }
11850         }
11851     } else {
11852         /* Make something up, but don't display it now */
11853         SetGameInfo();
11854         TagsPopDown();
11855     }
11856
11857     if (cm == PositionDiagram) {
11858         int i, j;
11859         char *p;
11860         Board initial_position;
11861
11862         if (appData.debugMode)
11863           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11864
11865         if (!startedFromSetupPosition) {
11866             p = yy_text;
11867             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11868               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11869                 switch (*p) {
11870                   case '{':
11871                   case '[':
11872                   case '-':
11873                   case ' ':
11874                   case '\t':
11875                   case '\n':
11876                   case '\r':
11877                     break;
11878                   default:
11879                     initial_position[i][j++] = CharToPiece(*p);
11880                     break;
11881                 }
11882             while (*p == ' ' || *p == '\t' ||
11883                    *p == '\n' || *p == '\r') p++;
11884
11885             if (strncmp(p, "black", strlen("black"))==0)
11886               blackPlaysFirst = TRUE;
11887             else
11888               blackPlaysFirst = FALSE;
11889             startedFromSetupPosition = TRUE;
11890
11891             CopyBoard(boards[0], initial_position);
11892             if (blackPlaysFirst) {
11893                 currentMove = forwardMostMove = backwardMostMove = 1;
11894                 CopyBoard(boards[1], initial_position);
11895                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11896                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11897                 timeRemaining[0][1] = whiteTimeRemaining;
11898                 timeRemaining[1][1] = blackTimeRemaining;
11899                 if (commentList[0] != NULL) {
11900                     commentList[1] = commentList[0];
11901                     commentList[0] = NULL;
11902                 }
11903             } else {
11904                 currentMove = forwardMostMove = backwardMostMove = 0;
11905             }
11906         }
11907         yyboardindex = forwardMostMove;
11908         cm = (ChessMove) Myylex();
11909     }
11910
11911     if (first.pr == NoProc) {
11912         StartChessProgram(&first);
11913     }
11914     InitChessProgram(&first, FALSE);
11915     SendToProgram("force\n", &first);
11916     if (startedFromSetupPosition) {
11917         SendBoard(&first, forwardMostMove);
11918     if (appData.debugMode) {
11919         fprintf(debugFP, "Load Game\n");
11920     }
11921         DisplayBothClocks();
11922     }
11923
11924     /* [HGM] server: flag to write setup moves in broadcast file as one */
11925     loadFlag = appData.suppressLoadMoves;
11926
11927     while (cm == Comment) {
11928         char *p;
11929         if (appData.debugMode)
11930           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11931         p = yy_text;
11932         AppendComment(currentMove, p, FALSE);
11933         yyboardindex = forwardMostMove;
11934         cm = (ChessMove) Myylex();
11935     }
11936
11937     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11938         cm == WhiteWins || cm == BlackWins ||
11939         cm == GameIsDrawn || cm == GameUnfinished) {
11940         DisplayMessage("", _("No moves in game"));
11941         if (cmailMsgLoaded) {
11942             if (appData.debugMode)
11943               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11944             ClearHighlights();
11945             flipView = FALSE;
11946         }
11947         DrawPosition(FALSE, boards[currentMove]);
11948         DisplayBothClocks();
11949         gameMode = EditGame;
11950         ModeHighlight();
11951         gameFileFP = NULL;
11952         cmailOldMove = 0;
11953         return TRUE;
11954     }
11955
11956     // [HGM] PV info: routine tests if comment empty
11957     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11958         DisplayComment(currentMove - 1, commentList[currentMove]);
11959     }
11960     if (!matchMode && appData.timeDelay != 0)
11961       DrawPosition(FALSE, boards[currentMove]);
11962
11963     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11964       programStats.ok_to_send = 1;
11965     }
11966
11967     /* if the first token after the PGN tags is a move
11968      * and not move number 1, retrieve it from the parser
11969      */
11970     if (cm != MoveNumberOne)
11971         LoadGameOneMove(cm);
11972
11973     /* load the remaining moves from the file */
11974     while (LoadGameOneMove(EndOfFile)) {
11975       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11976       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11977     }
11978
11979     /* rewind to the start of the game */
11980     currentMove = backwardMostMove;
11981
11982     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11983
11984     if (oldGameMode == AnalyzeFile ||
11985         oldGameMode == AnalyzeMode) {
11986       AnalyzeFileEvent();
11987     }
11988
11989     if (!matchMode && pos >= 0) {
11990         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11991     } else
11992     if (matchMode || appData.timeDelay == 0) {
11993       ToEndEvent();
11994     } else if (appData.timeDelay > 0) {
11995       AutoPlayGameLoop();
11996     }
11997
11998     if (appData.debugMode)
11999         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12000
12001     loadFlag = 0; /* [HGM] true game starts */
12002     return TRUE;
12003 }
12004
12005 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12006 int
12007 ReloadPosition (int offset)
12008 {
12009     int positionNumber = lastLoadPositionNumber + offset;
12010     if (lastLoadPositionFP == NULL) {
12011         DisplayError(_("No position has been loaded yet"), 0);
12012         return FALSE;
12013     }
12014     if (positionNumber <= 0) {
12015         DisplayError(_("Can't back up any further"), 0);
12016         return FALSE;
12017     }
12018     return LoadPosition(lastLoadPositionFP, positionNumber,
12019                         lastLoadPositionTitle);
12020 }
12021
12022 /* Load the nth position from the given file */
12023 int
12024 LoadPositionFromFile (char *filename, int n, char *title)
12025 {
12026     FILE *f;
12027     char buf[MSG_SIZ];
12028
12029     if (strcmp(filename, "-") == 0) {
12030         return LoadPosition(stdin, n, "stdin");
12031     } else {
12032         f = fopen(filename, "rb");
12033         if (f == NULL) {
12034             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12035             DisplayError(buf, errno);
12036             return FALSE;
12037         } else {
12038             return LoadPosition(f, n, title);
12039         }
12040     }
12041 }
12042
12043 /* Load the nth position from the given open file, and close it */
12044 int
12045 LoadPosition (FILE *f, int positionNumber, char *title)
12046 {
12047     char *p, line[MSG_SIZ];
12048     Board initial_position;
12049     int i, j, fenMode, pn;
12050
12051     if (gameMode == Training )
12052         SetTrainingModeOff();
12053
12054     if (gameMode != BeginningOfGame) {
12055         Reset(FALSE, TRUE);
12056     }
12057     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12058         fclose(lastLoadPositionFP);
12059     }
12060     if (positionNumber == 0) positionNumber = 1;
12061     lastLoadPositionFP = f;
12062     lastLoadPositionNumber = positionNumber;
12063     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12064     if (first.pr == NoProc && !appData.noChessProgram) {
12065       StartChessProgram(&first);
12066       InitChessProgram(&first, FALSE);
12067     }
12068     pn = positionNumber;
12069     if (positionNumber < 0) {
12070         /* Negative position number means to seek to that byte offset */
12071         if (fseek(f, -positionNumber, 0) == -1) {
12072             DisplayError(_("Can't seek on position file"), 0);
12073             return FALSE;
12074         };
12075         pn = 1;
12076     } else {
12077         if (fseek(f, 0, 0) == -1) {
12078             if (f == lastLoadPositionFP ?
12079                 positionNumber == lastLoadPositionNumber + 1 :
12080                 positionNumber == 1) {
12081                 pn = 1;
12082             } else {
12083                 DisplayError(_("Can't seek on position file"), 0);
12084                 return FALSE;
12085             }
12086         }
12087     }
12088     /* See if this file is FEN or old-style xboard */
12089     if (fgets(line, MSG_SIZ, f) == NULL) {
12090         DisplayError(_("Position not found in file"), 0);
12091         return FALSE;
12092     }
12093     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12094     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12095
12096     if (pn >= 2) {
12097         if (fenMode || line[0] == '#') pn--;
12098         while (pn > 0) {
12099             /* skip positions before number pn */
12100             if (fgets(line, MSG_SIZ, f) == NULL) {
12101                 Reset(TRUE, TRUE);
12102                 DisplayError(_("Position not found in file"), 0);
12103                 return FALSE;
12104             }
12105             if (fenMode || line[0] == '#') pn--;
12106         }
12107     }
12108
12109     if (fenMode) {
12110         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12111             DisplayError(_("Bad FEN position in file"), 0);
12112             return FALSE;
12113         }
12114     } else {
12115         (void) fgets(line, MSG_SIZ, f);
12116         (void) fgets(line, MSG_SIZ, f);
12117
12118         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12119             (void) fgets(line, MSG_SIZ, f);
12120             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12121                 if (*p == ' ')
12122                   continue;
12123                 initial_position[i][j++] = CharToPiece(*p);
12124             }
12125         }
12126
12127         blackPlaysFirst = FALSE;
12128         if (!feof(f)) {
12129             (void) fgets(line, MSG_SIZ, f);
12130             if (strncmp(line, "black", strlen("black"))==0)
12131               blackPlaysFirst = TRUE;
12132         }
12133     }
12134     startedFromSetupPosition = TRUE;
12135
12136     CopyBoard(boards[0], initial_position);
12137     if (blackPlaysFirst) {
12138         currentMove = forwardMostMove = backwardMostMove = 1;
12139         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12140         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12141         CopyBoard(boards[1], initial_position);
12142         DisplayMessage("", _("Black to play"));
12143     } else {
12144         currentMove = forwardMostMove = backwardMostMove = 0;
12145         DisplayMessage("", _("White to play"));
12146     }
12147     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12148     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12149         SendToProgram("force\n", &first);
12150         SendBoard(&first, forwardMostMove);
12151     }
12152     if (appData.debugMode) {
12153 int i, j;
12154   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12155   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12156         fprintf(debugFP, "Load Position\n");
12157     }
12158
12159     if (positionNumber > 1) {
12160       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12161         DisplayTitle(line);
12162     } else {
12163         DisplayTitle(title);
12164     }
12165     gameMode = EditGame;
12166     ModeHighlight();
12167     ResetClocks();
12168     timeRemaining[0][1] = whiteTimeRemaining;
12169     timeRemaining[1][1] = blackTimeRemaining;
12170     DrawPosition(FALSE, boards[currentMove]);
12171
12172     return TRUE;
12173 }
12174
12175
12176 void
12177 CopyPlayerNameIntoFileName (char **dest, char *src)
12178 {
12179     while (*src != NULLCHAR && *src != ',') {
12180         if (*src == ' ') {
12181             *(*dest)++ = '_';
12182             src++;
12183         } else {
12184             *(*dest)++ = *src++;
12185         }
12186     }
12187 }
12188
12189 char *
12190 DefaultFileName (char *ext)
12191 {
12192     static char def[MSG_SIZ];
12193     char *p;
12194
12195     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12196         p = def;
12197         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12198         *p++ = '-';
12199         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12200         *p++ = '.';
12201         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12202     } else {
12203         def[0] = NULLCHAR;
12204     }
12205     return def;
12206 }
12207
12208 /* Save the current game to the given file */
12209 int
12210 SaveGameToFile (char *filename, int append)
12211 {
12212     FILE *f;
12213     char buf[MSG_SIZ];
12214     int result, i, t,tot=0;
12215
12216     if (strcmp(filename, "-") == 0) {
12217         return SaveGame(stdout, 0, NULL);
12218     } else {
12219         for(i=0; i<10; i++) { // upto 10 tries
12220              f = fopen(filename, append ? "a" : "w");
12221              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12222              if(f || errno != 13) break;
12223              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12224              tot += t;
12225         }
12226         if (f == NULL) {
12227             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12228             DisplayError(buf, errno);
12229             return FALSE;
12230         } else {
12231             safeStrCpy(buf, lastMsg, MSG_SIZ);
12232             DisplayMessage(_("Waiting for access to save file"), "");
12233             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12234             DisplayMessage(_("Saving game"), "");
12235             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12236             result = SaveGame(f, 0, NULL);
12237             DisplayMessage(buf, "");
12238             return result;
12239         }
12240     }
12241 }
12242
12243 char *
12244 SavePart (char *str)
12245 {
12246     static char buf[MSG_SIZ];
12247     char *p;
12248
12249     p = strchr(str, ' ');
12250     if (p == NULL) return str;
12251     strncpy(buf, str, p - str);
12252     buf[p - str] = NULLCHAR;
12253     return buf;
12254 }
12255
12256 #define PGN_MAX_LINE 75
12257
12258 #define PGN_SIDE_WHITE  0
12259 #define PGN_SIDE_BLACK  1
12260
12261 static int
12262 FindFirstMoveOutOfBook (int side)
12263 {
12264     int result = -1;
12265
12266     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12267         int index = backwardMostMove;
12268         int has_book_hit = 0;
12269
12270         if( (index % 2) != side ) {
12271             index++;
12272         }
12273
12274         while( index < forwardMostMove ) {
12275             /* Check to see if engine is in book */
12276             int depth = pvInfoList[index].depth;
12277             int score = pvInfoList[index].score;
12278             int in_book = 0;
12279
12280             if( depth <= 2 ) {
12281                 in_book = 1;
12282             }
12283             else if( score == 0 && depth == 63 ) {
12284                 in_book = 1; /* Zappa */
12285             }
12286             else if( score == 2 && depth == 99 ) {
12287                 in_book = 1; /* Abrok */
12288             }
12289
12290             has_book_hit += in_book;
12291
12292             if( ! in_book ) {
12293                 result = index;
12294
12295                 break;
12296             }
12297
12298             index += 2;
12299         }
12300     }
12301
12302     return result;
12303 }
12304
12305 void
12306 GetOutOfBookInfo (char * buf)
12307 {
12308     int oob[2];
12309     int i;
12310     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12311
12312     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12313     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12314
12315     *buf = '\0';
12316
12317     if( oob[0] >= 0 || oob[1] >= 0 ) {
12318         for( i=0; i<2; i++ ) {
12319             int idx = oob[i];
12320
12321             if( idx >= 0 ) {
12322                 if( i > 0 && oob[0] >= 0 ) {
12323                     strcat( buf, "   " );
12324                 }
12325
12326                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12327                 sprintf( buf+strlen(buf), "%s%.2f",
12328                     pvInfoList[idx].score >= 0 ? "+" : "",
12329                     pvInfoList[idx].score / 100.0 );
12330             }
12331         }
12332     }
12333 }
12334
12335 /* Save game in PGN style and close the file */
12336 int
12337 SaveGamePGN (FILE *f)
12338 {
12339     int i, offset, linelen, newblock;
12340     time_t tm;
12341 //    char *movetext;
12342     char numtext[32];
12343     int movelen, numlen, blank;
12344     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12345
12346     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12347
12348     tm = time((time_t *) NULL);
12349
12350     PrintPGNTags(f, &gameInfo);
12351
12352     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12353
12354     if (backwardMostMove > 0 || startedFromSetupPosition) {
12355         char *fen = PositionToFEN(backwardMostMove, NULL);
12356         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12357         fprintf(f, "\n{--------------\n");
12358         PrintPosition(f, backwardMostMove);
12359         fprintf(f, "--------------}\n");
12360         free(fen);
12361     }
12362     else {
12363         /* [AS] Out of book annotation */
12364         if( appData.saveOutOfBookInfo ) {
12365             char buf[64];
12366
12367             GetOutOfBookInfo( buf );
12368
12369             if( buf[0] != '\0' ) {
12370                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12371             }
12372         }
12373
12374         fprintf(f, "\n");
12375     }
12376
12377     i = backwardMostMove;
12378     linelen = 0;
12379     newblock = TRUE;
12380
12381     while (i < forwardMostMove) {
12382         /* Print comments preceding this move */
12383         if (commentList[i] != NULL) {
12384             if (linelen > 0) fprintf(f, "\n");
12385             fprintf(f, "%s", commentList[i]);
12386             linelen = 0;
12387             newblock = TRUE;
12388         }
12389
12390         /* Format move number */
12391         if ((i % 2) == 0)
12392           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12393         else
12394           if (newblock)
12395             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12396           else
12397             numtext[0] = NULLCHAR;
12398
12399         numlen = strlen(numtext);
12400         newblock = FALSE;
12401
12402         /* Print move number */
12403         blank = linelen > 0 && numlen > 0;
12404         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12405             fprintf(f, "\n");
12406             linelen = 0;
12407             blank = 0;
12408         }
12409         if (blank) {
12410             fprintf(f, " ");
12411             linelen++;
12412         }
12413         fprintf(f, "%s", numtext);
12414         linelen += numlen;
12415
12416         /* Get move */
12417         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12418         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12419
12420         /* Print move */
12421         blank = linelen > 0 && movelen > 0;
12422         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12423             fprintf(f, "\n");
12424             linelen = 0;
12425             blank = 0;
12426         }
12427         if (blank) {
12428             fprintf(f, " ");
12429             linelen++;
12430         }
12431         fprintf(f, "%s", move_buffer);
12432         linelen += movelen;
12433
12434         /* [AS] Add PV info if present */
12435         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12436             /* [HGM] add time */
12437             char buf[MSG_SIZ]; int seconds;
12438
12439             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12440
12441             if( seconds <= 0)
12442               buf[0] = 0;
12443             else
12444               if( seconds < 30 )
12445                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12446               else
12447                 {
12448                   seconds = (seconds + 4)/10; // round to full seconds
12449                   if( seconds < 60 )
12450                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12451                   else
12452                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12453                 }
12454
12455             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12456                       pvInfoList[i].score >= 0 ? "+" : "",
12457                       pvInfoList[i].score / 100.0,
12458                       pvInfoList[i].depth,
12459                       buf );
12460
12461             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12462
12463             /* Print score/depth */
12464             blank = linelen > 0 && movelen > 0;
12465             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12466                 fprintf(f, "\n");
12467                 linelen = 0;
12468                 blank = 0;
12469             }
12470             if (blank) {
12471                 fprintf(f, " ");
12472                 linelen++;
12473             }
12474             fprintf(f, "%s", move_buffer);
12475             linelen += movelen;
12476         }
12477
12478         i++;
12479     }
12480
12481     /* Start a new line */
12482     if (linelen > 0) fprintf(f, "\n");
12483
12484     /* Print comments after last move */
12485     if (commentList[i] != NULL) {
12486         fprintf(f, "%s\n", commentList[i]);
12487     }
12488
12489     /* Print result */
12490     if (gameInfo.resultDetails != NULL &&
12491         gameInfo.resultDetails[0] != NULLCHAR) {
12492         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12493                 PGNResult(gameInfo.result));
12494     } else {
12495         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12496     }
12497
12498     fclose(f);
12499     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12500     return TRUE;
12501 }
12502
12503 /* Save game in old style and close the file */
12504 int
12505 SaveGameOldStyle (FILE *f)
12506 {
12507     int i, offset;
12508     time_t tm;
12509
12510     tm = time((time_t *) NULL);
12511
12512     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12513     PrintOpponents(f);
12514
12515     if (backwardMostMove > 0 || startedFromSetupPosition) {
12516         fprintf(f, "\n[--------------\n");
12517         PrintPosition(f, backwardMostMove);
12518         fprintf(f, "--------------]\n");
12519     } else {
12520         fprintf(f, "\n");
12521     }
12522
12523     i = backwardMostMove;
12524     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12525
12526     while (i < forwardMostMove) {
12527         if (commentList[i] != NULL) {
12528             fprintf(f, "[%s]\n", commentList[i]);
12529         }
12530
12531         if ((i % 2) == 1) {
12532             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12533             i++;
12534         } else {
12535             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12536             i++;
12537             if (commentList[i] != NULL) {
12538                 fprintf(f, "\n");
12539                 continue;
12540             }
12541             if (i >= forwardMostMove) {
12542                 fprintf(f, "\n");
12543                 break;
12544             }
12545             fprintf(f, "%s\n", parseList[i]);
12546             i++;
12547         }
12548     }
12549
12550     if (commentList[i] != NULL) {
12551         fprintf(f, "[%s]\n", commentList[i]);
12552     }
12553
12554     /* This isn't really the old style, but it's close enough */
12555     if (gameInfo.resultDetails != NULL &&
12556         gameInfo.resultDetails[0] != NULLCHAR) {
12557         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12558                 gameInfo.resultDetails);
12559     } else {
12560         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12561     }
12562
12563     fclose(f);
12564     return TRUE;
12565 }
12566
12567 /* Save the current game to open file f and close the file */
12568 int
12569 SaveGame (FILE *f, int dummy, char *dummy2)
12570 {
12571     if (gameMode == EditPosition) EditPositionDone(TRUE);
12572     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12573     if (appData.oldSaveStyle)
12574       return SaveGameOldStyle(f);
12575     else
12576       return SaveGamePGN(f);
12577 }
12578
12579 /* Save the current position to the given file */
12580 int
12581 SavePositionToFile (char *filename)
12582 {
12583     FILE *f;
12584     char buf[MSG_SIZ];
12585
12586     if (strcmp(filename, "-") == 0) {
12587         return SavePosition(stdout, 0, NULL);
12588     } else {
12589         f = fopen(filename, "a");
12590         if (f == NULL) {
12591             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12592             DisplayError(buf, errno);
12593             return FALSE;
12594         } else {
12595             safeStrCpy(buf, lastMsg, MSG_SIZ);
12596             DisplayMessage(_("Waiting for access to save file"), "");
12597             flock(fileno(f), LOCK_EX); // [HGM] lock
12598             DisplayMessage(_("Saving position"), "");
12599             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12600             SavePosition(f, 0, NULL);
12601             DisplayMessage(buf, "");
12602             return TRUE;
12603         }
12604     }
12605 }
12606
12607 /* Save the current position to the given open file and close the file */
12608 int
12609 SavePosition (FILE *f, int dummy, char *dummy2)
12610 {
12611     time_t tm;
12612     char *fen;
12613
12614     if (gameMode == EditPosition) EditPositionDone(TRUE);
12615     if (appData.oldSaveStyle) {
12616         tm = time((time_t *) NULL);
12617
12618         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12619         PrintOpponents(f);
12620         fprintf(f, "[--------------\n");
12621         PrintPosition(f, currentMove);
12622         fprintf(f, "--------------]\n");
12623     } else {
12624         fen = PositionToFEN(currentMove, NULL);
12625         fprintf(f, "%s\n", fen);
12626         free(fen);
12627     }
12628     fclose(f);
12629     return TRUE;
12630 }
12631
12632 void
12633 ReloadCmailMsgEvent (int unregister)
12634 {
12635 #if !WIN32
12636     static char *inFilename = NULL;
12637     static char *outFilename;
12638     int i;
12639     struct stat inbuf, outbuf;
12640     int status;
12641
12642     /* Any registered moves are unregistered if unregister is set, */
12643     /* i.e. invoked by the signal handler */
12644     if (unregister) {
12645         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12646             cmailMoveRegistered[i] = FALSE;
12647             if (cmailCommentList[i] != NULL) {
12648                 free(cmailCommentList[i]);
12649                 cmailCommentList[i] = NULL;
12650             }
12651         }
12652         nCmailMovesRegistered = 0;
12653     }
12654
12655     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12656         cmailResult[i] = CMAIL_NOT_RESULT;
12657     }
12658     nCmailResults = 0;
12659
12660     if (inFilename == NULL) {
12661         /* Because the filenames are static they only get malloced once  */
12662         /* and they never get freed                                      */
12663         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12664         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12665
12666         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12667         sprintf(outFilename, "%s.out", appData.cmailGameName);
12668     }
12669
12670     status = stat(outFilename, &outbuf);
12671     if (status < 0) {
12672         cmailMailedMove = FALSE;
12673     } else {
12674         status = stat(inFilename, &inbuf);
12675         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12676     }
12677
12678     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12679        counts the games, notes how each one terminated, etc.
12680
12681        It would be nice to remove this kludge and instead gather all
12682        the information while building the game list.  (And to keep it
12683        in the game list nodes instead of having a bunch of fixed-size
12684        parallel arrays.)  Note this will require getting each game's
12685        termination from the PGN tags, as the game list builder does
12686        not process the game moves.  --mann
12687        */
12688     cmailMsgLoaded = TRUE;
12689     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12690
12691     /* Load first game in the file or popup game menu */
12692     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12693
12694 #endif /* !WIN32 */
12695     return;
12696 }
12697
12698 int
12699 RegisterMove ()
12700 {
12701     FILE *f;
12702     char string[MSG_SIZ];
12703
12704     if (   cmailMailedMove
12705         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12706         return TRUE;            /* Allow free viewing  */
12707     }
12708
12709     /* Unregister move to ensure that we don't leave RegisterMove        */
12710     /* with the move registered when the conditions for registering no   */
12711     /* longer hold                                                       */
12712     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12713         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12714         nCmailMovesRegistered --;
12715
12716         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12717           {
12718               free(cmailCommentList[lastLoadGameNumber - 1]);
12719               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12720           }
12721     }
12722
12723     if (cmailOldMove == -1) {
12724         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12725         return FALSE;
12726     }
12727
12728     if (currentMove > cmailOldMove + 1) {
12729         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12730         return FALSE;
12731     }
12732
12733     if (currentMove < cmailOldMove) {
12734         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12735         return FALSE;
12736     }
12737
12738     if (forwardMostMove > currentMove) {
12739         /* Silently truncate extra moves */
12740         TruncateGame();
12741     }
12742
12743     if (   (currentMove == cmailOldMove + 1)
12744         || (   (currentMove == cmailOldMove)
12745             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12746                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12747         if (gameInfo.result != GameUnfinished) {
12748             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12749         }
12750
12751         if (commentList[currentMove] != NULL) {
12752             cmailCommentList[lastLoadGameNumber - 1]
12753               = StrSave(commentList[currentMove]);
12754         }
12755         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12756
12757         if (appData.debugMode)
12758           fprintf(debugFP, "Saving %s for game %d\n",
12759                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12760
12761         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12762
12763         f = fopen(string, "w");
12764         if (appData.oldSaveStyle) {
12765             SaveGameOldStyle(f); /* also closes the file */
12766
12767             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12768             f = fopen(string, "w");
12769             SavePosition(f, 0, NULL); /* also closes the file */
12770         } else {
12771             fprintf(f, "{--------------\n");
12772             PrintPosition(f, currentMove);
12773             fprintf(f, "--------------}\n\n");
12774
12775             SaveGame(f, 0, NULL); /* also closes the file*/
12776         }
12777
12778         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12779         nCmailMovesRegistered ++;
12780     } else if (nCmailGames == 1) {
12781         DisplayError(_("You have not made a move yet"), 0);
12782         return FALSE;
12783     }
12784
12785     return TRUE;
12786 }
12787
12788 void
12789 MailMoveEvent ()
12790 {
12791 #if !WIN32
12792     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12793     FILE *commandOutput;
12794     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12795     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12796     int nBuffers;
12797     int i;
12798     int archived;
12799     char *arcDir;
12800
12801     if (! cmailMsgLoaded) {
12802         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12803         return;
12804     }
12805
12806     if (nCmailGames == nCmailResults) {
12807         DisplayError(_("No unfinished games"), 0);
12808         return;
12809     }
12810
12811 #if CMAIL_PROHIBIT_REMAIL
12812     if (cmailMailedMove) {
12813       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);
12814         DisplayError(msg, 0);
12815         return;
12816     }
12817 #endif
12818
12819     if (! (cmailMailedMove || RegisterMove())) return;
12820
12821     if (   cmailMailedMove
12822         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12823       snprintf(string, MSG_SIZ, partCommandString,
12824                appData.debugMode ? " -v" : "", appData.cmailGameName);
12825         commandOutput = popen(string, "r");
12826
12827         if (commandOutput == NULL) {
12828             DisplayError(_("Failed to invoke cmail"), 0);
12829         } else {
12830             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12831                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12832             }
12833             if (nBuffers > 1) {
12834                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12835                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12836                 nBytes = MSG_SIZ - 1;
12837             } else {
12838                 (void) memcpy(msg, buffer, nBytes);
12839             }
12840             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12841
12842             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12843                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12844
12845                 archived = TRUE;
12846                 for (i = 0; i < nCmailGames; i ++) {
12847                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12848                         archived = FALSE;
12849                     }
12850                 }
12851                 if (   archived
12852                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12853                         != NULL)) {
12854                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12855                            arcDir,
12856                            appData.cmailGameName,
12857                            gameInfo.date);
12858                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12859                     cmailMsgLoaded = FALSE;
12860                 }
12861             }
12862
12863             DisplayInformation(msg);
12864             pclose(commandOutput);
12865         }
12866     } else {
12867         if ((*cmailMsg) != '\0') {
12868             DisplayInformation(cmailMsg);
12869         }
12870     }
12871
12872     return;
12873 #endif /* !WIN32 */
12874 }
12875
12876 char *
12877 CmailMsg ()
12878 {
12879 #if WIN32
12880     return NULL;
12881 #else
12882     int  prependComma = 0;
12883     char number[5];
12884     char string[MSG_SIZ];       /* Space for game-list */
12885     int  i;
12886
12887     if (!cmailMsgLoaded) return "";
12888
12889     if (cmailMailedMove) {
12890       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12891     } else {
12892         /* Create a list of games left */
12893       snprintf(string, MSG_SIZ, "[");
12894         for (i = 0; i < nCmailGames; i ++) {
12895             if (! (   cmailMoveRegistered[i]
12896                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12897                 if (prependComma) {
12898                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12899                 } else {
12900                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12901                     prependComma = 1;
12902                 }
12903
12904                 strcat(string, number);
12905             }
12906         }
12907         strcat(string, "]");
12908
12909         if (nCmailMovesRegistered + nCmailResults == 0) {
12910             switch (nCmailGames) {
12911               case 1:
12912                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12913                 break;
12914
12915               case 2:
12916                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12917                 break;
12918
12919               default:
12920                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12921                          nCmailGames);
12922                 break;
12923             }
12924         } else {
12925             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12926               case 1:
12927                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12928                          string);
12929                 break;
12930
12931               case 0:
12932                 if (nCmailResults == nCmailGames) {
12933                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12934                 } else {
12935                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12936                 }
12937                 break;
12938
12939               default:
12940                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12941                          string);
12942             }
12943         }
12944     }
12945     return cmailMsg;
12946 #endif /* WIN32 */
12947 }
12948
12949 void
12950 ResetGameEvent ()
12951 {
12952     if (gameMode == Training)
12953       SetTrainingModeOff();
12954
12955     Reset(TRUE, TRUE);
12956     cmailMsgLoaded = FALSE;
12957     if (appData.icsActive) {
12958       SendToICS(ics_prefix);
12959       SendToICS("refresh\n");
12960     }
12961 }
12962
12963 void
12964 ExitEvent (int status)
12965 {
12966     exiting++;
12967     if (exiting > 2) {
12968       /* Give up on clean exit */
12969       exit(status);
12970     }
12971     if (exiting > 1) {
12972       /* Keep trying for clean exit */
12973       return;
12974     }
12975
12976     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12977
12978     if (telnetISR != NULL) {
12979       RemoveInputSource(telnetISR);
12980     }
12981     if (icsPR != NoProc) {
12982       DestroyChildProcess(icsPR, TRUE);
12983     }
12984
12985     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12986     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12987
12988     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12989     /* make sure this other one finishes before killing it!                  */
12990     if(endingGame) { int count = 0;
12991         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12992         while(endingGame && count++ < 10) DoSleep(1);
12993         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12994     }
12995
12996     /* Kill off chess programs */
12997     if (first.pr != NoProc) {
12998         ExitAnalyzeMode();
12999
13000         DoSleep( appData.delayBeforeQuit );
13001         SendToProgram("quit\n", &first);
13002         DoSleep( appData.delayAfterQuit );
13003         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13004     }
13005     if (second.pr != NoProc) {
13006         DoSleep( appData.delayBeforeQuit );
13007         SendToProgram("quit\n", &second);
13008         DoSleep( appData.delayAfterQuit );
13009         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13010     }
13011     if (first.isr != NULL) {
13012         RemoveInputSource(first.isr);
13013     }
13014     if (second.isr != NULL) {
13015         RemoveInputSource(second.isr);
13016     }
13017
13018     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13019     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13020
13021     ShutDownFrontEnd();
13022     exit(status);
13023 }
13024
13025 void
13026 PauseEvent ()
13027 {
13028     if (appData.debugMode)
13029         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13030     if (pausing) {
13031         pausing = FALSE;
13032         ModeHighlight();
13033         if (gameMode == MachinePlaysWhite ||
13034             gameMode == MachinePlaysBlack) {
13035             StartClocks();
13036         } else {
13037             DisplayBothClocks();
13038         }
13039         if (gameMode == PlayFromGameFile) {
13040             if (appData.timeDelay >= 0)
13041                 AutoPlayGameLoop();
13042         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13043             Reset(FALSE, TRUE);
13044             SendToICS(ics_prefix);
13045             SendToICS("refresh\n");
13046         } else if (currentMove < forwardMostMove) {
13047             ForwardInner(forwardMostMove);
13048         }
13049         pauseExamInvalid = FALSE;
13050     } else {
13051         switch (gameMode) {
13052           default:
13053             return;
13054           case IcsExamining:
13055             pauseExamForwardMostMove = forwardMostMove;
13056             pauseExamInvalid = FALSE;
13057             /* fall through */
13058           case IcsObserving:
13059           case IcsPlayingWhite:
13060           case IcsPlayingBlack:
13061             pausing = TRUE;
13062             ModeHighlight();
13063             return;
13064           case PlayFromGameFile:
13065             (void) StopLoadGameTimer();
13066             pausing = TRUE;
13067             ModeHighlight();
13068             break;
13069           case BeginningOfGame:
13070             if (appData.icsActive) return;
13071             /* else fall through */
13072           case MachinePlaysWhite:
13073           case MachinePlaysBlack:
13074           case TwoMachinesPlay:
13075             if (forwardMostMove == 0)
13076               return;           /* don't pause if no one has moved */
13077             if ((gameMode == MachinePlaysWhite &&
13078                  !WhiteOnMove(forwardMostMove)) ||
13079                 (gameMode == MachinePlaysBlack &&
13080                  WhiteOnMove(forwardMostMove))) {
13081                 StopClocks();
13082             }
13083             pausing = TRUE;
13084             ModeHighlight();
13085             break;
13086         }
13087     }
13088 }
13089
13090 void
13091 EditCommentEvent ()
13092 {
13093     char title[MSG_SIZ];
13094
13095     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13096       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13097     } else {
13098       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13099                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13100                parseList[currentMove - 1]);
13101     }
13102
13103     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13104 }
13105
13106
13107 void
13108 EditTagsEvent ()
13109 {
13110     char *tags = PGNTags(&gameInfo);
13111     bookUp = FALSE;
13112     EditTagsPopUp(tags, NULL);
13113     free(tags);
13114 }
13115
13116 void
13117 AnalyzeModeEvent ()
13118 {
13119     if (appData.noChessProgram || gameMode == AnalyzeMode)
13120       return;
13121
13122     if (gameMode != AnalyzeFile) {
13123         if (!appData.icsEngineAnalyze) {
13124                EditGameEvent();
13125                if (gameMode != EditGame) return;
13126         }
13127         ResurrectChessProgram();
13128         SendToProgram("analyze\n", &first);
13129         first.analyzing = TRUE;
13130         /*first.maybeThinking = TRUE;*/
13131         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13132         EngineOutputPopUp();
13133     }
13134     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13135     pausing = FALSE;
13136     ModeHighlight();
13137     SetGameInfo();
13138
13139     StartAnalysisClock();
13140     GetTimeMark(&lastNodeCountTime);
13141     lastNodeCount = 0;
13142 }
13143
13144 void
13145 AnalyzeFileEvent ()
13146 {
13147     if (appData.noChessProgram || gameMode == AnalyzeFile)
13148       return;
13149
13150     if (gameMode != AnalyzeMode) {
13151         EditGameEvent();
13152         if (gameMode != EditGame) return;
13153         ResurrectChessProgram();
13154         SendToProgram("analyze\n", &first);
13155         first.analyzing = TRUE;
13156         /*first.maybeThinking = TRUE;*/
13157         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13158         EngineOutputPopUp();
13159     }
13160     gameMode = AnalyzeFile;
13161     pausing = FALSE;
13162     ModeHighlight();
13163     SetGameInfo();
13164
13165     StartAnalysisClock();
13166     GetTimeMark(&lastNodeCountTime);
13167     lastNodeCount = 0;
13168     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13169 }
13170
13171 void
13172 MachineWhiteEvent ()
13173 {
13174     char buf[MSG_SIZ];
13175     char *bookHit = NULL;
13176
13177     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13178       return;
13179
13180
13181     if (gameMode == PlayFromGameFile ||
13182         gameMode == TwoMachinesPlay  ||
13183         gameMode == Training         ||
13184         gameMode == AnalyzeMode      ||
13185         gameMode == EndOfGame)
13186         EditGameEvent();
13187
13188     if (gameMode == EditPosition)
13189         EditPositionDone(TRUE);
13190
13191     if (!WhiteOnMove(currentMove)) {
13192         DisplayError(_("It is not White's turn"), 0);
13193         return;
13194     }
13195
13196     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13197       ExitAnalyzeMode();
13198
13199     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13200         gameMode == AnalyzeFile)
13201         TruncateGame();
13202
13203     ResurrectChessProgram();    /* in case it isn't running */
13204     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13205         gameMode = MachinePlaysWhite;
13206         ResetClocks();
13207     } else
13208     gameMode = MachinePlaysWhite;
13209     pausing = FALSE;
13210     ModeHighlight();
13211     SetGameInfo();
13212     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13213     DisplayTitle(buf);
13214     if (first.sendName) {
13215       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13216       SendToProgram(buf, &first);
13217     }
13218     if (first.sendTime) {
13219       if (first.useColors) {
13220         SendToProgram("black\n", &first); /*gnu kludge*/
13221       }
13222       SendTimeRemaining(&first, TRUE);
13223     }
13224     if (first.useColors) {
13225       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13226     }
13227     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13228     SetMachineThinkingEnables();
13229     first.maybeThinking = TRUE;
13230     StartClocks();
13231     firstMove = FALSE;
13232
13233     if (appData.autoFlipView && !flipView) {
13234       flipView = !flipView;
13235       DrawPosition(FALSE, NULL);
13236       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13237     }
13238
13239     if(bookHit) { // [HGM] book: simulate book reply
13240         static char bookMove[MSG_SIZ]; // a bit generous?
13241
13242         programStats.nodes = programStats.depth = programStats.time =
13243         programStats.score = programStats.got_only_move = 0;
13244         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13245
13246         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13247         strcat(bookMove, bookHit);
13248         HandleMachineMove(bookMove, &first);
13249     }
13250 }
13251
13252 void
13253 MachineBlackEvent ()
13254 {
13255   char buf[MSG_SIZ];
13256   char *bookHit = NULL;
13257
13258     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13259         return;
13260
13261
13262     if (gameMode == PlayFromGameFile ||
13263         gameMode == TwoMachinesPlay  ||
13264         gameMode == Training         ||
13265         gameMode == AnalyzeMode      ||
13266         gameMode == EndOfGame)
13267         EditGameEvent();
13268
13269     if (gameMode == EditPosition)
13270         EditPositionDone(TRUE);
13271
13272     if (WhiteOnMove(currentMove)) {
13273         DisplayError(_("It is not Black's turn"), 0);
13274         return;
13275     }
13276
13277     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13278       ExitAnalyzeMode();
13279
13280     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13281         gameMode == AnalyzeFile)
13282         TruncateGame();
13283
13284     ResurrectChessProgram();    /* in case it isn't running */
13285     gameMode = MachinePlaysBlack;
13286     pausing = FALSE;
13287     ModeHighlight();
13288     SetGameInfo();
13289     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13290     DisplayTitle(buf);
13291     if (first.sendName) {
13292       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13293       SendToProgram(buf, &first);
13294     }
13295     if (first.sendTime) {
13296       if (first.useColors) {
13297         SendToProgram("white\n", &first); /*gnu kludge*/
13298       }
13299       SendTimeRemaining(&first, FALSE);
13300     }
13301     if (first.useColors) {
13302       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13303     }
13304     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13305     SetMachineThinkingEnables();
13306     first.maybeThinking = TRUE;
13307     StartClocks();
13308
13309     if (appData.autoFlipView && flipView) {
13310       flipView = !flipView;
13311       DrawPosition(FALSE, NULL);
13312       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13313     }
13314     if(bookHit) { // [HGM] book: simulate book reply
13315         static char bookMove[MSG_SIZ]; // a bit generous?
13316
13317         programStats.nodes = programStats.depth = programStats.time =
13318         programStats.score = programStats.got_only_move = 0;
13319         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13320
13321         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13322         strcat(bookMove, bookHit);
13323         HandleMachineMove(bookMove, &first);
13324     }
13325 }
13326
13327
13328 void
13329 DisplayTwoMachinesTitle ()
13330 {
13331     char buf[MSG_SIZ];
13332     if (appData.matchGames > 0) {
13333         if(appData.tourneyFile[0]) {
13334           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13335                    gameInfo.white, _("vs."), gameInfo.black,
13336                    nextGame+1, appData.matchGames+1,
13337                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13338         } else 
13339         if (first.twoMachinesColor[0] == 'w') {
13340           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13341                    gameInfo.white, _("vs."),  gameInfo.black,
13342                    first.matchWins, second.matchWins,
13343                    matchGame - 1 - (first.matchWins + second.matchWins));
13344         } else {
13345           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13346                    gameInfo.white, _("vs."), gameInfo.black,
13347                    second.matchWins, first.matchWins,
13348                    matchGame - 1 - (first.matchWins + second.matchWins));
13349         }
13350     } else {
13351       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13352     }
13353     DisplayTitle(buf);
13354 }
13355
13356 void
13357 SettingsMenuIfReady ()
13358 {
13359   if (second.lastPing != second.lastPong) {
13360     DisplayMessage("", _("Waiting for second chess program"));
13361     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13362     return;
13363   }
13364   ThawUI();
13365   DisplayMessage("", "");
13366   SettingsPopUp(&second);
13367 }
13368
13369 int
13370 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13371 {
13372     char buf[MSG_SIZ];
13373     if (cps->pr == NoProc) {
13374         StartChessProgram(cps);
13375         if (cps->protocolVersion == 1) {
13376           retry();
13377         } else {
13378           /* kludge: allow timeout for initial "feature" command */
13379           FreezeUI();
13380           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13381           DisplayMessage("", buf);
13382           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13383         }
13384         return 1;
13385     }
13386     return 0;
13387 }
13388
13389 void
13390 TwoMachinesEvent P((void))
13391 {
13392     int i;
13393     char buf[MSG_SIZ];
13394     ChessProgramState *onmove;
13395     char *bookHit = NULL;
13396     static int stalling = 0;
13397     TimeMark now;
13398     long wait;
13399
13400     if (appData.noChessProgram) return;
13401
13402     switch (gameMode) {
13403       case TwoMachinesPlay:
13404         return;
13405       case MachinePlaysWhite:
13406       case MachinePlaysBlack:
13407         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13408             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13409             return;
13410         }
13411         /* fall through */
13412       case BeginningOfGame:
13413       case PlayFromGameFile:
13414       case EndOfGame:
13415         EditGameEvent();
13416         if (gameMode != EditGame) return;
13417         break;
13418       case EditPosition:
13419         EditPositionDone(TRUE);
13420         break;
13421       case AnalyzeMode:
13422       case AnalyzeFile:
13423         ExitAnalyzeMode();
13424         break;
13425       case EditGame:
13426       default:
13427         break;
13428     }
13429
13430 //    forwardMostMove = currentMove;
13431     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13432
13433     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13434
13435     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13436     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13437       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13438       return;
13439     }
13440     if(!stalling) {
13441       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13442       SendToProgram("force\n", &second);
13443       stalling = 1;
13444       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13445       return;
13446     }
13447     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13448     if(appData.matchPause>10000 || appData.matchPause<10)
13449                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13450     wait = SubtractTimeMarks(&now, &pauseStart);
13451     if(wait < appData.matchPause) {
13452         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13453         return;
13454     }
13455     // we are now committed to starting the game
13456     stalling = 0;
13457     DisplayMessage("", "");
13458     if (startedFromSetupPosition) {
13459         SendBoard(&second, backwardMostMove);
13460     if (appData.debugMode) {
13461         fprintf(debugFP, "Two Machines\n");
13462     }
13463     }
13464     for (i = backwardMostMove; i < forwardMostMove; i++) {
13465         SendMoveToProgram(i, &second);
13466     }
13467
13468     gameMode = TwoMachinesPlay;
13469     pausing = FALSE;
13470     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13471     SetGameInfo();
13472     DisplayTwoMachinesTitle();
13473     firstMove = TRUE;
13474     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13475         onmove = &first;
13476     } else {
13477         onmove = &second;
13478     }
13479     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13480     SendToProgram(first.computerString, &first);
13481     if (first.sendName) {
13482       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13483       SendToProgram(buf, &first);
13484     }
13485     SendToProgram(second.computerString, &second);
13486     if (second.sendName) {
13487       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13488       SendToProgram(buf, &second);
13489     }
13490
13491     ResetClocks();
13492     if (!first.sendTime || !second.sendTime) {
13493         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13494         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13495     }
13496     if (onmove->sendTime) {
13497       if (onmove->useColors) {
13498         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13499       }
13500       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13501     }
13502     if (onmove->useColors) {
13503       SendToProgram(onmove->twoMachinesColor, onmove);
13504     }
13505     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13506 //    SendToProgram("go\n", onmove);
13507     onmove->maybeThinking = TRUE;
13508     SetMachineThinkingEnables();
13509
13510     StartClocks();
13511
13512     if(bookHit) { // [HGM] book: simulate book reply
13513         static char bookMove[MSG_SIZ]; // a bit generous?
13514
13515         programStats.nodes = programStats.depth = programStats.time =
13516         programStats.score = programStats.got_only_move = 0;
13517         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13518
13519         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13520         strcat(bookMove, bookHit);
13521         savedMessage = bookMove; // args for deferred call
13522         savedState = onmove;
13523         ScheduleDelayedEvent(DeferredBookMove, 1);
13524     }
13525 }
13526
13527 void
13528 TrainingEvent ()
13529 {
13530     if (gameMode == Training) {
13531       SetTrainingModeOff();
13532       gameMode = PlayFromGameFile;
13533       DisplayMessage("", _("Training mode off"));
13534     } else {
13535       gameMode = Training;
13536       animateTraining = appData.animate;
13537
13538       /* make sure we are not already at the end of the game */
13539       if (currentMove < forwardMostMove) {
13540         SetTrainingModeOn();
13541         DisplayMessage("", _("Training mode on"));
13542       } else {
13543         gameMode = PlayFromGameFile;
13544         DisplayError(_("Already at end of game"), 0);
13545       }
13546     }
13547     ModeHighlight();
13548 }
13549
13550 void
13551 IcsClientEvent ()
13552 {
13553     if (!appData.icsActive) return;
13554     switch (gameMode) {
13555       case IcsPlayingWhite:
13556       case IcsPlayingBlack:
13557       case IcsObserving:
13558       case IcsIdle:
13559       case BeginningOfGame:
13560       case IcsExamining:
13561         return;
13562
13563       case EditGame:
13564         break;
13565
13566       case EditPosition:
13567         EditPositionDone(TRUE);
13568         break;
13569
13570       case AnalyzeMode:
13571       case AnalyzeFile:
13572         ExitAnalyzeMode();
13573         break;
13574
13575       default:
13576         EditGameEvent();
13577         break;
13578     }
13579
13580     gameMode = IcsIdle;
13581     ModeHighlight();
13582     return;
13583 }
13584
13585 void
13586 EditGameEvent ()
13587 {
13588     int i;
13589
13590     switch (gameMode) {
13591       case Training:
13592         SetTrainingModeOff();
13593         break;
13594       case MachinePlaysWhite:
13595       case MachinePlaysBlack:
13596       case BeginningOfGame:
13597         SendToProgram("force\n", &first);
13598         SetUserThinkingEnables();
13599         break;
13600       case PlayFromGameFile:
13601         (void) StopLoadGameTimer();
13602         if (gameFileFP != NULL) {
13603             gameFileFP = NULL;
13604         }
13605         break;
13606       case EditPosition:
13607         EditPositionDone(TRUE);
13608         break;
13609       case AnalyzeMode:
13610       case AnalyzeFile:
13611         ExitAnalyzeMode();
13612         SendToProgram("force\n", &first);
13613         break;
13614       case TwoMachinesPlay:
13615         GameEnds(EndOfFile, NULL, GE_PLAYER);
13616         ResurrectChessProgram();
13617         SetUserThinkingEnables();
13618         break;
13619       case EndOfGame:
13620         ResurrectChessProgram();
13621         break;
13622       case IcsPlayingBlack:
13623       case IcsPlayingWhite:
13624         DisplayError(_("Warning: You are still playing a game"), 0);
13625         break;
13626       case IcsObserving:
13627         DisplayError(_("Warning: You are still observing a game"), 0);
13628         break;
13629       case IcsExamining:
13630         DisplayError(_("Warning: You are still examining a game"), 0);
13631         break;
13632       case IcsIdle:
13633         break;
13634       case EditGame:
13635       default:
13636         return;
13637     }
13638
13639     pausing = FALSE;
13640     StopClocks();
13641     first.offeredDraw = second.offeredDraw = 0;
13642
13643     if (gameMode == PlayFromGameFile) {
13644         whiteTimeRemaining = timeRemaining[0][currentMove];
13645         blackTimeRemaining = timeRemaining[1][currentMove];
13646         DisplayTitle("");
13647     }
13648
13649     if (gameMode == MachinePlaysWhite ||
13650         gameMode == MachinePlaysBlack ||
13651         gameMode == TwoMachinesPlay ||
13652         gameMode == EndOfGame) {
13653         i = forwardMostMove;
13654         while (i > currentMove) {
13655             SendToProgram("undo\n", &first);
13656             i--;
13657         }
13658         if(!adjustedClock) {
13659         whiteTimeRemaining = timeRemaining[0][currentMove];
13660         blackTimeRemaining = timeRemaining[1][currentMove];
13661         DisplayBothClocks();
13662         }
13663         if (whiteFlag || blackFlag) {
13664             whiteFlag = blackFlag = 0;
13665         }
13666         DisplayTitle("");
13667     }
13668
13669     gameMode = EditGame;
13670     ModeHighlight();
13671     SetGameInfo();
13672 }
13673
13674
13675 void
13676 EditPositionEvent ()
13677 {
13678     if (gameMode == EditPosition) {
13679         EditGameEvent();
13680         return;
13681     }
13682
13683     EditGameEvent();
13684     if (gameMode != EditGame) return;
13685
13686     gameMode = EditPosition;
13687     ModeHighlight();
13688     SetGameInfo();
13689     if (currentMove > 0)
13690       CopyBoard(boards[0], boards[currentMove]);
13691
13692     blackPlaysFirst = !WhiteOnMove(currentMove);
13693     ResetClocks();
13694     currentMove = forwardMostMove = backwardMostMove = 0;
13695     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13696     DisplayMove(-1);
13697     if(!appData.pieceMenu) DisplayMessage("Click clock to clear board", "");
13698 }
13699
13700 void
13701 ExitAnalyzeMode ()
13702 {
13703     /* [DM] icsEngineAnalyze - possible call from other functions */
13704     if (appData.icsEngineAnalyze) {
13705         appData.icsEngineAnalyze = FALSE;
13706
13707         DisplayMessage("",_("Close ICS engine analyze..."));
13708     }
13709     if (first.analysisSupport && first.analyzing) {
13710       SendToProgram("exit\n", &first);
13711       first.analyzing = FALSE;
13712     }
13713     thinkOutput[0] = NULLCHAR;
13714 }
13715
13716 void
13717 EditPositionDone (Boolean fakeRights)
13718 {
13719     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13720
13721     startedFromSetupPosition = TRUE;
13722     InitChessProgram(&first, FALSE);
13723     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13724       boards[0][EP_STATUS] = EP_NONE;
13725       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13726     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13727         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13728         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13729       } else boards[0][CASTLING][2] = NoRights;
13730     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13731         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13732         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13733       } else boards[0][CASTLING][5] = NoRights;
13734     }
13735     SendToProgram("force\n", &first);
13736     if (blackPlaysFirst) {
13737         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13738         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13739         currentMove = forwardMostMove = backwardMostMove = 1;
13740         CopyBoard(boards[1], boards[0]);
13741     } else {
13742         currentMove = forwardMostMove = backwardMostMove = 0;
13743     }
13744     SendBoard(&first, forwardMostMove);
13745     if (appData.debugMode) {
13746         fprintf(debugFP, "EditPosDone\n");
13747     }
13748     DisplayTitle("");
13749     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13750     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13751     gameMode = EditGame;
13752     ModeHighlight();
13753     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13754     ClearHighlights(); /* [AS] */
13755 }
13756
13757 /* Pause for `ms' milliseconds */
13758 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13759 void
13760 TimeDelay (long ms)
13761 {
13762     TimeMark m1, m2;
13763
13764     GetTimeMark(&m1);
13765     do {
13766         GetTimeMark(&m2);
13767     } while (SubtractTimeMarks(&m2, &m1) < ms);
13768 }
13769
13770 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13771 void
13772 SendMultiLineToICS (char *buf)
13773 {
13774     char temp[MSG_SIZ+1], *p;
13775     int len;
13776
13777     len = strlen(buf);
13778     if (len > MSG_SIZ)
13779       len = MSG_SIZ;
13780
13781     strncpy(temp, buf, len);
13782     temp[len] = 0;
13783
13784     p = temp;
13785     while (*p) {
13786         if (*p == '\n' || *p == '\r')
13787           *p = ' ';
13788         ++p;
13789     }
13790
13791     strcat(temp, "\n");
13792     SendToICS(temp);
13793     SendToPlayer(temp, strlen(temp));
13794 }
13795
13796 void
13797 SetWhiteToPlayEvent ()
13798 {
13799     if (gameMode == EditPosition) {
13800         blackPlaysFirst = FALSE;
13801         DisplayBothClocks();    /* works because currentMove is 0 */
13802     } else if (gameMode == IcsExamining) {
13803         SendToICS(ics_prefix);
13804         SendToICS("tomove white\n");
13805     }
13806 }
13807
13808 void
13809 SetBlackToPlayEvent ()
13810 {
13811     if (gameMode == EditPosition) {
13812         blackPlaysFirst = TRUE;
13813         currentMove = 1;        /* kludge */
13814         DisplayBothClocks();
13815         currentMove = 0;
13816     } else if (gameMode == IcsExamining) {
13817         SendToICS(ics_prefix);
13818         SendToICS("tomove black\n");
13819     }
13820 }
13821
13822 void
13823 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13824 {
13825     char buf[MSG_SIZ];
13826     ChessSquare piece = boards[0][y][x];
13827
13828     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13829
13830     switch (selection) {
13831       case ClearBoard:
13832         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13833             SendToICS(ics_prefix);
13834             SendToICS("bsetup clear\n");
13835         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13836             SendToICS(ics_prefix);
13837             SendToICS("clearboard\n");
13838         } else {
13839             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13840                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13841                 for (y = 0; y < BOARD_HEIGHT; y++) {
13842                     if (gameMode == IcsExamining) {
13843                         if (boards[currentMove][y][x] != EmptySquare) {
13844                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13845                                     AAA + x, ONE + y);
13846                             SendToICS(buf);
13847                         }
13848                     } else {
13849                         boards[0][y][x] = p;
13850                     }
13851                 }
13852             }
13853         }
13854         if (gameMode == EditPosition) {
13855             DrawPosition(FALSE, boards[0]);
13856         }
13857         break;
13858
13859       case WhitePlay:
13860         SetWhiteToPlayEvent();
13861         break;
13862
13863       case BlackPlay:
13864         SetBlackToPlayEvent();
13865         break;
13866
13867       case EmptySquare:
13868         if (gameMode == IcsExamining) {
13869             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13870             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13871             SendToICS(buf);
13872         } else {
13873             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13874                 if(x == BOARD_LEFT-2) {
13875                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13876                     boards[0][y][1] = 0;
13877                 } else
13878                 if(x == BOARD_RGHT+1) {
13879                     if(y >= gameInfo.holdingsSize) break;
13880                     boards[0][y][BOARD_WIDTH-2] = 0;
13881                 } else break;
13882             }
13883             boards[0][y][x] = EmptySquare;
13884             DrawPosition(FALSE, boards[0]);
13885         }
13886         break;
13887
13888       case PromotePiece:
13889         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13890            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13891             selection = (ChessSquare) (PROMOTED piece);
13892         } else if(piece == EmptySquare) selection = WhiteSilver;
13893         else selection = (ChessSquare)((int)piece - 1);
13894         goto defaultlabel;
13895
13896       case DemotePiece:
13897         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13898            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13899             selection = (ChessSquare) (DEMOTED piece);
13900         } else if(piece == EmptySquare) selection = BlackSilver;
13901         else selection = (ChessSquare)((int)piece + 1);
13902         goto defaultlabel;
13903
13904       case WhiteQueen:
13905       case BlackQueen:
13906         if(gameInfo.variant == VariantShatranj ||
13907            gameInfo.variant == VariantXiangqi  ||
13908            gameInfo.variant == VariantCourier  ||
13909            gameInfo.variant == VariantMakruk     )
13910             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13911         goto defaultlabel;
13912
13913       case WhiteKing:
13914       case BlackKing:
13915         if(gameInfo.variant == VariantXiangqi)
13916             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13917         if(gameInfo.variant == VariantKnightmate)
13918             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13919       default:
13920         defaultlabel:
13921         if (gameMode == IcsExamining) {
13922             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13923             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13924                      PieceToChar(selection), AAA + x, ONE + y);
13925             SendToICS(buf);
13926         } else {
13927             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13928                 int n;
13929                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13930                     n = PieceToNumber(selection - BlackPawn);
13931                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13932                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13933                     boards[0][BOARD_HEIGHT-1-n][1]++;
13934                 } else
13935                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13936                     n = PieceToNumber(selection);
13937                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13938                     boards[0][n][BOARD_WIDTH-1] = selection;
13939                     boards[0][n][BOARD_WIDTH-2]++;
13940                 }
13941             } else
13942             boards[0][y][x] = selection;
13943             DrawPosition(TRUE, boards[0]);
13944             ClearHighlights();
13945             fromX = fromY = -1;
13946         }
13947         break;
13948     }
13949 }
13950
13951
13952 void
13953 DropMenuEvent (ChessSquare selection, int x, int y)
13954 {
13955     ChessMove moveType;
13956
13957     switch (gameMode) {
13958       case IcsPlayingWhite:
13959       case MachinePlaysBlack:
13960         if (!WhiteOnMove(currentMove)) {
13961             DisplayMoveError(_("It is Black's turn"));
13962             return;
13963         }
13964         moveType = WhiteDrop;
13965         break;
13966       case IcsPlayingBlack:
13967       case MachinePlaysWhite:
13968         if (WhiteOnMove(currentMove)) {
13969             DisplayMoveError(_("It is White's turn"));
13970             return;
13971         }
13972         moveType = BlackDrop;
13973         break;
13974       case EditGame:
13975         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13976         break;
13977       default:
13978         return;
13979     }
13980
13981     if (moveType == BlackDrop && selection < BlackPawn) {
13982       selection = (ChessSquare) ((int) selection
13983                                  + (int) BlackPawn - (int) WhitePawn);
13984     }
13985     if (boards[currentMove][y][x] != EmptySquare) {
13986         DisplayMoveError(_("That square is occupied"));
13987         return;
13988     }
13989
13990     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13991 }
13992
13993 void
13994 AcceptEvent ()
13995 {
13996     /* Accept a pending offer of any kind from opponent */
13997
13998     if (appData.icsActive) {
13999         SendToICS(ics_prefix);
14000         SendToICS("accept\n");
14001     } else if (cmailMsgLoaded) {
14002         if (currentMove == cmailOldMove &&
14003             commentList[cmailOldMove] != NULL &&
14004             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14005                    "Black offers a draw" : "White offers a draw")) {
14006             TruncateGame();
14007             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14009         } else {
14010             DisplayError(_("There is no pending offer on this move"), 0);
14011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14012         }
14013     } else {
14014         /* Not used for offers from chess program */
14015     }
14016 }
14017
14018 void
14019 DeclineEvent ()
14020 {
14021     /* Decline a pending offer of any kind from opponent */
14022
14023     if (appData.icsActive) {
14024         SendToICS(ics_prefix);
14025         SendToICS("decline\n");
14026     } else if (cmailMsgLoaded) {
14027         if (currentMove == cmailOldMove &&
14028             commentList[cmailOldMove] != NULL &&
14029             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14030                    "Black offers a draw" : "White offers a draw")) {
14031 #ifdef NOTDEF
14032             AppendComment(cmailOldMove, "Draw declined", TRUE);
14033             DisplayComment(cmailOldMove - 1, "Draw declined");
14034 #endif /*NOTDEF*/
14035         } else {
14036             DisplayError(_("There is no pending offer on this move"), 0);
14037         }
14038     } else {
14039         /* Not used for offers from chess program */
14040     }
14041 }
14042
14043 void
14044 RematchEvent ()
14045 {
14046     /* Issue ICS rematch command */
14047     if (appData.icsActive) {
14048         SendToICS(ics_prefix);
14049         SendToICS("rematch\n");
14050     }
14051 }
14052
14053 void
14054 CallFlagEvent ()
14055 {
14056     /* Call your opponent's flag (claim a win on time) */
14057     if (appData.icsActive) {
14058         SendToICS(ics_prefix);
14059         SendToICS("flag\n");
14060     } else {
14061         switch (gameMode) {
14062           default:
14063             return;
14064           case MachinePlaysWhite:
14065             if (whiteFlag) {
14066                 if (blackFlag)
14067                   GameEnds(GameIsDrawn, "Both players ran out of time",
14068                            GE_PLAYER);
14069                 else
14070                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14071             } else {
14072                 DisplayError(_("Your opponent is not out of time"), 0);
14073             }
14074             break;
14075           case MachinePlaysBlack:
14076             if (blackFlag) {
14077                 if (whiteFlag)
14078                   GameEnds(GameIsDrawn, "Both players ran out of time",
14079                            GE_PLAYER);
14080                 else
14081                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14082             } else {
14083                 DisplayError(_("Your opponent is not out of time"), 0);
14084             }
14085             break;
14086         }
14087     }
14088 }
14089
14090 void
14091 ClockClick (int which)
14092 {       // [HGM] code moved to back-end from winboard.c
14093         if(which) { // black clock
14094           if (gameMode == EditPosition || gameMode == IcsExamining) {
14095             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14096             SetBlackToPlayEvent();
14097           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14098           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14099           } else if (shiftKey) {
14100             AdjustClock(which, -1);
14101           } else if (gameMode == IcsPlayingWhite ||
14102                      gameMode == MachinePlaysBlack) {
14103             CallFlagEvent();
14104           }
14105         } else { // white clock
14106           if (gameMode == EditPosition || gameMode == IcsExamining) {
14107             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14108             SetWhiteToPlayEvent();
14109           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14110           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14111           } else if (shiftKey) {
14112             AdjustClock(which, -1);
14113           } else if (gameMode == IcsPlayingBlack ||
14114                    gameMode == MachinePlaysWhite) {
14115             CallFlagEvent();
14116           }
14117         }
14118 }
14119
14120 void
14121 DrawEvent ()
14122 {
14123     /* Offer draw or accept pending draw offer from opponent */
14124
14125     if (appData.icsActive) {
14126         /* Note: tournament rules require draw offers to be
14127            made after you make your move but before you punch
14128            your clock.  Currently ICS doesn't let you do that;
14129            instead, you immediately punch your clock after making
14130            a move, but you can offer a draw at any time. */
14131
14132         SendToICS(ics_prefix);
14133         SendToICS("draw\n");
14134         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14135     } else if (cmailMsgLoaded) {
14136         if (currentMove == cmailOldMove &&
14137             commentList[cmailOldMove] != NULL &&
14138             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14139                    "Black offers a draw" : "White offers a draw")) {
14140             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14141             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14142         } else if (currentMove == cmailOldMove + 1) {
14143             char *offer = WhiteOnMove(cmailOldMove) ?
14144               "White offers a draw" : "Black offers a draw";
14145             AppendComment(currentMove, offer, TRUE);
14146             DisplayComment(currentMove - 1, offer);
14147             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14148         } else {
14149             DisplayError(_("You must make your move before offering a draw"), 0);
14150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14151         }
14152     } else if (first.offeredDraw) {
14153         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14154     } else {
14155         if (first.sendDrawOffers) {
14156             SendToProgram("draw\n", &first);
14157             userOfferedDraw = TRUE;
14158         }
14159     }
14160 }
14161
14162 void
14163 AdjournEvent ()
14164 {
14165     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14166
14167     if (appData.icsActive) {
14168         SendToICS(ics_prefix);
14169         SendToICS("adjourn\n");
14170     } else {
14171         /* Currently GNU Chess doesn't offer or accept Adjourns */
14172     }
14173 }
14174
14175
14176 void
14177 AbortEvent ()
14178 {
14179     /* Offer Abort or accept pending Abort offer from opponent */
14180
14181     if (appData.icsActive) {
14182         SendToICS(ics_prefix);
14183         SendToICS("abort\n");
14184     } else {
14185         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14186     }
14187 }
14188
14189 void
14190 ResignEvent ()
14191 {
14192     /* Resign.  You can do this even if it's not your turn. */
14193
14194     if (appData.icsActive) {
14195         SendToICS(ics_prefix);
14196         SendToICS("resign\n");
14197     } else {
14198         switch (gameMode) {
14199           case MachinePlaysWhite:
14200             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14201             break;
14202           case MachinePlaysBlack:
14203             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14204             break;
14205           case EditGame:
14206             if (cmailMsgLoaded) {
14207                 TruncateGame();
14208                 if (WhiteOnMove(cmailOldMove)) {
14209                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14210                 } else {
14211                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14212                 }
14213                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14214             }
14215             break;
14216           default:
14217             break;
14218         }
14219     }
14220 }
14221
14222
14223 void
14224 StopObservingEvent ()
14225 {
14226     /* Stop observing current games */
14227     SendToICS(ics_prefix);
14228     SendToICS("unobserve\n");
14229 }
14230
14231 void
14232 StopExaminingEvent ()
14233 {
14234     /* Stop observing current game */
14235     SendToICS(ics_prefix);
14236     SendToICS("unexamine\n");
14237 }
14238
14239 void
14240 ForwardInner (int target)
14241 {
14242     int limit; int oldSeekGraphUp = seekGraphUp;
14243
14244     if (appData.debugMode)
14245         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14246                 target, currentMove, forwardMostMove);
14247
14248     if (gameMode == EditPosition)
14249       return;
14250
14251     seekGraphUp = FALSE;
14252     MarkTargetSquares(1);
14253
14254     if (gameMode == PlayFromGameFile && !pausing)
14255       PauseEvent();
14256
14257     if (gameMode == IcsExamining && pausing)
14258       limit = pauseExamForwardMostMove;
14259     else
14260       limit = forwardMostMove;
14261
14262     if (target > limit) target = limit;
14263
14264     if (target > 0 && moveList[target - 1][0]) {
14265         int fromX, fromY, toX, toY;
14266         toX = moveList[target - 1][2] - AAA;
14267         toY = moveList[target - 1][3] - ONE;
14268         if (moveList[target - 1][1] == '@') {
14269             if (appData.highlightLastMove) {
14270                 SetHighlights(-1, -1, toX, toY);
14271             }
14272         } else {
14273             fromX = moveList[target - 1][0] - AAA;
14274             fromY = moveList[target - 1][1] - ONE;
14275             if (target == currentMove + 1) {
14276                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14277             }
14278             if (appData.highlightLastMove) {
14279                 SetHighlights(fromX, fromY, toX, toY);
14280             }
14281         }
14282     }
14283     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14284         gameMode == Training || gameMode == PlayFromGameFile ||
14285         gameMode == AnalyzeFile) {
14286         while (currentMove < target) {
14287             SendMoveToProgram(currentMove++, &first);
14288         }
14289     } else {
14290         currentMove = target;
14291     }
14292
14293     if (gameMode == EditGame || gameMode == EndOfGame) {
14294         whiteTimeRemaining = timeRemaining[0][currentMove];
14295         blackTimeRemaining = timeRemaining[1][currentMove];
14296     }
14297     DisplayBothClocks();
14298     DisplayMove(currentMove - 1);
14299     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14300     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14301     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14302         DisplayComment(currentMove - 1, commentList[currentMove]);
14303     }
14304 }
14305
14306
14307 void
14308 ForwardEvent ()
14309 {
14310     if (gameMode == IcsExamining && !pausing) {
14311         SendToICS(ics_prefix);
14312         SendToICS("forward\n");
14313     } else {
14314         ForwardInner(currentMove + 1);
14315     }
14316 }
14317
14318 void
14319 ToEndEvent ()
14320 {
14321     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14322         /* to optimze, we temporarily turn off analysis mode while we feed
14323          * the remaining moves to the engine. Otherwise we get analysis output
14324          * after each move.
14325          */
14326         if (first.analysisSupport) {
14327           SendToProgram("exit\nforce\n", &first);
14328           first.analyzing = FALSE;
14329         }
14330     }
14331
14332     if (gameMode == IcsExamining && !pausing) {
14333         SendToICS(ics_prefix);
14334         SendToICS("forward 999999\n");
14335     } else {
14336         ForwardInner(forwardMostMove);
14337     }
14338
14339     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14340         /* we have fed all the moves, so reactivate analysis mode */
14341         SendToProgram("analyze\n", &first);
14342         first.analyzing = TRUE;
14343         /*first.maybeThinking = TRUE;*/
14344         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14345     }
14346 }
14347
14348 void
14349 BackwardInner (int target)
14350 {
14351     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14352
14353     if (appData.debugMode)
14354         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14355                 target, currentMove, forwardMostMove);
14356
14357     if (gameMode == EditPosition) return;
14358     seekGraphUp = FALSE;
14359     MarkTargetSquares(1);
14360     if (currentMove <= backwardMostMove) {
14361         ClearHighlights();
14362         DrawPosition(full_redraw, boards[currentMove]);
14363         return;
14364     }
14365     if (gameMode == PlayFromGameFile && !pausing)
14366       PauseEvent();
14367
14368     if (moveList[target][0]) {
14369         int fromX, fromY, toX, toY;
14370         toX = moveList[target][2] - AAA;
14371         toY = moveList[target][3] - ONE;
14372         if (moveList[target][1] == '@') {
14373             if (appData.highlightLastMove) {
14374                 SetHighlights(-1, -1, toX, toY);
14375             }
14376         } else {
14377             fromX = moveList[target][0] - AAA;
14378             fromY = moveList[target][1] - ONE;
14379             if (target == currentMove - 1) {
14380                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14381             }
14382             if (appData.highlightLastMove) {
14383                 SetHighlights(fromX, fromY, toX, toY);
14384             }
14385         }
14386     }
14387     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14388         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14389         while (currentMove > target) {
14390             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14391                 // null move cannot be undone. Reload program with move history before it.
14392                 int i;
14393                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14394                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14395                 }
14396                 SendBoard(&first, i); 
14397                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14398                 break;
14399             }
14400             SendToProgram("undo\n", &first);
14401             currentMove--;
14402         }
14403     } else {
14404         currentMove = target;
14405     }
14406
14407     if (gameMode == EditGame || gameMode == EndOfGame) {
14408         whiteTimeRemaining = timeRemaining[0][currentMove];
14409         blackTimeRemaining = timeRemaining[1][currentMove];
14410     }
14411     DisplayBothClocks();
14412     DisplayMove(currentMove - 1);
14413     DrawPosition(full_redraw, boards[currentMove]);
14414     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14415     // [HGM] PV info: routine tests if comment empty
14416     DisplayComment(currentMove - 1, commentList[currentMove]);
14417 }
14418
14419 void
14420 BackwardEvent ()
14421 {
14422     if (gameMode == IcsExamining && !pausing) {
14423         SendToICS(ics_prefix);
14424         SendToICS("backward\n");
14425     } else {
14426         BackwardInner(currentMove - 1);
14427     }
14428 }
14429
14430 void
14431 ToStartEvent ()
14432 {
14433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14434         /* to optimize, we temporarily turn off analysis mode while we undo
14435          * all the moves. Otherwise we get analysis output after each undo.
14436          */
14437         if (first.analysisSupport) {
14438           SendToProgram("exit\nforce\n", &first);
14439           first.analyzing = FALSE;
14440         }
14441     }
14442
14443     if (gameMode == IcsExamining && !pausing) {
14444         SendToICS(ics_prefix);
14445         SendToICS("backward 999999\n");
14446     } else {
14447         BackwardInner(backwardMostMove);
14448     }
14449
14450     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14451         /* we have fed all the moves, so reactivate analysis mode */
14452         SendToProgram("analyze\n", &first);
14453         first.analyzing = TRUE;
14454         /*first.maybeThinking = TRUE;*/
14455         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14456     }
14457 }
14458
14459 void
14460 ToNrEvent (int to)
14461 {
14462   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14463   if (to >= forwardMostMove) to = forwardMostMove;
14464   if (to <= backwardMostMove) to = backwardMostMove;
14465   if (to < currentMove) {
14466     BackwardInner(to);
14467   } else {
14468     ForwardInner(to);
14469   }
14470 }
14471
14472 void
14473 RevertEvent (Boolean annotate)
14474 {
14475     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14476         return;
14477     }
14478     if (gameMode != IcsExamining) {
14479         DisplayError(_("You are not examining a game"), 0);
14480         return;
14481     }
14482     if (pausing) {
14483         DisplayError(_("You can't revert while pausing"), 0);
14484         return;
14485     }
14486     SendToICS(ics_prefix);
14487     SendToICS("revert\n");
14488 }
14489
14490 void
14491 RetractMoveEvent ()
14492 {
14493     switch (gameMode) {
14494       case MachinePlaysWhite:
14495       case MachinePlaysBlack:
14496         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14497             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14498             return;
14499         }
14500         if (forwardMostMove < 2) return;
14501         currentMove = forwardMostMove = forwardMostMove - 2;
14502         whiteTimeRemaining = timeRemaining[0][currentMove];
14503         blackTimeRemaining = timeRemaining[1][currentMove];
14504         DisplayBothClocks();
14505         DisplayMove(currentMove - 1);
14506         ClearHighlights();/*!! could figure this out*/
14507         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14508         SendToProgram("remove\n", &first);
14509         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14510         break;
14511
14512       case BeginningOfGame:
14513       default:
14514         break;
14515
14516       case IcsPlayingWhite:
14517       case IcsPlayingBlack:
14518         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14519             SendToICS(ics_prefix);
14520             SendToICS("takeback 2\n");
14521         } else {
14522             SendToICS(ics_prefix);
14523             SendToICS("takeback 1\n");
14524         }
14525         break;
14526     }
14527 }
14528
14529 void
14530 MoveNowEvent ()
14531 {
14532     ChessProgramState *cps;
14533
14534     switch (gameMode) {
14535       case MachinePlaysWhite:
14536         if (!WhiteOnMove(forwardMostMove)) {
14537             DisplayError(_("It is your turn"), 0);
14538             return;
14539         }
14540         cps = &first;
14541         break;
14542       case MachinePlaysBlack:
14543         if (WhiteOnMove(forwardMostMove)) {
14544             DisplayError(_("It is your turn"), 0);
14545             return;
14546         }
14547         cps = &first;
14548         break;
14549       case TwoMachinesPlay:
14550         if (WhiteOnMove(forwardMostMove) ==
14551             (first.twoMachinesColor[0] == 'w')) {
14552             cps = &first;
14553         } else {
14554             cps = &second;
14555         }
14556         break;
14557       case BeginningOfGame:
14558       default:
14559         return;
14560     }
14561     SendToProgram("?\n", cps);
14562 }
14563
14564 void
14565 TruncateGameEvent ()
14566 {
14567     EditGameEvent();
14568     if (gameMode != EditGame) return;
14569     TruncateGame();
14570 }
14571
14572 void
14573 TruncateGame ()
14574 {
14575     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14576     if (forwardMostMove > currentMove) {
14577         if (gameInfo.resultDetails != NULL) {
14578             free(gameInfo.resultDetails);
14579             gameInfo.resultDetails = NULL;
14580             gameInfo.result = GameUnfinished;
14581         }
14582         forwardMostMove = currentMove;
14583         HistorySet(parseList, backwardMostMove, forwardMostMove,
14584                    currentMove-1);
14585     }
14586 }
14587
14588 void
14589 HintEvent ()
14590 {
14591     if (appData.noChessProgram) return;
14592     switch (gameMode) {
14593       case MachinePlaysWhite:
14594         if (WhiteOnMove(forwardMostMove)) {
14595             DisplayError(_("Wait until your turn"), 0);
14596             return;
14597         }
14598         break;
14599       case BeginningOfGame:
14600       case MachinePlaysBlack:
14601         if (!WhiteOnMove(forwardMostMove)) {
14602             DisplayError(_("Wait until your turn"), 0);
14603             return;
14604         }
14605         break;
14606       default:
14607         DisplayError(_("No hint available"), 0);
14608         return;
14609     }
14610     SendToProgram("hint\n", &first);
14611     hintRequested = TRUE;
14612 }
14613
14614 void
14615 BookEvent ()
14616 {
14617     if (appData.noChessProgram) return;
14618     switch (gameMode) {
14619       case MachinePlaysWhite:
14620         if (WhiteOnMove(forwardMostMove)) {
14621             DisplayError(_("Wait until your turn"), 0);
14622             return;
14623         }
14624         break;
14625       case BeginningOfGame:
14626       case MachinePlaysBlack:
14627         if (!WhiteOnMove(forwardMostMove)) {
14628             DisplayError(_("Wait until your turn"), 0);
14629             return;
14630         }
14631         break;
14632       case EditPosition:
14633         EditPositionDone(TRUE);
14634         break;
14635       case TwoMachinesPlay:
14636         return;
14637       default:
14638         break;
14639     }
14640     SendToProgram("bk\n", &first);
14641     bookOutput[0] = NULLCHAR;
14642     bookRequested = TRUE;
14643 }
14644
14645 void
14646 AboutGameEvent ()
14647 {
14648     char *tags = PGNTags(&gameInfo);
14649     TagsPopUp(tags, CmailMsg());
14650     free(tags);
14651 }
14652
14653 /* end button procedures */
14654
14655 void
14656 PrintPosition (FILE *fp, int move)
14657 {
14658     int i, j;
14659
14660     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14661         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14662             char c = PieceToChar(boards[move][i][j]);
14663             fputc(c == 'x' ? '.' : c, fp);
14664             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14665         }
14666     }
14667     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14668       fprintf(fp, "white to play\n");
14669     else
14670       fprintf(fp, "black to play\n");
14671 }
14672
14673 void
14674 PrintOpponents (FILE *fp)
14675 {
14676     if (gameInfo.white != NULL) {
14677         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14678     } else {
14679         fprintf(fp, "\n");
14680     }
14681 }
14682
14683 /* Find last component of program's own name, using some heuristics */
14684 void
14685 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14686 {
14687     char *p, *q, c;
14688     int local = (strcmp(host, "localhost") == 0);
14689     while (!local && (p = strchr(prog, ';')) != NULL) {
14690         p++;
14691         while (*p == ' ') p++;
14692         prog = p;
14693     }
14694     if (*prog == '"' || *prog == '\'') {
14695         q = strchr(prog + 1, *prog);
14696     } else {
14697         q = strchr(prog, ' ');
14698     }
14699     if (q == NULL) q = prog + strlen(prog);
14700     p = q;
14701     while (p >= prog && *p != '/' && *p != '\\') p--;
14702     p++;
14703     if(p == prog && *p == '"') p++;
14704     c = *q; *q = 0;
14705     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14706     memcpy(buf, p, q - p);
14707     buf[q - p] = NULLCHAR;
14708     if (!local) {
14709         strcat(buf, "@");
14710         strcat(buf, host);
14711     }
14712 }
14713
14714 char *
14715 TimeControlTagValue ()
14716 {
14717     char buf[MSG_SIZ];
14718     if (!appData.clockMode) {
14719       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14720     } else if (movesPerSession > 0) {
14721       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14722     } else if (timeIncrement == 0) {
14723       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14724     } else {
14725       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14726     }
14727     return StrSave(buf);
14728 }
14729
14730 void
14731 SetGameInfo ()
14732 {
14733     /* This routine is used only for certain modes */
14734     VariantClass v = gameInfo.variant;
14735     ChessMove r = GameUnfinished;
14736     char *p = NULL;
14737
14738     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14739         r = gameInfo.result;
14740         p = gameInfo.resultDetails;
14741         gameInfo.resultDetails = NULL;
14742     }
14743     ClearGameInfo(&gameInfo);
14744     gameInfo.variant = v;
14745
14746     switch (gameMode) {
14747       case MachinePlaysWhite:
14748         gameInfo.event = StrSave( appData.pgnEventHeader );
14749         gameInfo.site = StrSave(HostName());
14750         gameInfo.date = PGNDate();
14751         gameInfo.round = StrSave("-");
14752         gameInfo.white = StrSave(first.tidy);
14753         gameInfo.black = StrSave(UserName());
14754         gameInfo.timeControl = TimeControlTagValue();
14755         break;
14756
14757       case MachinePlaysBlack:
14758         gameInfo.event = StrSave( appData.pgnEventHeader );
14759         gameInfo.site = StrSave(HostName());
14760         gameInfo.date = PGNDate();
14761         gameInfo.round = StrSave("-");
14762         gameInfo.white = StrSave(UserName());
14763         gameInfo.black = StrSave(first.tidy);
14764         gameInfo.timeControl = TimeControlTagValue();
14765         break;
14766
14767       case TwoMachinesPlay:
14768         gameInfo.event = StrSave( appData.pgnEventHeader );
14769         gameInfo.site = StrSave(HostName());
14770         gameInfo.date = PGNDate();
14771         if (roundNr > 0) {
14772             char buf[MSG_SIZ];
14773             snprintf(buf, MSG_SIZ, "%d", roundNr);
14774             gameInfo.round = StrSave(buf);
14775         } else {
14776             gameInfo.round = StrSave("-");
14777         }
14778         if (first.twoMachinesColor[0] == 'w') {
14779             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14780             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14781         } else {
14782             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14783             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14784         }
14785         gameInfo.timeControl = TimeControlTagValue();
14786         break;
14787
14788       case EditGame:
14789         gameInfo.event = StrSave("Edited game");
14790         gameInfo.site = StrSave(HostName());
14791         gameInfo.date = PGNDate();
14792         gameInfo.round = StrSave("-");
14793         gameInfo.white = StrSave("-");
14794         gameInfo.black = StrSave("-");
14795         gameInfo.result = r;
14796         gameInfo.resultDetails = p;
14797         break;
14798
14799       case EditPosition:
14800         gameInfo.event = StrSave("Edited position");
14801         gameInfo.site = StrSave(HostName());
14802         gameInfo.date = PGNDate();
14803         gameInfo.round = StrSave("-");
14804         gameInfo.white = StrSave("-");
14805         gameInfo.black = StrSave("-");
14806         break;
14807
14808       case IcsPlayingWhite:
14809       case IcsPlayingBlack:
14810       case IcsObserving:
14811       case IcsExamining:
14812         break;
14813
14814       case PlayFromGameFile:
14815         gameInfo.event = StrSave("Game from non-PGN file");
14816         gameInfo.site = StrSave(HostName());
14817         gameInfo.date = PGNDate();
14818         gameInfo.round = StrSave("-");
14819         gameInfo.white = StrSave("?");
14820         gameInfo.black = StrSave("?");
14821         break;
14822
14823       default:
14824         break;
14825     }
14826 }
14827
14828 void
14829 ReplaceComment (int index, char *text)
14830 {
14831     int len;
14832     char *p;
14833     float score;
14834
14835     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14836        pvInfoList[index-1].depth == len &&
14837        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14838        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14839     while (*text == '\n') text++;
14840     len = strlen(text);
14841     while (len > 0 && text[len - 1] == '\n') len--;
14842
14843     if (commentList[index] != NULL)
14844       free(commentList[index]);
14845
14846     if (len == 0) {
14847         commentList[index] = NULL;
14848         return;
14849     }
14850   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14851       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14852       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14853     commentList[index] = (char *) malloc(len + 2);
14854     strncpy(commentList[index], text, len);
14855     commentList[index][len] = '\n';
14856     commentList[index][len + 1] = NULLCHAR;
14857   } else {
14858     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14859     char *p;
14860     commentList[index] = (char *) malloc(len + 7);
14861     safeStrCpy(commentList[index], "{\n", 3);
14862     safeStrCpy(commentList[index]+2, text, len+1);
14863     commentList[index][len+2] = NULLCHAR;
14864     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14865     strcat(commentList[index], "\n}\n");
14866   }
14867 }
14868
14869 void
14870 CrushCRs (char *text)
14871 {
14872   char *p = text;
14873   char *q = text;
14874   char ch;
14875
14876   do {
14877     ch = *p++;
14878     if (ch == '\r') continue;
14879     *q++ = ch;
14880   } while (ch != '\0');
14881 }
14882
14883 void
14884 AppendComment (int index, char *text, Boolean addBraces)
14885 /* addBraces  tells if we should add {} */
14886 {
14887     int oldlen, len;
14888     char *old;
14889
14890 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14891     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14892
14893     CrushCRs(text);
14894     while (*text == '\n') text++;
14895     len = strlen(text);
14896     while (len > 0 && text[len - 1] == '\n') len--;
14897     text[len] = NULLCHAR;
14898
14899     if (len == 0) return;
14900
14901     if (commentList[index] != NULL) {
14902       Boolean addClosingBrace = addBraces;
14903         old = commentList[index];
14904         oldlen = strlen(old);
14905         while(commentList[index][oldlen-1] ==  '\n')
14906           commentList[index][--oldlen] = NULLCHAR;
14907         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14908         safeStrCpy(commentList[index], old, oldlen + len + 6);
14909         free(old);
14910         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14911         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14912           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14913           while (*text == '\n') { text++; len--; }
14914           commentList[index][--oldlen] = NULLCHAR;
14915       }
14916         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14917         else          strcat(commentList[index], "\n");
14918         strcat(commentList[index], text);
14919         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14920         else          strcat(commentList[index], "\n");
14921     } else {
14922         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14923         if(addBraces)
14924           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14925         else commentList[index][0] = NULLCHAR;
14926         strcat(commentList[index], text);
14927         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14928         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14929     }
14930 }
14931
14932 static char *
14933 FindStr (char * text, char * sub_text)
14934 {
14935     char * result = strstr( text, sub_text );
14936
14937     if( result != NULL ) {
14938         result += strlen( sub_text );
14939     }
14940
14941     return result;
14942 }
14943
14944 /* [AS] Try to extract PV info from PGN comment */
14945 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14946 char *
14947 GetInfoFromComment (int index, char * text)
14948 {
14949     char * sep = text, *p;
14950
14951     if( text != NULL && index > 0 ) {
14952         int score = 0;
14953         int depth = 0;
14954         int time = -1, sec = 0, deci;
14955         char * s_eval = FindStr( text, "[%eval " );
14956         char * s_emt = FindStr( text, "[%emt " );
14957
14958         if( s_eval != NULL || s_emt != NULL ) {
14959             /* New style */
14960             char delim;
14961
14962             if( s_eval != NULL ) {
14963                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14964                     return text;
14965                 }
14966
14967                 if( delim != ']' ) {
14968                     return text;
14969                 }
14970             }
14971
14972             if( s_emt != NULL ) {
14973             }
14974                 return text;
14975         }
14976         else {
14977             /* We expect something like: [+|-]nnn.nn/dd */
14978             int score_lo = 0;
14979
14980             if(*text != '{') return text; // [HGM] braces: must be normal comment
14981
14982             sep = strchr( text, '/' );
14983             if( sep == NULL || sep < (text+4) ) {
14984                 return text;
14985             }
14986
14987             p = text;
14988             if(p[1] == '(') { // comment starts with PV
14989                p = strchr(p, ')'); // locate end of PV
14990                if(p == NULL || sep < p+5) return text;
14991                // at this point we have something like "{(.*) +0.23/6 ..."
14992                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14993                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14994                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14995             }
14996             time = -1; sec = -1; deci = -1;
14997             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14998                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14999                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15000                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15001                 return text;
15002             }
15003
15004             if( score_lo < 0 || score_lo >= 100 ) {
15005                 return text;
15006             }
15007
15008             if(sec >= 0) time = 600*time + 10*sec; else
15009             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15010
15011             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15012
15013             /* [HGM] PV time: now locate end of PV info */
15014             while( *++sep >= '0' && *sep <= '9'); // strip depth
15015             if(time >= 0)
15016             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15017             if(sec >= 0)
15018             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15019             if(deci >= 0)
15020             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15021             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15022         }
15023
15024         if( depth <= 0 ) {
15025             return text;
15026         }
15027
15028         if( time < 0 ) {
15029             time = -1;
15030         }
15031
15032         pvInfoList[index-1].depth = depth;
15033         pvInfoList[index-1].score = score;
15034         pvInfoList[index-1].time  = 10*time; // centi-sec
15035         if(*sep == '}') *sep = 0; else *--sep = '{';
15036         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15037     }
15038     return sep;
15039 }
15040
15041 void
15042 SendToProgram (char *message, ChessProgramState *cps)
15043 {
15044     int count, outCount, error;
15045     char buf[MSG_SIZ];
15046
15047     if (cps->pr == NoProc) return;
15048     Attention(cps);
15049
15050     if (appData.debugMode) {
15051         TimeMark now;
15052         GetTimeMark(&now);
15053         fprintf(debugFP, "%ld >%-6s: %s",
15054                 SubtractTimeMarks(&now, &programStartTime),
15055                 cps->which, message);
15056         if(serverFP)
15057             fprintf(serverFP, "%ld >%-6s: %s",
15058                 SubtractTimeMarks(&now, &programStartTime),
15059                 cps->which, message), fflush(serverFP);
15060     }
15061
15062     count = strlen(message);
15063     outCount = OutputToProcess(cps->pr, message, count, &error);
15064     if (outCount < count && !exiting
15065                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15066       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15067       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15068         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15069             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15070                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15071                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15072                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15073             } else {
15074                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15075                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15076                 gameInfo.result = res;
15077             }
15078             gameInfo.resultDetails = StrSave(buf);
15079         }
15080         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15081         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15082     }
15083 }
15084
15085 void
15086 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15087 {
15088     char *end_str;
15089     char buf[MSG_SIZ];
15090     ChessProgramState *cps = (ChessProgramState *)closure;
15091
15092     if (isr != cps->isr) return; /* Killed intentionally */
15093     if (count <= 0) {
15094         if (count == 0) {
15095             RemoveInputSource(cps->isr);
15096             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15097             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15098                     _(cps->which), cps->program);
15099         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15100                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15101                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15102                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15103                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15104                 } else {
15105                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15106                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15107                     gameInfo.result = res;
15108                 }
15109                 gameInfo.resultDetails = StrSave(buf);
15110             }
15111             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15112             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15113         } else {
15114             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15115                     _(cps->which), cps->program);
15116             RemoveInputSource(cps->isr);
15117
15118             /* [AS] Program is misbehaving badly... kill it */
15119             if( count == -2 ) {
15120                 DestroyChildProcess( cps->pr, 9 );
15121                 cps->pr = NoProc;
15122             }
15123
15124             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15125         }
15126         return;
15127     }
15128
15129     if ((end_str = strchr(message, '\r')) != NULL)
15130       *end_str = NULLCHAR;
15131     if ((end_str = strchr(message, '\n')) != NULL)
15132       *end_str = NULLCHAR;
15133
15134     if (appData.debugMode) {
15135         TimeMark now; int print = 1;
15136         char *quote = ""; char c; int i;
15137
15138         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15139                 char start = message[0];
15140                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15141                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15142                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15143                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15144                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15145                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15146                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15147                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15148                    sscanf(message, "hint: %c", &c)!=1 && 
15149                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15150                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15151                     print = (appData.engineComments >= 2);
15152                 }
15153                 message[0] = start; // restore original message
15154         }
15155         if(print) {
15156                 GetTimeMark(&now);
15157                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15158                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15159                         quote,
15160                         message);
15161                 if(serverFP)
15162                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15163                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15164                         quote,
15165                         message), fflush(serverFP);
15166         }
15167     }
15168
15169     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15170     if (appData.icsEngineAnalyze) {
15171         if (strstr(message, "whisper") != NULL ||
15172              strstr(message, "kibitz") != NULL ||
15173             strstr(message, "tellics") != NULL) return;
15174     }
15175
15176     HandleMachineMove(message, cps);
15177 }
15178
15179
15180 void
15181 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15182 {
15183     char buf[MSG_SIZ];
15184     int seconds;
15185
15186     if( timeControl_2 > 0 ) {
15187         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15188             tc = timeControl_2;
15189         }
15190     }
15191     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15192     inc /= cps->timeOdds;
15193     st  /= cps->timeOdds;
15194
15195     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15196
15197     if (st > 0) {
15198       /* Set exact time per move, normally using st command */
15199       if (cps->stKludge) {
15200         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15201         seconds = st % 60;
15202         if (seconds == 0) {
15203           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15204         } else {
15205           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15206         }
15207       } else {
15208         snprintf(buf, MSG_SIZ, "st %d\n", st);
15209       }
15210     } else {
15211       /* Set conventional or incremental time control, using level command */
15212       if (seconds == 0) {
15213         /* Note old gnuchess bug -- minutes:seconds used to not work.
15214            Fixed in later versions, but still avoid :seconds
15215            when seconds is 0. */
15216         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15217       } else {
15218         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15219                  seconds, inc/1000.);
15220       }
15221     }
15222     SendToProgram(buf, cps);
15223
15224     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15225     /* Orthogonally, limit search to given depth */
15226     if (sd > 0) {
15227       if (cps->sdKludge) {
15228         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15229       } else {
15230         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15231       }
15232       SendToProgram(buf, cps);
15233     }
15234
15235     if(cps->nps >= 0) { /* [HGM] nps */
15236         if(cps->supportsNPS == FALSE)
15237           cps->nps = -1; // don't use if engine explicitly says not supported!
15238         else {
15239           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15240           SendToProgram(buf, cps);
15241         }
15242     }
15243 }
15244
15245 ChessProgramState *
15246 WhitePlayer ()
15247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15248 {
15249     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15250        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15251         return &second;
15252     return &first;
15253 }
15254
15255 void
15256 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15257 {
15258     char message[MSG_SIZ];
15259     long time, otime;
15260
15261     /* Note: this routine must be called when the clocks are stopped
15262        or when they have *just* been set or switched; otherwise
15263        it will be off by the time since the current tick started.
15264     */
15265     if (machineWhite) {
15266         time = whiteTimeRemaining / 10;
15267         otime = blackTimeRemaining / 10;
15268     } else {
15269         time = blackTimeRemaining / 10;
15270         otime = whiteTimeRemaining / 10;
15271     }
15272     /* [HGM] translate opponent's time by time-odds factor */
15273     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15274
15275     if (time <= 0) time = 1;
15276     if (otime <= 0) otime = 1;
15277
15278     snprintf(message, MSG_SIZ, "time %ld\n", time);
15279     SendToProgram(message, cps);
15280
15281     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15282     SendToProgram(message, cps);
15283 }
15284
15285 int
15286 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15287 {
15288   char buf[MSG_SIZ];
15289   int len = strlen(name);
15290   int val;
15291
15292   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15293     (*p) += len + 1;
15294     sscanf(*p, "%d", &val);
15295     *loc = (val != 0);
15296     while (**p && **p != ' ')
15297       (*p)++;
15298     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15299     SendToProgram(buf, cps);
15300     return TRUE;
15301   }
15302   return FALSE;
15303 }
15304
15305 int
15306 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15307 {
15308   char buf[MSG_SIZ];
15309   int len = strlen(name);
15310   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15311     (*p) += len + 1;
15312     sscanf(*p, "%d", loc);
15313     while (**p && **p != ' ') (*p)++;
15314     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15315     SendToProgram(buf, cps);
15316     return TRUE;
15317   }
15318   return FALSE;
15319 }
15320
15321 int
15322 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15323 {
15324   char buf[MSG_SIZ];
15325   int len = strlen(name);
15326   if (strncmp((*p), name, len) == 0
15327       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15328     (*p) += len + 2;
15329     sscanf(*p, "%[^\"]", loc);
15330     while (**p && **p != '\"') (*p)++;
15331     if (**p == '\"') (*p)++;
15332     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15333     SendToProgram(buf, cps);
15334     return TRUE;
15335   }
15336   return FALSE;
15337 }
15338
15339 int
15340 ParseOption (Option *opt, ChessProgramState *cps)
15341 // [HGM] options: process the string that defines an engine option, and determine
15342 // name, type, default value, and allowed value range
15343 {
15344         char *p, *q, buf[MSG_SIZ];
15345         int n, min = (-1)<<31, max = 1<<31, def;
15346
15347         if(p = strstr(opt->name, " -spin ")) {
15348             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15349             if(max < min) max = min; // enforce consistency
15350             if(def < min) def = min;
15351             if(def > max) def = max;
15352             opt->value = def;
15353             opt->min = min;
15354             opt->max = max;
15355             opt->type = Spin;
15356         } else if((p = strstr(opt->name, " -slider "))) {
15357             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15358             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15359             if(max < min) max = min; // enforce consistency
15360             if(def < min) def = min;
15361             if(def > max) def = max;
15362             opt->value = def;
15363             opt->min = min;
15364             opt->max = max;
15365             opt->type = Spin; // Slider;
15366         } else if((p = strstr(opt->name, " -string "))) {
15367             opt->textValue = p+9;
15368             opt->type = TextBox;
15369         } else if((p = strstr(opt->name, " -file "))) {
15370             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15371             opt->textValue = p+7;
15372             opt->type = FileName; // FileName;
15373         } else if((p = strstr(opt->name, " -path "))) {
15374             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15375             opt->textValue = p+7;
15376             opt->type = PathName; // PathName;
15377         } else if(p = strstr(opt->name, " -check ")) {
15378             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15379             opt->value = (def != 0);
15380             opt->type = CheckBox;
15381         } else if(p = strstr(opt->name, " -combo ")) {
15382             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15383             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15384             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15385             opt->value = n = 0;
15386             while(q = StrStr(q, " /// ")) {
15387                 n++; *q = 0;    // count choices, and null-terminate each of them
15388                 q += 5;
15389                 if(*q == '*') { // remember default, which is marked with * prefix
15390                     q++;
15391                     opt->value = n;
15392                 }
15393                 cps->comboList[cps->comboCnt++] = q;
15394             }
15395             cps->comboList[cps->comboCnt++] = NULL;
15396             opt->max = n + 1;
15397             opt->type = ComboBox;
15398         } else if(p = strstr(opt->name, " -button")) {
15399             opt->type = Button;
15400         } else if(p = strstr(opt->name, " -save")) {
15401             opt->type = SaveButton;
15402         } else return FALSE;
15403         *p = 0; // terminate option name
15404         // now look if the command-line options define a setting for this engine option.
15405         if(cps->optionSettings && cps->optionSettings[0])
15406             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15407         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15408           snprintf(buf, MSG_SIZ, "option %s", p);
15409                 if(p = strstr(buf, ",")) *p = 0;
15410                 if(q = strchr(buf, '=')) switch(opt->type) {
15411                     case ComboBox:
15412                         for(n=0; n<opt->max; n++)
15413                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15414                         break;
15415                     case TextBox:
15416                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15417                         break;
15418                     case Spin:
15419                     case CheckBox:
15420                         opt->value = atoi(q+1);
15421                     default:
15422                         break;
15423                 }
15424                 strcat(buf, "\n");
15425                 SendToProgram(buf, cps);
15426         }
15427         return TRUE;
15428 }
15429
15430 void
15431 FeatureDone (ChessProgramState *cps, int val)
15432 {
15433   DelayedEventCallback cb = GetDelayedEvent();
15434   if ((cb == InitBackEnd3 && cps == &first) ||
15435       (cb == SettingsMenuIfReady && cps == &second) ||
15436       (cb == LoadEngine) ||
15437       (cb == TwoMachinesEventIfReady)) {
15438     CancelDelayedEvent();
15439     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15440   }
15441   cps->initDone = val;
15442 }
15443
15444 /* Parse feature command from engine */
15445 void
15446 ParseFeatures (char *args, ChessProgramState *cps)
15447 {
15448   char *p = args;
15449   char *q;
15450   int val;
15451   char buf[MSG_SIZ];
15452
15453   for (;;) {
15454     while (*p == ' ') p++;
15455     if (*p == NULLCHAR) return;
15456
15457     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15458     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15459     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15460     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15461     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15462     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15463     if (BoolFeature(&p, "reuse", &val, cps)) {
15464       /* Engine can disable reuse, but can't enable it if user said no */
15465       if (!val) cps->reuse = FALSE;
15466       continue;
15467     }
15468     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15469     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15470       if (gameMode == TwoMachinesPlay) {
15471         DisplayTwoMachinesTitle();
15472       } else {
15473         DisplayTitle("");
15474       }
15475       continue;
15476     }
15477     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15478     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15479     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15480     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15481     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15482     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15483     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15484     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15485     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15486     if (IntFeature(&p, "done", &val, cps)) {
15487       FeatureDone(cps, val);
15488       continue;
15489     }
15490     /* Added by Tord: */
15491     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15492     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15493     /* End of additions by Tord */
15494
15495     /* [HGM] added features: */
15496     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15497     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15498     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15499     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15500     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15501     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15502     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15503         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15504           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15505             SendToProgram(buf, cps);
15506             continue;
15507         }
15508         if(cps->nrOptions >= MAX_OPTIONS) {
15509             cps->nrOptions--;
15510             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15511             DisplayError(buf, 0);
15512         }
15513         continue;
15514     }
15515     /* End of additions by HGM */
15516
15517     /* unknown feature: complain and skip */
15518     q = p;
15519     while (*q && *q != '=') q++;
15520     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15521     SendToProgram(buf, cps);
15522     p = q;
15523     if (*p == '=') {
15524       p++;
15525       if (*p == '\"') {
15526         p++;
15527         while (*p && *p != '\"') p++;
15528         if (*p == '\"') p++;
15529       } else {
15530         while (*p && *p != ' ') p++;
15531       }
15532     }
15533   }
15534
15535 }
15536
15537 void
15538 PeriodicUpdatesEvent (int newState)
15539 {
15540     if (newState == appData.periodicUpdates)
15541       return;
15542
15543     appData.periodicUpdates=newState;
15544
15545     /* Display type changes, so update it now */
15546 //    DisplayAnalysis();
15547
15548     /* Get the ball rolling again... */
15549     if (newState) {
15550         AnalysisPeriodicEvent(1);
15551         StartAnalysisClock();
15552     }
15553 }
15554
15555 void
15556 PonderNextMoveEvent (int newState)
15557 {
15558     if (newState == appData.ponderNextMove) return;
15559     if (gameMode == EditPosition) EditPositionDone(TRUE);
15560     if (newState) {
15561         SendToProgram("hard\n", &first);
15562         if (gameMode == TwoMachinesPlay) {
15563             SendToProgram("hard\n", &second);
15564         }
15565     } else {
15566         SendToProgram("easy\n", &first);
15567         thinkOutput[0] = NULLCHAR;
15568         if (gameMode == TwoMachinesPlay) {
15569             SendToProgram("easy\n", &second);
15570         }
15571     }
15572     appData.ponderNextMove = newState;
15573 }
15574
15575 void
15576 NewSettingEvent (int option, int *feature, char *command, int value)
15577 {
15578     char buf[MSG_SIZ];
15579
15580     if (gameMode == EditPosition) EditPositionDone(TRUE);
15581     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15582     if(feature == NULL || *feature) SendToProgram(buf, &first);
15583     if (gameMode == TwoMachinesPlay) {
15584         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15585     }
15586 }
15587
15588 void
15589 ShowThinkingEvent ()
15590 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15591 {
15592     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15593     int newState = appData.showThinking
15594         // [HGM] thinking: other features now need thinking output as well
15595         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15596
15597     if (oldState == newState) return;
15598     oldState = newState;
15599     if (gameMode == EditPosition) EditPositionDone(TRUE);
15600     if (oldState) {
15601         SendToProgram("post\n", &first);
15602         if (gameMode == TwoMachinesPlay) {
15603             SendToProgram("post\n", &second);
15604         }
15605     } else {
15606         SendToProgram("nopost\n", &first);
15607         thinkOutput[0] = NULLCHAR;
15608         if (gameMode == TwoMachinesPlay) {
15609             SendToProgram("nopost\n", &second);
15610         }
15611     }
15612 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15613 }
15614
15615 void
15616 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15617 {
15618   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15619   if (pr == NoProc) return;
15620   AskQuestion(title, question, replyPrefix, pr);
15621 }
15622
15623 void
15624 TypeInEvent (char firstChar)
15625 {
15626     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15627         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15628         gameMode == AnalyzeMode || gameMode == EditGame || 
15629         gameMode == EditPosition || gameMode == IcsExamining ||
15630         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15631         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15632                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15633                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15634         gameMode == Training) PopUpMoveDialog(firstChar);
15635 }
15636
15637 void
15638 TypeInDoneEvent (char *move)
15639 {
15640         Board board;
15641         int n, fromX, fromY, toX, toY;
15642         char promoChar;
15643         ChessMove moveType;
15644
15645         // [HGM] FENedit
15646         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15647                 EditPositionPasteFEN(move);
15648                 return;
15649         }
15650         // [HGM] movenum: allow move number to be typed in any mode
15651         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15652           ToNrEvent(2*n-1);
15653           return;
15654         }
15655         // undocumented kludge: allow command-line option to be typed in!
15656         // (potentially fatal, and does not implement the effect of the option.)
15657         // should only be used for options that are values on which future decisions will be made,
15658         // and definitely not on options that would be used during initialization.
15659         if(strstr(move, "!!! -") == move) {
15660             ParseArgsFromString(move+4);
15661             return;
15662         }
15663
15664       if (gameMode != EditGame && currentMove != forwardMostMove && 
15665         gameMode != Training) {
15666         DisplayMoveError(_("Displayed move is not current"));
15667       } else {
15668         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15669           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15670         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15671         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15672           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15673           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15674         } else {
15675           DisplayMoveError(_("Could not parse move"));
15676         }
15677       }
15678 }
15679
15680 void
15681 DisplayMove (int moveNumber)
15682 {
15683     char message[MSG_SIZ];
15684     char res[MSG_SIZ];
15685     char cpThinkOutput[MSG_SIZ];
15686
15687     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15688
15689     if (moveNumber == forwardMostMove - 1 ||
15690         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15691
15692         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15693
15694         if (strchr(cpThinkOutput, '\n')) {
15695             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15696         }
15697     } else {
15698         *cpThinkOutput = NULLCHAR;
15699     }
15700
15701     /* [AS] Hide thinking from human user */
15702     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15703         *cpThinkOutput = NULLCHAR;
15704         if( thinkOutput[0] != NULLCHAR ) {
15705             int i;
15706
15707             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15708                 cpThinkOutput[i] = '.';
15709             }
15710             cpThinkOutput[i] = NULLCHAR;
15711             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15712         }
15713     }
15714
15715     if (moveNumber == forwardMostMove - 1 &&
15716         gameInfo.resultDetails != NULL) {
15717         if (gameInfo.resultDetails[0] == NULLCHAR) {
15718           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15719         } else {
15720           snprintf(res, MSG_SIZ, " {%s} %s",
15721                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15722         }
15723     } else {
15724         res[0] = NULLCHAR;
15725     }
15726
15727     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15728         DisplayMessage(res, cpThinkOutput);
15729     } else {
15730       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15731                 WhiteOnMove(moveNumber) ? " " : ".. ",
15732                 parseList[moveNumber], res);
15733         DisplayMessage(message, cpThinkOutput);
15734     }
15735 }
15736
15737 void
15738 DisplayComment (int moveNumber, char *text)
15739 {
15740     char title[MSG_SIZ];
15741
15742     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15743       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15744     } else {
15745       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15746               WhiteOnMove(moveNumber) ? " " : ".. ",
15747               parseList[moveNumber]);
15748     }
15749     if (text != NULL && (appData.autoDisplayComment || commentUp))
15750         CommentPopUp(title, text);
15751 }
15752
15753 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15754  * might be busy thinking or pondering.  It can be omitted if your
15755  * gnuchess is configured to stop thinking immediately on any user
15756  * input.  However, that gnuchess feature depends on the FIONREAD
15757  * ioctl, which does not work properly on some flavors of Unix.
15758  */
15759 void
15760 Attention (ChessProgramState *cps)
15761 {
15762 #if ATTENTION
15763     if (!cps->useSigint) return;
15764     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15765     switch (gameMode) {
15766       case MachinePlaysWhite:
15767       case MachinePlaysBlack:
15768       case TwoMachinesPlay:
15769       case IcsPlayingWhite:
15770       case IcsPlayingBlack:
15771       case AnalyzeMode:
15772       case AnalyzeFile:
15773         /* Skip if we know it isn't thinking */
15774         if (!cps->maybeThinking) return;
15775         if (appData.debugMode)
15776           fprintf(debugFP, "Interrupting %s\n", cps->which);
15777         InterruptChildProcess(cps->pr);
15778         cps->maybeThinking = FALSE;
15779         break;
15780       default:
15781         break;
15782     }
15783 #endif /*ATTENTION*/
15784 }
15785
15786 int
15787 CheckFlags ()
15788 {
15789     if (whiteTimeRemaining <= 0) {
15790         if (!whiteFlag) {
15791             whiteFlag = TRUE;
15792             if (appData.icsActive) {
15793                 if (appData.autoCallFlag &&
15794                     gameMode == IcsPlayingBlack && !blackFlag) {
15795                   SendToICS(ics_prefix);
15796                   SendToICS("flag\n");
15797                 }
15798             } else {
15799                 if (blackFlag) {
15800                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15801                 } else {
15802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15803                     if (appData.autoCallFlag) {
15804                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15805                         return TRUE;
15806                     }
15807                 }
15808             }
15809         }
15810     }
15811     if (blackTimeRemaining <= 0) {
15812         if (!blackFlag) {
15813             blackFlag = TRUE;
15814             if (appData.icsActive) {
15815                 if (appData.autoCallFlag &&
15816                     gameMode == IcsPlayingWhite && !whiteFlag) {
15817                   SendToICS(ics_prefix);
15818                   SendToICS("flag\n");
15819                 }
15820             } else {
15821                 if (whiteFlag) {
15822                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15823                 } else {
15824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15825                     if (appData.autoCallFlag) {
15826                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15827                         return TRUE;
15828                     }
15829                 }
15830             }
15831         }
15832     }
15833     return FALSE;
15834 }
15835
15836 void
15837 CheckTimeControl ()
15838 {
15839     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15840         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15841
15842     /*
15843      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15844      */
15845     if ( !WhiteOnMove(forwardMostMove) ) {
15846         /* White made time control */
15847         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15848         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15849         /* [HGM] time odds: correct new time quota for time odds! */
15850                                             / WhitePlayer()->timeOdds;
15851         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15852     } else {
15853         lastBlack -= blackTimeRemaining;
15854         /* Black made time control */
15855         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15856                                             / WhitePlayer()->other->timeOdds;
15857         lastWhite = whiteTimeRemaining;
15858     }
15859 }
15860
15861 void
15862 DisplayBothClocks ()
15863 {
15864     int wom = gameMode == EditPosition ?
15865       !blackPlaysFirst : WhiteOnMove(currentMove);
15866     DisplayWhiteClock(whiteTimeRemaining, wom);
15867     DisplayBlackClock(blackTimeRemaining, !wom);
15868 }
15869
15870
15871 /* Timekeeping seems to be a portability nightmare.  I think everyone
15872    has ftime(), but I'm really not sure, so I'm including some ifdefs
15873    to use other calls if you don't.  Clocks will be less accurate if
15874    you have neither ftime nor gettimeofday.
15875 */
15876
15877 /* VS 2008 requires the #include outside of the function */
15878 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15879 #include <sys/timeb.h>
15880 #endif
15881
15882 /* Get the current time as a TimeMark */
15883 void
15884 GetTimeMark (TimeMark *tm)
15885 {
15886 #if HAVE_GETTIMEOFDAY
15887
15888     struct timeval timeVal;
15889     struct timezone timeZone;
15890
15891     gettimeofday(&timeVal, &timeZone);
15892     tm->sec = (long) timeVal.tv_sec;
15893     tm->ms = (int) (timeVal.tv_usec / 1000L);
15894
15895 #else /*!HAVE_GETTIMEOFDAY*/
15896 #if HAVE_FTIME
15897
15898 // include <sys/timeb.h> / moved to just above start of function
15899     struct timeb timeB;
15900
15901     ftime(&timeB);
15902     tm->sec = (long) timeB.time;
15903     tm->ms = (int) timeB.millitm;
15904
15905 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15906     tm->sec = (long) time(NULL);
15907     tm->ms = 0;
15908 #endif
15909 #endif
15910 }
15911
15912 /* Return the difference in milliseconds between two
15913    time marks.  We assume the difference will fit in a long!
15914 */
15915 long
15916 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15917 {
15918     return 1000L*(tm2->sec - tm1->sec) +
15919            (long) (tm2->ms - tm1->ms);
15920 }
15921
15922
15923 /*
15924  * Code to manage the game clocks.
15925  *
15926  * In tournament play, black starts the clock and then white makes a move.
15927  * We give the human user a slight advantage if he is playing white---the
15928  * clocks don't run until he makes his first move, so it takes zero time.
15929  * Also, we don't account for network lag, so we could get out of sync
15930  * with GNU Chess's clock -- but then, referees are always right.
15931  */
15932
15933 static TimeMark tickStartTM;
15934 static long intendedTickLength;
15935
15936 long
15937 NextTickLength (long timeRemaining)
15938 {
15939     long nominalTickLength, nextTickLength;
15940
15941     if (timeRemaining > 0L && timeRemaining <= 10000L)
15942       nominalTickLength = 100L;
15943     else
15944       nominalTickLength = 1000L;
15945     nextTickLength = timeRemaining % nominalTickLength;
15946     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15947
15948     return nextTickLength;
15949 }
15950
15951 /* Adjust clock one minute up or down */
15952 void
15953 AdjustClock (Boolean which, int dir)
15954 {
15955     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15956     if(which) blackTimeRemaining += 60000*dir;
15957     else      whiteTimeRemaining += 60000*dir;
15958     DisplayBothClocks();
15959     adjustedClock = TRUE;
15960 }
15961
15962 /* Stop clocks and reset to a fresh time control */
15963 void
15964 ResetClocks ()
15965 {
15966     (void) StopClockTimer();
15967     if (appData.icsActive) {
15968         whiteTimeRemaining = blackTimeRemaining = 0;
15969     } else if (searchTime) {
15970         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15971         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15972     } else { /* [HGM] correct new time quote for time odds */
15973         whiteTC = blackTC = fullTimeControlString;
15974         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15975         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15976     }
15977     if (whiteFlag || blackFlag) {
15978         DisplayTitle("");
15979         whiteFlag = blackFlag = FALSE;
15980     }
15981     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15982     DisplayBothClocks();
15983     adjustedClock = FALSE;
15984 }
15985
15986 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15987
15988 /* Decrement running clock by amount of time that has passed */
15989 void
15990 DecrementClocks ()
15991 {
15992     long timeRemaining;
15993     long lastTickLength, fudge;
15994     TimeMark now;
15995
15996     if (!appData.clockMode) return;
15997     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15998
15999     GetTimeMark(&now);
16000
16001     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16002
16003     /* Fudge if we woke up a little too soon */
16004     fudge = intendedTickLength - lastTickLength;
16005     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16006
16007     if (WhiteOnMove(forwardMostMove)) {
16008         if(whiteNPS >= 0) lastTickLength = 0;
16009         timeRemaining = whiteTimeRemaining -= lastTickLength;
16010         if(timeRemaining < 0 && !appData.icsActive) {
16011             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16012             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16013                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16014                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16015             }
16016         }
16017         DisplayWhiteClock(whiteTimeRemaining - fudge,
16018                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16019     } else {
16020         if(blackNPS >= 0) lastTickLength = 0;
16021         timeRemaining = blackTimeRemaining -= lastTickLength;
16022         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16023             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16024             if(suddenDeath) {
16025                 blackStartMove = forwardMostMove;
16026                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16027             }
16028         }
16029         DisplayBlackClock(blackTimeRemaining - fudge,
16030                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16031     }
16032     if (CheckFlags()) return;
16033
16034     tickStartTM = now;
16035     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16036     StartClockTimer(intendedTickLength);
16037
16038     /* if the time remaining has fallen below the alarm threshold, sound the
16039      * alarm. if the alarm has sounded and (due to a takeback or time control
16040      * with increment) the time remaining has increased to a level above the
16041      * threshold, reset the alarm so it can sound again.
16042      */
16043
16044     if (appData.icsActive && appData.icsAlarm) {
16045
16046         /* make sure we are dealing with the user's clock */
16047         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16048                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16049            )) return;
16050
16051         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16052             alarmSounded = FALSE;
16053         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16054             PlayAlarmSound();
16055             alarmSounded = TRUE;
16056         }
16057     }
16058 }
16059
16060
16061 /* A player has just moved, so stop the previously running
16062    clock and (if in clock mode) start the other one.
16063    We redisplay both clocks in case we're in ICS mode, because
16064    ICS gives us an update to both clocks after every move.
16065    Note that this routine is called *after* forwardMostMove
16066    is updated, so the last fractional tick must be subtracted
16067    from the color that is *not* on move now.
16068 */
16069 void
16070 SwitchClocks (int newMoveNr)
16071 {
16072     long lastTickLength;
16073     TimeMark now;
16074     int flagged = FALSE;
16075
16076     GetTimeMark(&now);
16077
16078     if (StopClockTimer() && appData.clockMode) {
16079         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16080         if (!WhiteOnMove(forwardMostMove)) {
16081             if(blackNPS >= 0) lastTickLength = 0;
16082             blackTimeRemaining -= lastTickLength;
16083            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16084 //         if(pvInfoList[forwardMostMove].time == -1)
16085                  pvInfoList[forwardMostMove].time =               // use GUI time
16086                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16087         } else {
16088            if(whiteNPS >= 0) lastTickLength = 0;
16089            whiteTimeRemaining -= lastTickLength;
16090            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16091 //         if(pvInfoList[forwardMostMove].time == -1)
16092                  pvInfoList[forwardMostMove].time =
16093                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16094         }
16095         flagged = CheckFlags();
16096     }
16097     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16098     CheckTimeControl();
16099
16100     if (flagged || !appData.clockMode) return;
16101
16102     switch (gameMode) {
16103       case MachinePlaysBlack:
16104       case MachinePlaysWhite:
16105       case BeginningOfGame:
16106         if (pausing) return;
16107         break;
16108
16109       case EditGame:
16110       case PlayFromGameFile:
16111       case IcsExamining:
16112         return;
16113
16114       default:
16115         break;
16116     }
16117
16118     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16119         if(WhiteOnMove(forwardMostMove))
16120              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16121         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16122     }
16123
16124     tickStartTM = now;
16125     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16126       whiteTimeRemaining : blackTimeRemaining);
16127     StartClockTimer(intendedTickLength);
16128 }
16129
16130
16131 /* Stop both clocks */
16132 void
16133 StopClocks ()
16134 {
16135     long lastTickLength;
16136     TimeMark now;
16137
16138     if (!StopClockTimer()) return;
16139     if (!appData.clockMode) return;
16140
16141     GetTimeMark(&now);
16142
16143     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16144     if (WhiteOnMove(forwardMostMove)) {
16145         if(whiteNPS >= 0) lastTickLength = 0;
16146         whiteTimeRemaining -= lastTickLength;
16147         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16148     } else {
16149         if(blackNPS >= 0) lastTickLength = 0;
16150         blackTimeRemaining -= lastTickLength;
16151         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16152     }
16153     CheckFlags();
16154 }
16155
16156 /* Start clock of player on move.  Time may have been reset, so
16157    if clock is already running, stop and restart it. */
16158 void
16159 StartClocks ()
16160 {
16161     (void) StopClockTimer(); /* in case it was running already */
16162     DisplayBothClocks();
16163     if (CheckFlags()) return;
16164
16165     if (!appData.clockMode) return;
16166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16167
16168     GetTimeMark(&tickStartTM);
16169     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16170       whiteTimeRemaining : blackTimeRemaining);
16171
16172    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16173     whiteNPS = blackNPS = -1;
16174     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16175        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16176         whiteNPS = first.nps;
16177     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16178        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16179         blackNPS = first.nps;
16180     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16181         whiteNPS = second.nps;
16182     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16183         blackNPS = second.nps;
16184     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16185
16186     StartClockTimer(intendedTickLength);
16187 }
16188
16189 char *
16190 TimeString (long ms)
16191 {
16192     long second, minute, hour, day;
16193     char *sign = "";
16194     static char buf[32];
16195
16196     if (ms > 0 && ms <= 9900) {
16197       /* convert milliseconds to tenths, rounding up */
16198       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16199
16200       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16201       return buf;
16202     }
16203
16204     /* convert milliseconds to seconds, rounding up */
16205     /* use floating point to avoid strangeness of integer division
16206        with negative dividends on many machines */
16207     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16208
16209     if (second < 0) {
16210         sign = "-";
16211         second = -second;
16212     }
16213
16214     day = second / (60 * 60 * 24);
16215     second = second % (60 * 60 * 24);
16216     hour = second / (60 * 60);
16217     second = second % (60 * 60);
16218     minute = second / 60;
16219     second = second % 60;
16220
16221     if (day > 0)
16222       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16223               sign, day, hour, minute, second);
16224     else if (hour > 0)
16225       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16226     else
16227       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16228
16229     return buf;
16230 }
16231
16232
16233 /*
16234  * This is necessary because some C libraries aren't ANSI C compliant yet.
16235  */
16236 char *
16237 StrStr (char *string, char *match)
16238 {
16239     int i, length;
16240
16241     length = strlen(match);
16242
16243     for (i = strlen(string) - length; i >= 0; i--, string++)
16244       if (!strncmp(match, string, length))
16245         return string;
16246
16247     return NULL;
16248 }
16249
16250 char *
16251 StrCaseStr (char *string, char *match)
16252 {
16253     int i, j, length;
16254
16255     length = strlen(match);
16256
16257     for (i = strlen(string) - length; i >= 0; i--, string++) {
16258         for (j = 0; j < length; j++) {
16259             if (ToLower(match[j]) != ToLower(string[j]))
16260               break;
16261         }
16262         if (j == length) return string;
16263     }
16264
16265     return NULL;
16266 }
16267
16268 #ifndef _amigados
16269 int
16270 StrCaseCmp (char *s1, char *s2)
16271 {
16272     char c1, c2;
16273
16274     for (;;) {
16275         c1 = ToLower(*s1++);
16276         c2 = ToLower(*s2++);
16277         if (c1 > c2) return 1;
16278         if (c1 < c2) return -1;
16279         if (c1 == NULLCHAR) return 0;
16280     }
16281 }
16282
16283
16284 int
16285 ToLower (int c)
16286 {
16287     return isupper(c) ? tolower(c) : c;
16288 }
16289
16290
16291 int
16292 ToUpper (int c)
16293 {
16294     return islower(c) ? toupper(c) : c;
16295 }
16296 #endif /* !_amigados    */
16297
16298 char *
16299 StrSave (char *s)
16300 {
16301   char *ret;
16302
16303   if ((ret = (char *) malloc(strlen(s) + 1)))
16304     {
16305       safeStrCpy(ret, s, strlen(s)+1);
16306     }
16307   return ret;
16308 }
16309
16310 char *
16311 StrSavePtr (char *s, char **savePtr)
16312 {
16313     if (*savePtr) {
16314         free(*savePtr);
16315     }
16316     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16317       safeStrCpy(*savePtr, s, strlen(s)+1);
16318     }
16319     return(*savePtr);
16320 }
16321
16322 char *
16323 PGNDate ()
16324 {
16325     time_t clock;
16326     struct tm *tm;
16327     char buf[MSG_SIZ];
16328
16329     clock = time((time_t *)NULL);
16330     tm = localtime(&clock);
16331     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16332             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16333     return StrSave(buf);
16334 }
16335
16336
16337 char *
16338 PositionToFEN (int move, char *overrideCastling)
16339 {
16340     int i, j, fromX, fromY, toX, toY;
16341     int whiteToPlay;
16342     char buf[MSG_SIZ];
16343     char *p, *q;
16344     int emptycount;
16345     ChessSquare piece;
16346
16347     whiteToPlay = (gameMode == EditPosition) ?
16348       !blackPlaysFirst : (move % 2 == 0);
16349     p = buf;
16350
16351     /* Piece placement data */
16352     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16353         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16354         emptycount = 0;
16355         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16356             if (boards[move][i][j] == EmptySquare) {
16357                 emptycount++;
16358             } else { ChessSquare piece = boards[move][i][j];
16359                 if (emptycount > 0) {
16360                     if(emptycount<10) /* [HGM] can be >= 10 */
16361                         *p++ = '0' + emptycount;
16362                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16363                     emptycount = 0;
16364                 }
16365                 if(PieceToChar(piece) == '+') {
16366                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16367                     *p++ = '+';
16368                     piece = (ChessSquare)(DEMOTED piece);
16369                 }
16370                 *p++ = PieceToChar(piece);
16371                 if(p[-1] == '~') {
16372                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16373                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16374                     *p++ = '~';
16375                 }
16376             }
16377         }
16378         if (emptycount > 0) {
16379             if(emptycount<10) /* [HGM] can be >= 10 */
16380                 *p++ = '0' + emptycount;
16381             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16382             emptycount = 0;
16383         }
16384         *p++ = '/';
16385     }
16386     *(p - 1) = ' ';
16387
16388     /* [HGM] print Crazyhouse or Shogi holdings */
16389     if( gameInfo.holdingsWidth ) {
16390         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16391         q = p;
16392         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16393             piece = boards[move][i][BOARD_WIDTH-1];
16394             if( piece != EmptySquare )
16395               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16396                   *p++ = PieceToChar(piece);
16397         }
16398         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16399             piece = boards[move][BOARD_HEIGHT-i-1][0];
16400             if( piece != EmptySquare )
16401               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16402                   *p++ = PieceToChar(piece);
16403         }
16404
16405         if( q == p ) *p++ = '-';
16406         *p++ = ']';
16407         *p++ = ' ';
16408     }
16409
16410     /* Active color */
16411     *p++ = whiteToPlay ? 'w' : 'b';
16412     *p++ = ' ';
16413
16414   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16415     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16416   } else {
16417   if(nrCastlingRights) {
16418      q = p;
16419      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16420        /* [HGM] write directly from rights */
16421            if(boards[move][CASTLING][2] != NoRights &&
16422               boards[move][CASTLING][0] != NoRights   )
16423                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16424            if(boards[move][CASTLING][2] != NoRights &&
16425               boards[move][CASTLING][1] != NoRights   )
16426                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16427            if(boards[move][CASTLING][5] != NoRights &&
16428               boards[move][CASTLING][3] != NoRights   )
16429                 *p++ = boards[move][CASTLING][3] + AAA;
16430            if(boards[move][CASTLING][5] != NoRights &&
16431               boards[move][CASTLING][4] != NoRights   )
16432                 *p++ = boards[move][CASTLING][4] + AAA;
16433      } else {
16434
16435         /* [HGM] write true castling rights */
16436         if( nrCastlingRights == 6 ) {
16437             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16438                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16439             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16440                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16441             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16442                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16443             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16444                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16445         }
16446      }
16447      if (q == p) *p++ = '-'; /* No castling rights */
16448      *p++ = ' ';
16449   }
16450
16451   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16452      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16453     /* En passant target square */
16454     if (move > backwardMostMove) {
16455         fromX = moveList[move - 1][0] - AAA;
16456         fromY = moveList[move - 1][1] - ONE;
16457         toX = moveList[move - 1][2] - AAA;
16458         toY = moveList[move - 1][3] - ONE;
16459         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16460             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16461             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16462             fromX == toX) {
16463             /* 2-square pawn move just happened */
16464             *p++ = toX + AAA;
16465             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16466         } else {
16467             *p++ = '-';
16468         }
16469     } else if(move == backwardMostMove) {
16470         // [HGM] perhaps we should always do it like this, and forget the above?
16471         if((signed char)boards[move][EP_STATUS] >= 0) {
16472             *p++ = boards[move][EP_STATUS] + AAA;
16473             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16474         } else {
16475             *p++ = '-';
16476         }
16477     } else {
16478         *p++ = '-';
16479     }
16480     *p++ = ' ';
16481   }
16482   }
16483
16484     /* [HGM] find reversible plies */
16485     {   int i = 0, j=move;
16486
16487         if (appData.debugMode) { int k;
16488             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16489             for(k=backwardMostMove; k<=forwardMostMove; k++)
16490                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16491
16492         }
16493
16494         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16495         if( j == backwardMostMove ) i += initialRulePlies;
16496         sprintf(p, "%d ", i);
16497         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16498     }
16499     /* Fullmove number */
16500     sprintf(p, "%d", (move / 2) + 1);
16501
16502     return StrSave(buf);
16503 }
16504
16505 Boolean
16506 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16507 {
16508     int i, j;
16509     char *p, c;
16510     int emptycount;
16511     ChessSquare piece;
16512
16513     p = fen;
16514
16515     /* [HGM] by default clear Crazyhouse holdings, if present */
16516     if(gameInfo.holdingsWidth) {
16517        for(i=0; i<BOARD_HEIGHT; i++) {
16518            board[i][0]             = EmptySquare; /* black holdings */
16519            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16520            board[i][1]             = (ChessSquare) 0; /* black counts */
16521            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16522        }
16523     }
16524
16525     /* Piece placement data */
16526     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16527         j = 0;
16528         for (;;) {
16529             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16530                 if (*p == '/') p++;
16531                 emptycount = gameInfo.boardWidth - j;
16532                 while (emptycount--)
16533                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16534                 break;
16535 #if(BOARD_FILES >= 10)
16536             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16537                 p++; emptycount=10;
16538                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16539                 while (emptycount--)
16540                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16541 #endif
16542             } else if (isdigit(*p)) {
16543                 emptycount = *p++ - '0';
16544                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16545                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16546                 while (emptycount--)
16547                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16548             } else if (*p == '+' || isalpha(*p)) {
16549                 if (j >= gameInfo.boardWidth) return FALSE;
16550                 if(*p=='+') {
16551                     piece = CharToPiece(*++p);
16552                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16553                     piece = (ChessSquare) (PROMOTED piece ); p++;
16554                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16555                 } else piece = CharToPiece(*p++);
16556
16557                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16558                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16559                     piece = (ChessSquare) (PROMOTED piece);
16560                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16561                     p++;
16562                 }
16563                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16564             } else {
16565                 return FALSE;
16566             }
16567         }
16568     }
16569     while (*p == '/' || *p == ' ') p++;
16570
16571     /* [HGM] look for Crazyhouse holdings here */
16572     while(*p==' ') p++;
16573     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16574         if(*p == '[') p++;
16575         if(*p == '-' ) p++; /* empty holdings */ else {
16576             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16577             /* if we would allow FEN reading to set board size, we would   */
16578             /* have to add holdings and shift the board read so far here   */
16579             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16580                 p++;
16581                 if((int) piece >= (int) BlackPawn ) {
16582                     i = (int)piece - (int)BlackPawn;
16583                     i = PieceToNumber((ChessSquare)i);
16584                     if( i >= gameInfo.holdingsSize ) return FALSE;
16585                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16586                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16587                 } else {
16588                     i = (int)piece - (int)WhitePawn;
16589                     i = PieceToNumber((ChessSquare)i);
16590                     if( i >= gameInfo.holdingsSize ) return FALSE;
16591                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16592                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16593                 }
16594             }
16595         }
16596         if(*p == ']') p++;
16597     }
16598
16599     while(*p == ' ') p++;
16600
16601     /* Active color */
16602     c = *p++;
16603     if(appData.colorNickNames) {
16604       if( c == appData.colorNickNames[0] ) c = 'w'; else
16605       if( c == appData.colorNickNames[1] ) c = 'b';
16606     }
16607     switch (c) {
16608       case 'w':
16609         *blackPlaysFirst = FALSE;
16610         break;
16611       case 'b':
16612         *blackPlaysFirst = TRUE;
16613         break;
16614       default:
16615         return FALSE;
16616     }
16617
16618     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16619     /* return the extra info in global variiables             */
16620
16621     /* set defaults in case FEN is incomplete */
16622     board[EP_STATUS] = EP_UNKNOWN;
16623     for(i=0; i<nrCastlingRights; i++ ) {
16624         board[CASTLING][i] =
16625             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16626     }   /* assume possible unless obviously impossible */
16627     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16628     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16629     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16630                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16631     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16632     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16633     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16634                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16635     FENrulePlies = 0;
16636
16637     while(*p==' ') p++;
16638     if(nrCastlingRights) {
16639       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16640           /* castling indicator present, so default becomes no castlings */
16641           for(i=0; i<nrCastlingRights; i++ ) {
16642                  board[CASTLING][i] = NoRights;
16643           }
16644       }
16645       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16646              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16647              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16648              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16649         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16650
16651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16652             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16653             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16654         }
16655         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16656             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16657         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16658                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16659         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16660                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16661         switch(c) {
16662           case'K':
16663               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16664               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16665               board[CASTLING][2] = whiteKingFile;
16666               break;
16667           case'Q':
16668               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16669               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16670               board[CASTLING][2] = whiteKingFile;
16671               break;
16672           case'k':
16673               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16674               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16675               board[CASTLING][5] = blackKingFile;
16676               break;
16677           case'q':
16678               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16679               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16680               board[CASTLING][5] = blackKingFile;
16681           case '-':
16682               break;
16683           default: /* FRC castlings */
16684               if(c >= 'a') { /* black rights */
16685                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16686                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16687                   if(i == BOARD_RGHT) break;
16688                   board[CASTLING][5] = i;
16689                   c -= AAA;
16690                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16691                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16692                   if(c > i)
16693                       board[CASTLING][3] = c;
16694                   else
16695                       board[CASTLING][4] = c;
16696               } else { /* white rights */
16697                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16698                     if(board[0][i] == WhiteKing) break;
16699                   if(i == BOARD_RGHT) break;
16700                   board[CASTLING][2] = i;
16701                   c -= AAA - 'a' + 'A';
16702                   if(board[0][c] >= WhiteKing) break;
16703                   if(c > i)
16704                       board[CASTLING][0] = c;
16705                   else
16706                       board[CASTLING][1] = c;
16707               }
16708         }
16709       }
16710       for(i=0; i<nrCastlingRights; i++)
16711         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16712     if (appData.debugMode) {
16713         fprintf(debugFP, "FEN castling rights:");
16714         for(i=0; i<nrCastlingRights; i++)
16715         fprintf(debugFP, " %d", board[CASTLING][i]);
16716         fprintf(debugFP, "\n");
16717     }
16718
16719       while(*p==' ') p++;
16720     }
16721
16722     /* read e.p. field in games that know e.p. capture */
16723     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16724        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16725       if(*p=='-') {
16726         p++; board[EP_STATUS] = EP_NONE;
16727       } else {
16728          char c = *p++ - AAA;
16729
16730          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16731          if(*p >= '0' && *p <='9') p++;
16732          board[EP_STATUS] = c;
16733       }
16734     }
16735
16736
16737     if(sscanf(p, "%d", &i) == 1) {
16738         FENrulePlies = i; /* 50-move ply counter */
16739         /* (The move number is still ignored)    */
16740     }
16741
16742     return TRUE;
16743 }
16744
16745 void
16746 EditPositionPasteFEN (char *fen)
16747 {
16748   if (fen != NULL) {
16749     Board initial_position;
16750
16751     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16752       DisplayError(_("Bad FEN position in clipboard"), 0);
16753       return ;
16754     } else {
16755       int savedBlackPlaysFirst = blackPlaysFirst;
16756       EditPositionEvent();
16757       blackPlaysFirst = savedBlackPlaysFirst;
16758       CopyBoard(boards[0], initial_position);
16759       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16760       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16761       DisplayBothClocks();
16762       DrawPosition(FALSE, boards[currentMove]);
16763     }
16764   }
16765 }
16766
16767 static char cseq[12] = "\\   ";
16768
16769 Boolean
16770 set_cont_sequence (char *new_seq)
16771 {
16772     int len;
16773     Boolean ret;
16774
16775     // handle bad attempts to set the sequence
16776         if (!new_seq)
16777                 return 0; // acceptable error - no debug
16778
16779     len = strlen(new_seq);
16780     ret = (len > 0) && (len < sizeof(cseq));
16781     if (ret)
16782       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16783     else if (appData.debugMode)
16784       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16785     return ret;
16786 }
16787
16788 /*
16789     reformat a source message so words don't cross the width boundary.  internal
16790     newlines are not removed.  returns the wrapped size (no null character unless
16791     included in source message).  If dest is NULL, only calculate the size required
16792     for the dest buffer.  lp argument indicats line position upon entry, and it's
16793     passed back upon exit.
16794 */
16795 int
16796 wrap (char *dest, char *src, int count, int width, int *lp)
16797 {
16798     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16799
16800     cseq_len = strlen(cseq);
16801     old_line = line = *lp;
16802     ansi = len = clen = 0;
16803
16804     for (i=0; i < count; i++)
16805     {
16806         if (src[i] == '\033')
16807             ansi = 1;
16808
16809         // if we hit the width, back up
16810         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16811         {
16812             // store i & len in case the word is too long
16813             old_i = i, old_len = len;
16814
16815             // find the end of the last word
16816             while (i && src[i] != ' ' && src[i] != '\n')
16817             {
16818                 i--;
16819                 len--;
16820             }
16821
16822             // word too long?  restore i & len before splitting it
16823             if ((old_i-i+clen) >= width)
16824             {
16825                 i = old_i;
16826                 len = old_len;
16827             }
16828
16829             // extra space?
16830             if (i && src[i-1] == ' ')
16831                 len--;
16832
16833             if (src[i] != ' ' && src[i] != '\n')
16834             {
16835                 i--;
16836                 if (len)
16837                     len--;
16838             }
16839
16840             // now append the newline and continuation sequence
16841             if (dest)
16842                 dest[len] = '\n';
16843             len++;
16844             if (dest)
16845                 strncpy(dest+len, cseq, cseq_len);
16846             len += cseq_len;
16847             line = cseq_len;
16848             clen = cseq_len;
16849             continue;
16850         }
16851
16852         if (dest)
16853             dest[len] = src[i];
16854         len++;
16855         if (!ansi)
16856             line++;
16857         if (src[i] == '\n')
16858             line = 0;
16859         if (src[i] == 'm')
16860             ansi = 0;
16861     }
16862     if (dest && appData.debugMode)
16863     {
16864         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16865             count, width, line, len, *lp);
16866         show_bytes(debugFP, src, count);
16867         fprintf(debugFP, "\ndest: ");
16868         show_bytes(debugFP, dest, len);
16869         fprintf(debugFP, "\n");
16870     }
16871     *lp = dest ? line : old_line;
16872
16873     return len;
16874 }
16875
16876 // [HGM] vari: routines for shelving variations
16877 Boolean modeRestore = FALSE;
16878
16879 void
16880 PushInner (int firstMove, int lastMove)
16881 {
16882         int i, j, nrMoves = lastMove - firstMove;
16883
16884         // push current tail of game on stack
16885         savedResult[storedGames] = gameInfo.result;
16886         savedDetails[storedGames] = gameInfo.resultDetails;
16887         gameInfo.resultDetails = NULL;
16888         savedFirst[storedGames] = firstMove;
16889         savedLast [storedGames] = lastMove;
16890         savedFramePtr[storedGames] = framePtr;
16891         framePtr -= nrMoves; // reserve space for the boards
16892         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16893             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16894             for(j=0; j<MOVE_LEN; j++)
16895                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16896             for(j=0; j<2*MOVE_LEN; j++)
16897                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16898             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16899             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16900             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16901             pvInfoList[firstMove+i-1].depth = 0;
16902             commentList[framePtr+i] = commentList[firstMove+i];
16903             commentList[firstMove+i] = NULL;
16904         }
16905
16906         storedGames++;
16907         forwardMostMove = firstMove; // truncate game so we can start variation
16908 }
16909
16910 void
16911 PushTail (int firstMove, int lastMove)
16912 {
16913         if(appData.icsActive) { // only in local mode
16914                 forwardMostMove = currentMove; // mimic old ICS behavior
16915                 return;
16916         }
16917         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16918
16919         PushInner(firstMove, lastMove);
16920         if(storedGames == 1) GreyRevert(FALSE);
16921         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16922 }
16923
16924 void
16925 PopInner (Boolean annotate)
16926 {
16927         int i, j, nrMoves;
16928         char buf[8000], moveBuf[20];
16929
16930         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16931         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16932         nrMoves = savedLast[storedGames] - currentMove;
16933         if(annotate) {
16934                 int cnt = 10;
16935                 if(!WhiteOnMove(currentMove))
16936                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16937                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16938                 for(i=currentMove; i<forwardMostMove; i++) {
16939                         if(WhiteOnMove(i))
16940                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16941                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16942                         strcat(buf, moveBuf);
16943                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16944                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16945                 }
16946                 strcat(buf, ")");
16947         }
16948         for(i=1; i<=nrMoves; i++) { // copy last variation back
16949             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16950             for(j=0; j<MOVE_LEN; j++)
16951                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16952             for(j=0; j<2*MOVE_LEN; j++)
16953                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16954             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16955             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16956             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16957             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16958             commentList[currentMove+i] = commentList[framePtr+i];
16959             commentList[framePtr+i] = NULL;
16960         }
16961         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16962         framePtr = savedFramePtr[storedGames];
16963         gameInfo.result = savedResult[storedGames];
16964         if(gameInfo.resultDetails != NULL) {
16965             free(gameInfo.resultDetails);
16966       }
16967         gameInfo.resultDetails = savedDetails[storedGames];
16968         forwardMostMove = currentMove + nrMoves;
16969 }
16970
16971 Boolean
16972 PopTail (Boolean annotate)
16973 {
16974         if(appData.icsActive) return FALSE; // only in local mode
16975         if(!storedGames) return FALSE; // sanity
16976         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16977
16978         PopInner(annotate);
16979         if(currentMove < forwardMostMove) ForwardEvent(); else
16980         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16981
16982         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16983         return TRUE;
16984 }
16985
16986 void
16987 CleanupTail ()
16988 {       // remove all shelved variations
16989         int i;
16990         for(i=0; i<storedGames; i++) {
16991             if(savedDetails[i])
16992                 free(savedDetails[i]);
16993             savedDetails[i] = NULL;
16994         }
16995         for(i=framePtr; i<MAX_MOVES; i++) {
16996                 if(commentList[i]) free(commentList[i]);
16997                 commentList[i] = NULL;
16998         }
16999         framePtr = MAX_MOVES-1;
17000         storedGames = 0;
17001 }
17002
17003 void
17004 LoadVariation (int index, char *text)
17005 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17006         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17007         int level = 0, move;
17008
17009         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17010         // first find outermost bracketing variation
17011         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17012             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17013                 if(*p == '{') wait = '}'; else
17014                 if(*p == '[') wait = ']'; else
17015                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17016                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17017             }
17018             if(*p == wait) wait = NULLCHAR; // closing ]} found
17019             p++;
17020         }
17021         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17022         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17023         end[1] = NULLCHAR; // clip off comment beyond variation
17024         ToNrEvent(currentMove-1);
17025         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17026         // kludge: use ParsePV() to append variation to game
17027         move = currentMove;
17028         ParsePV(start, TRUE, TRUE);
17029         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17030         ClearPremoveHighlights();
17031         CommentPopDown();
17032         ToNrEvent(currentMove+1);
17033 }
17034